Rodger-Wang
|
ace92c9ea1
|
client: 通话翻译耳机杂音——四个独立原因逐个修掉(Android 定性,iOS 同步)
六轮真机定位出四个叠加的原因,每修掉一个概率降一档:
1. 发包定时器系统性漂移:scheduleWithFixedDelay 从任务**结束**起算,WSOLA+G.722+
写 socket 的 1~3ms 全叠进周期,"20ms"实测 21~23ms、只有 0.9 倍速。改成
scheduleAtFixedRate 固定节拍且永不重排。
2. 起播缓冲在句中重新生效(杂音落在句中的真凶):队列空了就要求再攒 120ms,而
变速追赶时队列中途归零很常见,等于句中硬插 120ms 静音。加 playing 标志让
起播缓冲只在句首用一次;真正的断粮单独计数并打「句中空洞」日志。
3. 恢复发包时从空缓冲起播(杂音落在句首):两路都空就停发、耳机缓冲被榨干。
现在先塞 4 包编码静音垫底再发真音频,两路空后再续 500ms 静音才停。
4. 24k→16k 重采样是逐块独立的线性插值、无抗混叠、每块相位从零重算。换成
64 阶低通 FIR + 跨块连续(Resampler24kTo16k.kt/.swift,Python 复刻校验
1k/3k/6k 正弦)。
顺带:耳机 0xD6 帧 data[6] 的语义是「补一包」不是「改速率」,按此重写
onHeadsetPace(01 补 1 包、00 补 2 包、03 扣 1 包);空闲那一路的静音帧改用该路
自己的编码器编零(G.722 是自适应差分,固定码流会让解码器状态跳);裁静音加
滞回(300 进 / 600 出)+ 3ms 淡入;速率每帧最多变 0.03;150ms 内有新数据不补零
收尾;HARD_CAP_SEC 补回 30s 纯兜底。DEEPVOICE_PARITY 对照开关保持 false。
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
3 weeks ago |
Rodger-Wang
|
3e05be48d6
|
client: 通话翻译下行追赶参数收紧(两端同改):1s 起变速、2.5s 达 1.3x、0.5s 起裁静音
四轮压测 + 同一到达轨迹回放对比:>3s 积压累计时长减半、中位降 25%,零丢帧零错误。
峰值由云端单次响应长度决定,参数再收也不降,下一步只能从切句(turn_detection)下手。
CLAUDE.md 记录取舍与数据。
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
|
3 weeks ago |
Rodger-Wang
|
d85f0fbaed
|
client(ios),docs: 译文积压追赶同步到 iOS 下行发送器;CLAUDE.md 记录 9-22 三条结论
- CallTranslationDownlink.swift 改成与 Android 同一套:积压 >1s 裁静音、>2s 起 WSOLA 变速不变调
(4s 达 1.3x),不丢内容;stats() 多了 backlog/rate/trimmed 三组键,原有键保留。
只动这一个文件,公开接口(start/stop/stats/resetPeaks/setIntervalMs/pushPcm)不变;
已用 iphonesimulator SDK typecheck,未上真机。
- CLAUDE.md:Android SPP 控制命令会被协议栈合帧、AST 就绪状态同页第二场必聋、译文积压追赶。
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
|
3 weeks ago |
Rodger-Wang
|
7ce56f693d
|
client: 通话翻译音频链路修复(iOS GATT 写入、后台保活、状态竞态)
iOS 上恒玄走的是 GATT over BR/EDR 而非 BLE,之前按 BLE 模型处理导致一连串问题。
本次改动都有真机数据支撑(2026-09-21 压测)。
GATT 写入(bluetooth_manager)
- 写类型改为优先 .withoutResponse。ATT 是请求-响应协议,带应答写每包等 ACK,
追不上下行 50 包/秒,表现为音频越听越滞后。
- canSendWriteWithoutResponse 只作观测、不作门槛。它是 CoreBluetooth 为 BLE
设计的提示,在 BR/EDR GATT 上不反映真实能力(实测 100% 为 false 却零丢弃);
拿它当闸门会死锁:不写就不回调,不回调就永不翻真,实测 548 包只出去 11 个。
- 控制命令无条件直发、不排队。排到积压音频后面会让 AA 09/AA 06/queryCallState
全部发不出去,曾被误判成「固件换了命令号」。
- 不再对标准电池服务 0x180F 做任何 ATT 操作:它在 Echo-one 上是空壳(值恒为 0),
而挂在它上面的读/订阅会把整条 ATT 通道堵死。
后台存活(translation)
- 通话翻译期间持有 BackgroundKeepalive。两路音频全走耳机 GATT,手机侧没有
AVAudioSession 的实际 I/O,UIBackgroundModes=audio 不成立,切后台几十秒后
上行被掐断:WebSocket 还连着,但服务端收不到语音,靠默认 VAD 每 30 秒强切一句,
吐出空译文或幻觉 —— 表现为「切后台后只剩译文、原文全没」。
- BackgroundKeepalive 不再无条件把音频类别改成 .playback,避免踩掉通话翻译
正在跑的 .playAndRecord 录音链路。
状态竞态(azure_speech / device_hub)
- markReady 的就绪状态记在插件上、建桥时继承。建桥与 AST 异步初始化是两条独立
路径,初始化先完成时那句 optional chaining 打在 nil 上静默丢失,之后 ready
恒为 false、上行帧全积在环形缓冲里,表现为「打开通话翻译没反应,再开一次才好」。
- DeviceHub.inCall 改以会话快照为准。事件会随 worker 在断连时被 dispose 而丢失,
重连后 ever 又不回调当前值,且后续重复赋同值不触发,永远不会自愈。
可观测性
- 下行发送器与 GATT 写入的日志从 NSLog 改为 os_log(release/profile 包里
idevicesyslog 只收得到 os_log),并经 DeviceSession.diagnostics() 暴露给 Dart,
写入文件日志后可用 devicectl 直接拉取,不依赖 usbmuxd。
- 新增 inCall 变化、闸门拦截、通话状态翻转、前后台切换四处观测点。
其它
- DeviceSession 新增 diagnostics()(默认空表);SmartcarSession 因是 implements
而非 extends,同步补了实现。
- CLAUDE.md 记录本次全部实测结论,含 BB 8E 为通话状态权威、BB 02/03 是噪声
(已知问题,本次未改判定)。
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
3 weeks ago |
Rodger-Wang
|
2d1ee0b783
|
apps/client 首次入库(Flutter 移动端)
从 /Users/yunyanzhineng/work/eaimar_client-main 搬入,未带原 .git,历史留在原仓库。
3215 个文件 / 约 118MB,其中约 40MB 是构建必需的预编译 SDK(azure_speech 的
MicrosoftCognitiveServicesSpeech.xcframework、device_jieli 的 JL_BLEKit 等)。
build/ (5.2G)、ios/Pods/ (311M)、android/.gradle/ (120M) 已由 .gitignore 挡掉。
本次入库的代码包含两项刚做完的改动:
1) 游客登录入口接上服务端开关。此前 _checkGuestLoginConfig() 恒定写死 true,
入口根本不读配置。现改为取服务端算好的结论,并把入口所在的 Builder 改成 Obx
——配置是异步拿到的,Builder 只渲染 onInit 时的初值 false,入口再也不会出现。
拿不到配置时按隐藏处理:默认显示会在送审期把不该露的入口露给审核员。
⚠️ 已发布的老客户端关不掉(写死 true),只对本版及以后生效。
2) iOS 上架合规修复:
- 移除 ATT/IDFA:删掉 Info.plist 的 NSUserTrackingUsageDescription、
SKAdNetworkItems、NSAdvertisingAttributionReportEndpoint,删掉 splash 里的
授权请求与 app_tracking_transparency 依赖。本 App 从不读取 IDFA,也没有任何
广告/归因 SDK,弹框纯粹劝退用户,还要在 App Store Connect 申报追踪数据类型。
- ios/Podfile 里那段 config.build_settings.delete('NSUserTrackingUsageDescription')
是无效代码(该 key 在 Info.plist 里,不是 build setting),换成注释说明。
- 通讯录符号(ITMS-90683 被拒原因)随之一并解决:permission_handler 走 SPM 后
按 Info.plist 有无对应 key 决定编不编,判定结果被 SwiftPM 按内容哈希缓存,
改完 plist 必须清 DerivedData 与 manifests 缓存才会重新求值。
另附 GOOGLE_PLAY_RELEASE_CHECKLIST.md:Google Play 上架阻塞项的实测梳理
(targetSdk 36 与 Billing 8 的 2026-08-31 期限、16KB 页对齐不合规的两个 .so、
支付回退逻辑的下架风险)。
⚠️ android/app/sign/ 下的 eaimar.keystore 与 Eaimar.properties(含明文签名密码)
随本次提交入库,与本仓库"私有仓库下接受真实凭据"的既有取舍一致。
仓库若要公开,这两个必须先摘出去。
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
1 month ago |