本文介绍了一些调试 Android 音频的技巧和诀窍。
Tee sink
“tee sink”是一项 AudioFlinger 调试功能,仅在自定义版本中提供,用于保留最近音频的短片段以供后续分析。这样可以将实际播放或录制的内容与预期内容进行比较。
出于隐私考虑,默认情况下,tee sink 在编译时和运行时均处于停用状态。要使用 tee sink,您需要通过重新编译并设置属性来启用它。完成调试后,请务必停用此功能;不应在生产版本中启用 tee sink。
本部分中的说明适用于 Android 7.x 及更高版本。对于 Android 5.x 和 6.x,请将 /data/misc/audioserver
替换为 /data/misc/media
。此外,您必须使用 userdebug 或 eng 版本。如果您使用 userdebug 版本,请使用以下命令停用验证:
adb root && adb disable-verity && adb reboot
编译时设置
cd frameworks/av/services/audioflinger
- 编辑
Configuration.h
。 - 取消注释
#define TEE_SINK
。 - 重新构建
libaudioflinger.so
。 adb root
adb remount
- 将新的
libaudioflinger.so
推送或同步到设备的/system/lib
。
运行时设置
adb shell getprop | grep ro.debuggable
确认输出为:[ro.debuggable]: [1]
adb shell
ls -ld /data/misc/audioserver
确认输出为
drwx------ media media ... media
如果该目录不存在,请按如下方式创建:
mkdir /data/misc/audioserver
chown media:media /data/misc/audioserver
echo af.tee=# > /data/local.prop
其中af.tee
值是下面描述的数字。chmod 644 /data/local.prop
reboot
af.tee 属性的值
af.tee
的值是介于 0 到 7 之间的数字,表示多个位的总和,每个位对应一个功能。有关每个位的说明,请参阅 AudioFlinger.cpp
中 AudioFlinger::AudioFlinger()
的代码,但简而言之
- 1 = 输入
- 2 = FastMixer 输出
- 4 = 每个轨道的 AudioRecord 和 AudioTrack
目前还没有用于深层缓冲区或普通混音器的位,但您可以使用“4”获得类似的结果。
测试和获取数据
- 运行您的音频测试。
adb shell dumpsys media.audio_flinger
- 在
dumpsys
输出中查找如下行
tee copied to /data/misc/audioserver/20131010101147_2.wav
这是一个 PCM .wav 文件。 - 然后
adb pull
任何感兴趣的/data/misc/audioserver/*.wav
文件;请注意,特定于轨道的转储文件名不会显示在dumpsys
输出中,但仍会在轨道关闭时保存到/data/misc/audioserver
。 - 在与他人共享之前,请检查转储文件是否存在隐私问题。
建议
尝试以下建议以获得更有用的结果
- 停用触摸声音和按键音,以减少测试输出中的中断。
- 最大化所有音量。
- 如果您对不感兴趣的应用发出声音或从麦克风录音,请将其停用。
- 仅当轨道关闭时才会保存特定于轨道的转储;您可能需要强制关闭应用才能转储其特定于轨道的数据
- 在测试后立即执行
dumpsys
;可用记录空间有限。 - 为了确保您不会丢失转储文件,请定期将其上传到您的主机。仅保留有限数量的转储文件;达到该限制后,较旧的转储文件将被删除。
恢复
如上所述,不应保持启用 tee sink 功能。请按如下方式恢复您的构建和设备
- 恢复对
Configuration.h
的源代码更改。 - 重新构建
libaudioflinger.so
。 - 将恢复的
libaudioflinger.so
推送或同步到设备的/system/lib
。 adb shell
rm /data/local.prop
rm /data/misc/audioserver/*.wav
reboot
media.log
ALOGx 宏
Android SDK 中的标准 Java 语言日志记录 API 是 android.util.Log。
Android NDK 中对应的 C 语言 API 是 __android_log_print
,声明在 <android/log.h>
中。
在 Android 框架的 native 部分,我们更喜欢名为 ALOGE
、ALOGW
、ALOGI
、ALOGV
等的宏。它们在 <utils/Log.h>
中声明,为了本文的目的,我们将统称为 ALOGx
。
所有这些 API 都易于使用且易于理解,因此它们在整个 Android 平台中都很普及。特别是 mediaserver
进程(包括 AudioFlinger 声音服务器)广泛使用 ALOGx
。
然而,ALOGx
及其类似工具也存在一些限制
- 它们容易受到“日志垃圾信息”的影响:日志缓冲区是一种共享资源,因此很容易因不相关的日志条目而溢出,从而导致信息丢失。默认情况下,
ALOGV
变体在编译时被禁用。但当然,即使启用它也可能导致日志垃圾信息。 - 底层内核系统调用可能会阻塞,可能导致优先级反转,从而导致测量干扰和不准确。这对于时间关键型线程(如
FastMixer
和FastCapture
)尤其值得关注。 - 如果禁用特定日志以减少日志垃圾信息,那么该日志本应捕获的任何信息都将丢失。在 *之后* 明确日志可能很有趣时,无法追溯启用特定日志。
NBLOG、media.log 和 MediaLogService
NBLOG
API 以及相关的 media.log
进程和 MediaLogService
服务共同构成了一个较新的媒体日志记录系统,专门用于解决上述问题。我们将宽松地使用术语 “media.log” 来指代这三者,但严格来说,NBLOG
是 C++ 日志记录 API,media.log
是 Linux 进程名称,而 MediaLogService
是用于检查日志的 Android binder 服务。
media.log
“时间线” 是一系列日志条目,其相对顺序被保留。按照惯例,每个线程都应使用自己的时间线。
优势
media.log
系统的优势在于它
- 除非需要,否则不会向主日志发送垃圾信息。
- 即使
mediaserver
崩溃或挂起,也可以对其进行检查。 - 每个时间线都是非阻塞的。
- 对性能的干扰较小。(当然,没有任何形式的日志记录是完全非侵入式的。)
架构
下图显示了在引入 media.log
之前,mediaserver
进程和 init
进程的关系

图 1. media.log 之前的架构
重点
init
fork 和 execmediaserver
。init
检测到mediaserver
的终止,并在必要时重新 fork。- 未显示
ALOGx
日志记录。
下图显示了将 media.log
添加到架构后,组件的新关系

图 2. media.log 之后的架构
重要更改
- 客户端使用
NBLOG
API 构建日志条目,并将其附加到共享内存中的循环缓冲区。 -
MediaLogService
可以随时转储循环缓冲区的内容。 - 循环缓冲区的设计方式使得共享内存的任何损坏都不会导致
MediaLogService
崩溃,并且它仍然能够转储缓冲区中未受损坏影响的部分。 - 对于写入新条目和读取现有条目,循环缓冲区都是非阻塞且无锁的。
- 写入或读取循环缓冲区不需要内核系统调用(可选时间戳除外)。
使用场景
在 Android 4.4 中,AudioFlinger 中只有少数日志点使用 media.log
系统。尽管新的 API 不如 ALOGx
易于使用,但它们也并非极其困难。我们鼓励您学习新的日志记录系统,以应对那些不可或缺的场合。特别是,建议将其用于必须频繁、定期且无阻塞运行的 AudioFlinger 线程,例如 FastMixer
和 FastCapture
线程。
如何使用
添加日志
首先,您需要在代码中添加日志。
在 FastMixer
和 FastCapture
线程中,使用如下代码
logWriter->log("string"); logWriter->logf("format", parameters); logWriter->logTimestamp();
由于此 NBLog
时间线仅由 FastMixer
和 FastCapture
线程使用,因此无需互斥。
在其他 AudioFlinger 线程中,使用 mNBLogWriter
mNBLogWriter->log("string"); mNBLogWriter->logf("format", parameters); mNBLogWriter->logTimestamp();
对于 FastMixer
和 FastCapture
以外的线程,线程的 NBLog
时间线可以由线程自身和 binder 操作使用。NBLog::Writer
不提供每个时间线的任何隐式互斥,因此请确保所有日志都发生在持有线程互斥锁 mLock
的上下文中。
添加日志后,重新构建 AudioFlinger。
注意: 为了确保线程安全,每个线程都需要单独的 NBLog::Writer
时间线,因为时间线在设计上省略了互斥锁。如果希望多个线程使用同一时间线,则可以使用现有互斥锁进行保护(如上文针对 mLock
所述)。或者,您可以使用 NBLog::LockedWriter
包装器而不是 NBLog::Writer
。但是,这会抵消此 API 的一个主要优势:其非阻塞行为。
完整的 NBLog
API 位于 frameworks/av/include/media/nbaio/NBLog.h
。
启用 media.log
默认情况下,media.log
处于禁用状态。仅当属性 ro.test_harness
为 1
时才处于活动状态。您可以通过以下方式启用它
adb root
adb shell
echo ro.test_harness=1 > /data/local.prop
chmod 644 /data/local.prop
reboot
重启期间连接会丢失,因此
adb shell
ps media
现在将显示两个进程- media.log
- mediaserver
记下 mediaserver
的进程 ID 以供稍后使用。
显示时间线
您可以随时手动请求日志转储。此命令显示来自所有活动和最近时间线的日志,然后清除它们
dumpsys media.log
请注意,根据设计,时间线是独立的,并且没有合并时间线的功能。
在 mediaserver 终止后恢复日志
现在尝试终止 mediaserver
进程:kill -9 #
,其中 # 是您之前记下的进程 ID。您应该在主 logcat
中看到来自 media.log
的转储,显示崩溃前的所有日志。
dumpsys media.log