本文介绍了一些与 Android 音频调试有关的提示和技巧。
Tee Sink
“tee sink”是一种 AudioFlinger 调试功能,仅在定制 build 中提供,用于获取最近音频的短片段以供日后分析。 这方便我们比较实际播放或录制的内容与预期内容。
出于隐私考虑,tee sink 在编译时和运行时均默认处于停用状态。如需使用 tee sink,您需要通过重新编译以及设置属性来启用它。完成调试后,请务必停用此功能;tee sink 在正式 build 中不能处于启用状态。
本部分中的说明适用于 Android 7.x 及更高版本。对于 Android 5.x 和 6.x,请将 /data/misc/audioserver
替换为 /data/misc/media
。此外,您还必须使用 userdebug 或 eng build。如果您使用 userdebug 版本,请使用以下命令停用 verity:
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 功能不能保持启用状态。 如需恢复您的 build 和设备,请执行以下操作:
- 还原对
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.h>
中声明的 __android_log_print
。
在 Android 框架的原生部分,我们倾向于使用名为 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
进程之间的关系:
![引入 media.log 之前的架构](https://source.android.google.cn/static/docs/core/audio/images/medialog_before.png?authuser=4&hl=cs)
图 1. 引入 media.log 之前的架构
下面几点值得注意:
init
可创建mediaserver
分支并执行该进程。init
可检测到mediaserver
故障,并根据需要重新创建分支。- 未显示
ALOGx
日志记录。
以下示意图展示了将 media.log
添加到架构之后组件之间的新关系:
![添加 media.log 之后的架构](https://source.android.google.cn/static/docs/core/audio/images/medialog_after.png?authuser=4&hl=cs)
图 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