openharmony 设备录像「无声音」全流程排查与修复
开篇:先别慌,从日志里找线索
「录像没有声音」这类问题,第一反应往往是恐慌式排查:是不是麦克风坏了?是不是权限没给?是不是某个配置项写错了?
但经验告诉我们:先别慌,把日志抓下来,让日志告诉你问题在哪。
这次的整个过程,其实就是顺着一行日志一路追下去:
HiRecorder: (Prepare(), 405): A333 camera recorder disables audio path
for video-only raw NV12 recording experiment.
就这一行,把根因的性质、位置、甚至具体代码行号都标出来了。后面所有的定位、反汇编、改二进制,都是围绕它展开的。
几个可以先记下来的经验:
- 日志是最诚实的。 应用层看着一切正常,不代表系统层没问题 —— 真正的开关往往藏在系统引擎里,而它出错时通常会留下一句话。
- 先缩小范围,再深入。 从 tag(
C02b2c/HiRecorder)和进程(media_service)就能锁定是哪个库、哪个进程,不必从最底层一层层扒。 - 别急着改代码。 先确认「问题出在哪一步、为什么出」,再动手 —— 这次如果凭感觉去改应用层,改再多也无济于事。
- 没有源码不等于没法修。 本机没有系统源码,但通过 ELF 解析 + 反汇编,照样定位到了具体分支,只改了 4 个字节就修好。
下面就是完整的排查与修复过程。
起因
某自研设备上使用相机录像时,录出的视频没有声音。
排查的第一站是应用层 —— CameraService 中 AVRecorder 的配置是完整的(AAC 编码、48kHz、双声道),麦克风权限声明和运行时授权也正常。应用这边没问题,转向系统媒体层。
发现关键线索
给真机抓取了 hilog,媒体服务进程(media_service)的日志里出现了这样一行:
HiRecorder: (Prepare(), 405): A333 camera recorder disables audio path for video-only raw NV12 recording experiment.
带 A333 前缀、又写着「video-only raw NV12 recording」—— 这一行,直接把根因的性质定死:这是系统媒体引擎里的厂商实验补丁,在录像准备阶段主动把音频通道关掉了。
而拉取录制文件后确认 mp4 里没有 soun 音轨,佐证了「只录了视频」。音频链路不是坏的,是被显式杀掉的。
第一步:确定问题在哪个 .so 里
运行日志里那行警告的 tag 是 C02b2c/HiRecorder,进程 pid 是 660,对应 media_service。所以在设备上:
hdc shell "cat /proc/660/maps"
拿到 media_service 加载的所有 .so 列表,筛出媒体相关的几个候选(libmedia_engine_histreamer.z.so、libmedia_service.z.so 等),逐个拉到电脑上:
hdc file recv /system/lib64/media/libmedia_engine_histreamer.z.so ./xxx.so
然后本机 grep -c "A333" —— 命中的是 libmedia_engine_histreamer.z.so(设备端 grep 对二进制会漏检,必须拉回本机查)。
第二步:解析 ELF,拿到“地图”
用 pyelftools 解析这个文件,得到三样东西:
- 符号表(
.symtab没被剥离,很幸运):找到_ZN4OHOS5Media14HiRecorderImpl7PrepareEv,即HiRecorderImpl::Prepare(),虚拟地址0x66a10,大小 1408 字节 —— 这就是日志里打印(Prepare(), 405)的函数; .rodata里的字符串地址:"A333 camera recorder disables audio path..."位于0x3ef0e;- 段表(program headers):后面把虚拟地址换算成文件偏移要用。
第三步:用字符串地址反查引用它的代码
ARM64 每条指令固定 4 字节,不能把 64 位地址直接塞进一条指令,所以引用字符串都是两条指令配合:
adrp x4, #0x3e000 ; 取目标所在 4KB 页的起始地址
add x4, x4, #0xf0e ; 加上页内偏移 → x4 = 0x3ef0e(字符串地址)
我写了个扫描脚本(capstone 反汇编 + pyelftools 读字符串):线性扫 .text,遇到 adrp 就把「寄存器 → 页地址」记进字典,后面紧跟的 add 如果用了同一个寄存器,就算出最终地址去 .rodata 读字符串,含 "A333" 就报出来。结果在 Prepare() 内部 0x66b10 处找到了对 "disables audio path" 的引用 —— 警告日志的打印代码就在这个函数里。
顺便全库扫了一遍:A333 字符串共 7 处引用,其余 6 处在 SetVideoSource / PrepareVideoEncoder 等函数,全是 NV12 视频通路 —— 说明只有 Prepare() 这一处碰音频,动它不会影响视频。
第四步:反汇编 Prepare(),重建控制流
对 0x66a10 开始的 1408 字节做反汇编,关键片段(x19 保存的是 this 指针):
0x66af4: ldrb w8, [x19, #0x185] ; 读成员变量:模式字节(A333 NV12 标志)
0x66af8: cmp w8, #2
0x66afc: b.hs #0x66eb8 ; ≥2 → 非法值,报错返回
0x66b00: cbz w8, #0x66d30 ; ★ ==0 → 直接跳到汇合点(正常路径)
; ≠0 → 掉进下面的禁用音频块
0x66b04~0x66b30: 打印警告日志 ; w6=0x195=405,正好是日志行号(__LINE__)
0x66b34~0x66bd8: 释放 audioCaptureFilter_ (shared_ptr 减引用→析构)
0x66be0~0x66ca4: 释放第二个音频 filter
0x66d00~0x66d2c: 清理音频相关状态
0x66d30: ←———— 两条路径在这里汇合 ————
0x66d48: bl PrepareAudioCapture
0x66d5c: bl PrepareAudioDataSource
0x66d70: bl PrepareVideoEncoder ← A333 NV12 就是在这里面设置的
0x66dc0: bl PrepareMetaData
0x66e10: bl PrepareVideoCapture
0x66e70: bl Pipeline::Prepare
(这些 bl 的目标一开始是匿名地址,我解析 .rela.plt 把 PLT 桩 → GOT 表 → 符号名逐一对应,才确认是上面这些函数。)
结论清晰了:A333 分支比正常路径多干的唯一事情,就是把音频 filter 拆掉;视频准备(PrepareVideoEncoder 里的 NV12 设置)在汇合点之后,两条路径共用。 所以修法就是:让 0x66b00 的条件跳转变成无条件跳转,禁用块变成永远走不到的死代码,其余一切照旧(模式字节本身没动,视频 NV12 通路不受影响)。
第五步:算指令编码(4 字节的由来)
原指令 88 11 00 34
小端序,实际指令字是 0x34001188。CBZ 的编码格式是:
sf 0010100 imm19(19位) Rt(5位)
Rt = 0b01000 = 8→ 被测寄存器是w8imm19 = (0x34001188 >> 5) & 0x7FFFF = 0x8C = 140- 跳转目标 = PC + imm19×4 =
0x66b00 + 140×4 = 0x66b00 + 0x230 = 0x66d30✓(和反汇编显示一致)
新指令
无条件跳转 B 的编码是 000101 imm26(26位):
- 偏移 =
0x66d30 − 0x66b00 = 0x230;imm26 = 0x230 >> 2 = 0x8C(同样以 4 字节为单位) - 指令字 =
0x14000000 | 0x8C=0x1400008C - 小端序写回文件就是
8c 00 00 14
注意新旧指令的 imm 巧合地都是 0x8C(因为目标一样,只是编码位宽不同)。
第六步:虚拟地址 → 文件偏移,写盘
虚拟地址不能直接当文件偏移用。查 PT_LOAD 段表,0x66b00 落在某个段的 vaddr 基址 + 页偏移 处,换算出文件偏移 0x65B00。写个小脚本:
- 断言原字节确实等于
88 11 00 34(防呆 —— 库里版本不对就中止); - 替换成
8c 00 00 14,另存为新文件; - 重新反汇编新文件验证:
0x66b00: b #0x66d30✓
第七步:部署
hdc shell "mount -o remount,rw /" # 系统分区改读写
hdc file send 新.so /data/local/tmp/ # 先推到临时目录
hdc shell "cat /data/local/tmp/新.so > /system/lib64/media/libmedia_engine_histreamer.z.so"
# cat 覆写保留原 inode 的权限/SELinux 标签
hdc shell "kill <media_service的pid>" # safwk 自动拉起新进程加载新库
最后 md5 核对设备上的文件 = 本地补丁版,再看日志验证(不再出现禁用警告、AudioCaptureFilter 走完全链路)。
总结
「录制视频没有声音」从最初的恐慌排查,最终落在了一个系统级代码分支的选择问题上。最终解法:把走禁用分支的条件去掉,让系统走自己的正常音频准备流程。
技术要点写在代码注释里了:一句 ARM64 分支指令,让整个媒体引擎重新学会「听」。
复现与避坑提示
过程中踩的几个坑,供你复现时参考:
- 设备端 grep 二进制会漏检 —— 必须把
.so拉回本地再做检索; - capstone 遇到无法解码的字节会静默停止 —— 要按 4 字节步进重试;
adrp的目的寄存器在 operands[0]、立即数在 operands[1] —— 取反了寄存器名会是None;- 符号表没被剥离 —— 若缺失,ELF 解析就困难了,注意检查是否有人做了
strip; - PLT 目标需交叉解析 —— 匿名
bl地址通过.rela.plt+ GOT 对齐成符号名才看得清; - 虚拟地址 → 文件偏移 —— 库版本变了后 vaddr 基址可能变,必须从段表重新算;
- 部署覆写 —— 用
cat >而非cp,能保留原文件的权限与 SELinux 标签,否则remount rw后可能被拒绝。
所有脚本都留在本地工作目录中(scan_refs.py 找字符串引用、plt.py 解析 PLT、disasm.py 反汇编、patch.py 打补丁),以后遇到类似「系统库改一个分支」的需求可以直接改着用。
更多推荐

所有评论(0)