DJOneHub 通话接通无声音与 ADB 接口修复

Posted by 秦 on August 17, 2026

DJOneHub 通话接通无声音与 ADB 接口修复

1. 原始问题和现象

使用 DJOneHub 拨打或接听电话时:

  • 电话可以正常拨出、振铃和接通;
  • AT 通话控制、短信和网络功能基本正常;
  • 接通后双方都听不到声音;
  • DJOneHub 界面显示:
1
2
MaVo 音频准备失败:模块语音路由准备失败:
USB ADB interface (6/adb) not found on 2c7c:0125

日志中的关键记录为:

1
2
3
call state "dialing" -> "active"
module voice: starting route
module voice: start failed: USB ADB interface (6/adb) not found on 2c7c:0125

这说明电话控制链路已经成功,失败发生在电话接通后启动模块语音媒体路由的阶段。

2. 问题的本质

本次问题由两个连续的原因组成。

2.1 模块 ADB 尚未解锁

DJOneHub 的电话控制和音频使用不同链路:

  • 拨号、接听、挂断通过模块 AT 接口完成;
  • 双向声音需要 DJOneHub 通过 USB ADB,把模块侧语音运行时临时部署到模块,并启动 D4/UAC 路由;
  • 因此即使电话已经接通,只要 ADB 接口不存在,仍然不会有声音。

故障时模块的 USB 配置为:

1
+QCFG: "usbcfg",0x2C7C,0x0125,1,1,1,1,1,0,1

倒数第二位为 0,表示 ADB USB 功能未启用;最后一位为 1,表示 USB Audio 已启用。

macOS 当时实际看到的接口布局为:

  • interface 0~3:模块厂商接口;
  • interface 4~5:CDC-ECM 网络接口;
  • interface 6~8:USB Audio;
  • 没有 bInterfaceSubClass = 66 的 ADB 接口。

进一步执行只读查询:

1
AT+QADBKEY?

模块返回:

1
2
+QADBKEY: 30115171
OK

这表明当前固件启用了 Quectel ADB 挑战码保护。没有先提交正确的 QADBKEY,直接写入 ADB 功能位可能暂时返回 OK,但重启后不会得到真正可用的 ADB 接口。

2.2 系统 adb 后台进程抢占了接口

完成模块解锁并重新枚举后,interface 6 已变成正确的 ADB 接口,但 DJOneHub 第一次启动语音路由又返回:

1
claim USB ADB interface 6: LIBUSB_ERROR_ACCESS

检查 macOS USB 注册表后发现:

1
2
ADB Interface@6
UsbExclusiveOwner = "pid 89856, adb"

本机已有一个 Android Platform Tools 的 adb server 长期运行并独占 interface 6。DJOneHub 也需要直接通过 libusb 独占该接口,因此两者不能同时使用。

停止 Android adb server 后,DJOneHub 才能成功打开接口并启动语音路由。

2.3 项目当前逻辑存在的缺口

项目源码中,模块语音路由明确依赖 USB ADB:

1
2
3
4
adb, err := openDJIUSBADB()
if err != nil {
    return err
}

ADB 探测要求找到 interface 6 或 ADB subclass 66,并且必须有一对 Bulk IN/OUT 端点:

1
2
3
if byte(alt.bInterfaceNumber) != 6 && byte(alt.bInterfaceSubClass) != 66 {
    continue
}

但 v1.2.9 又把旧配置 …,1,1,1,1,1,0,1 判断成“通话音频已就绪”。该配置虽然能枚举 USB Audio,却不保证当前固件已解锁并枚举 ADB。因此 UI 可能显示准备完成,真正接通电话后才暴露 ADB 不存在的问题。

3. 总体处理思路和方案

处理过程遵循以下原则:

  1. 先做只读检查,避免盲目修改模块配置;
  2. 确认当前没有语音通话,避免在通话中切换 USB 组合;
  3. 查询当前 USBCFGQADBKEY 挑战码;
  4. 计算并提交本模块对应的 ADB 解锁密码;
  5. 只修改 USBCFG 的 ADB 功能位,保留 VID、PID 和其他功能位;
  6. 写入后立即精确回读,只有完全匹配才允许重启;
  7. 重启后从 macOS USB 描述符验证真实接口,而不是只相信 AT 配置字符串;
  8. 若接口存在但无法打开,检查并释放其他 ADB 客户端的独占占用;
  9. 实际启动并停止一次模块语音路由,验证运行时部署、驱动加载和 D4/UAC 路由完整可用。

4. 完整处理过程

4.1 检查通话状态

先检查 DJOneHub 状态及模块原始 CLCC

1
2
3
4
5
curl -fsS 'http://127.0.0.1:7575/api/calls/status'

curl -fsS -X POST 'http://127.0.0.1:7575/api/at' \
  -H 'Content-Type: application/json' \
  --data-binary '{"command":"AT+CLCC"}'

模块返回了两条 mode=1 的记录:

1
2
3
+CLCC: 2,1,0,1,0,"",128
+CLCC: 1,1,0,1,0,"",128
OK

3GPP/项目解析规则中 mode=0 才是语音,mode=1 是数据会话。因此当时没有活动语音通话,可以安全修改 USB 配置。

4.2 查询原始 USB 配置

1
2
3
curl -fsS -X POST 'http://127.0.0.1:7575/api/at' \
  -H 'Content-Type: application/json' \
  --data-binary '{"command":"AT+QCFG=\"USBCFG\""}'

结果:

1
2
+QCFG: "usbcfg",0x2C7C,0x125,1,1,1,1,1,0,1
OK

项目此前已经保存过原始出厂配置备份:

1
~/Library/Application Support/DJOneHub/module-backups/

原始配置为:

1
2CA3:4006,1,1,1,1,1,0,0

4.3 查询 ADB 挑战码并计算解锁密码

查询命令:

1
AT+QADBKEY?

返回挑战码:

1
30115171

Quectel 常见 QADBKEY 算法使用 MD5-crypt:

1
openssl passwd -1 -salt '30115171' 'SH_adb_quectel'

输出:

1
$1$30115171$zqsKlu75gP1ZamfXKVUmG0

取哈希主体的前 15 个字符,得到本模块当次使用的 ADB 解锁密码:

1
zqsKlu75gP1Zamf

先用公开示例挑战码进行过交叉校验,生成结果与 Quectel 官方社区公开密码一致,再用于当前模块。

4.4 提交 ADB 解锁密码

实际发送:

1
AT+QADBKEY="zqsKlu75gP1Zamf"

模块返回:

1
OK

4.5 启用 ADB 与 USB Audio 完整组合

保持现有 2C7C:0125 身份及其他功能位不变,只把倒数第二位 ADB 从 0 改为 1

1
AT+QCFG="USBCFG",0x2C7C,0x0125,1,1,1,1,1,1,1

写入返回:

1
OK

随后立即回读:

1
AT+QCFG="USBCFG"

精确得到:

1
2
+QCFG: "usbcfg",0x2C7C,0x125,1,1,1,1,1,1,1
OK

只有在回读完全一致后才继续重启。

4.6 重启模块

1
AT+CFUN=1,1

重启期间 4G、短信、USB AT 和音频设备会短暂断开并重新枚举。

4.7 验证真实 USB 接口

重启完成后,DJOneHub 健康接口报告如下 USB 布局:

1
2
3
interface 6: class=255, subclass=66, protocol=1, endpoints=2
interface 7: USB Audio Control
interface 8~9: USB Audio Streaming

macOS 注册表最终确认:

1
2
3
4
ADB Interface@6
"bInterfaceClass" = 255
"bInterfaceSubClass" = 66
"bNumEndpoints" = 2

这证明 ADB 与 UAC 已同时真实枚举,不再只是配置字符串显示成功。

4.8 处理 ADB 接口占用

首次测试语音路由时返回:

1
claim USB ADB interface 6: LIBUSB_ERROR_ACCESS

查到独占进程:

1
UsbExclusiveOwner = "pid 89856, adb"

对应程序:

1
~/.local/android/platform-tools/adb

停止 adb server:

1
adb kill-server

确认进程停止且 interface 6 不再有独占所有者后,继续测试。

4.9 启动实际语音路由验证

调用 DJOneHub 本地接口:

1
2
curl --max-time 70 -X POST \
  'http://127.0.0.1:7575/api/voice/start'

日志确认完成了全部关键步骤:

1
2
3
4
5
6
7
module voice: pushed qdc507_aprv3.ko
module voice: pushed qdc507_voice.ko
module voice: pushed mavo-pcm-bridge.armv7
module voice: insmod qdc507_aprv3.ko
module voice: insmod qdc507_voice.ko
module voice: runtime prepared (qdc507-3.18.44-voice-20260712.5)
module voice: route is ready (UAC voice enabled)

API 返回:

1
2
3
4
5
6
7
8
{
  "started": true,
  "status": {
    "ready": true,
    "last_error": "",
    "runtime_installed": true
  }
}

由于当时没有真实电话,验证成功后按正常生命周期停止测试路由:

1
2
curl --max-time 40 -X POST \
  'http://127.0.0.1:7575/api/voice/stop'

日志确认:

1
module voice: route stopped

下一次电话进入 active 状态时,DJOneHub 会自动重新启动该路由。

5. 最终结果

最终状态如下:

  • ADB 解锁成功;
  • USBCFG 持久化为 2C7C:0125,1,1,1,1,1,1,1
  • interface 6 正确枚举成 ADB;
  • interface 7~9 正确枚举成 USB Audio;
  • 模块语音运行时推送成功;
  • 两个语音内核模块加载成功;
  • D4/UAC 语音路由成功进入 ready;
  • 测试结束后路由已正常停止;
  • Android adb server 已停止,不再抢占模块接口。

6. 换电脑后的处理

QADBKEY 解锁和 USBCFG 保存在模块侧,因此正常换到另一台 Mac 后,不需要再次解锁模块。

新电脑仍需完成:

  1. 安装 DJOneHub;
  2. 在“设置 → 语音运行时”重新下载并校验语音运行时,因为该缓存保存在电脑本地;
  3. 授予 DJOneHub 麦克风权限;
  4. 等待 macOS 完成 USB Audio 和 CDC-ECM 网络接口识别;
  5. 确保没有 Android adb server、Android File Transfer、OpenMTP 等程序独占模块 ADB 接口。

若新电脑出现:

1
LIBUSB_ERROR_ACCESS

优先执行:

1
adb kill-server

只有以下情况才可能需要重新解锁或重新配置模块:

  • 模块恢复出厂配置;
  • 刷写或升级固件后安全状态被重置;
  • 其他工具改写了 USBCFG
  • 回读发现倒数第二位重新变成 0
  • interface 6 不再是 subclass 66 的 ADB 接口。

7. 总结与反思

7.1 后续需要加强的知识点

  1. AT 控制面与媒体面的区别
    电话能拨通只证明 AT/IMS/VoLTE 控制链路有效,不代表 PCM、UAC 和主机音频链路有效。

  2. USB 组合配置与真实描述符验证
    AT+QCFG="USBCFG" 的回读只能表示配置值,最终仍应使用 IOKit、libusb 或系统 USB 注册表验证接口 class、subclass、端点和实际占用者。

  3. Quectel ADB 安全机制
    某些固件要求先执行 QADBKEY 挑战应答;没有解锁时,直接打开 ADB 功能位可能无法在重启后真正生效。

  4. macOS USB 接口独占
    Android adb server 与 DJOneHub 的直连 ADB 实现都会独占同一个 USB interface。检测到 LIBUSB_ERROR_ACCESS 时,应先检查 UsbExclusiveOwner,而不是反复重装或重启模块。

  5. CLCC 字段语义
    CLCC mode=0 是语音,mode=1 是数据。只根据“存在 CLCC 行”判断正在通话会造成误判。

  6. 持久配置变更的安全流程
    必须坚持“备份原值 → 精确写入 → 立即回读 → 再重启 → 验证真实枚举 → 功能测试”的顺序,避免模块因 USB 组合错误而失去控制接口。

7.2 对项目的改进建议

建议 DJOneHub 后续增加:

  1. 初始化时主动查询 AT+QADBKEY?,发现挑战码时进入明确的 ADB 解锁步骤;
  2. 不再仅凭 …,0,1 判断完整通话音频已就绪;
  3. 在 UI 显示“UAC 已存在,但 ADB 未枚举/未解锁”的独立状态;
  4. 初始化完成后检查 interface 6 的 class/subclass 和 Bulk 端点;
  5. 遇到 LIBUSB_ERROR_ACCESS 时显示接口占用进程,并提示执行 adb kill-server
  6. 在拨号前做快速 ADB 可用性检查,避免电话接通后才发现没有声音;
  7. 对重启后配置回退、应用退出和验证超时提供可靠的恢复流程。

8. 重要提醒

  • 不要随意修改 USBCFG 中其他功能位或照抄其他型号模块的 VID/PID;
  • 不要在真实语音通话期间修改 USB 配置或重启模块;
  • 不要让普通 Android adb server 与 DJOneHub 同时占用模块接口;
  • 如需恢复,优先使用 DJOneHub 已保存的原始配置备份;
  • 文档中的 QADBKEY 密码与本次模块挑战码相关,不应直接用于其他模块。