音频调试

本文介绍了一些与 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

编译时设置

  1. cd frameworks/av/services/audioflinger
  2. 修改 Configuration.h
  3. #define TEE_SINK 取消注释。
  4. 重新构建 libaudioflinger.so
  5. adb root
  6. adb remount
  7. 将新的 libaudioflinger.so 推送或同步到设备的 /system/lib

运行时设置

  1. adb shell getprop | grep ro.debuggable
    确认输出是:[ro.debuggable]: [1]
  2. adb shell
  3. ls -ld /data/misc/audioserver

    确认输出是:

    drwx------ media media ... media
    

    如果目录不存在,请按如下方式创建:

    mkdir /data/misc/audioserver
    chown media:media /data/misc/audioserver
    
  4. echo af.tee=# > /data/local.prop
    其中,af.tee 值是一个数字,在下文中有所说明。
  5. chmod 644 /data/local.prop
  6. reboot

af.tee 属性的值

af.tee 的值是一个 0 到 7 之间的数字,表示几个位的总和(每个功能一个位)。请查看位于 AudioFlinger.cpp 中的 AudioFlinger::AudioFlinger() 的代码,了解各个位的说明,简单来说就是:

  • 1 = 输入
  • 2 = FastMixer 输出
  • 4 = 各音轨的 AudioRecord 和 AudioTrack

目前还没有针对深度缓冲区和常规混合器的位,不过您可以使用“4”获取类似结果。

测试和获取数据

  1. 运行您的音频测试。
  2. adb shell dumpsys media.audio_flinger
  3. dumpsys 输出中查找如下行:
    tee copied to /data/misc/audioserver/20131010101147_2.wav
    这是一个 PCM .wav 文件。
  4. 然后,使用 adb pull 命令提取任何相关的 /data/misc/audioserver/*.wav 文件;请注意,音轨特定的转储文件名不会显示在 dumpsys 输出中,但仍会在音轨关闭时保存到 /data/misc/audioserver
  5. 在与其他人分享之前,请先查看转储文件是否涉及隐私权问题。

建议

要获取更实用的结果,请尝试以下方法:

  • 停用触摸提示音和按键音,以减少测试输出过程中的中断。
  • 将所有音量调到最大。
  • 停用通过麦克风发出声音或录音的应用(如果这些应用与测试无关)。
  • 音轨特定的转储文件仅在音轨关闭时保存;您可能需要强制关闭某个应用才能转储其音轨特定的数据。
  • 在测试后立即执行 dumpsys;可用的录制空间是有限的。
  • 如需确保转储文件不会丢失,请定期将其上传到您的主机。 您只能保留有限数量的转储文件;达到此数量上限后,系统会移除较旧的转储文件。

恢复

如上文所述,tee sink 功能不能保持启用状态。 如需恢复您的 build 和设备,请执行以下操作:

  1. 还原对 Configuration.h 所做的源代码更改。
  2. 重新构建 libaudioflinger.so
  3. 将恢复后的 libaudioflinger.so 推送或同步到设备的 /system/lib
  4. adb shell
  5. rm /data/local.prop
  6. rm /data/misc/audioserver/*.wav
  7. reboot

media.log

ALOGx 宏

Android SDK 中的标准 Java 语言日志记录 API 是 android.util.Log

Android NDK 中的对应 C 语言 API 是 <android/log.h> 中声明的 __android_log_print

在 Android 框架的原生部分,我们倾向于使用名为 ALOGEALOGWALOGIALOGV 等的宏。这些宏会在 <utils/Log.h> 中声明,在本文中,我们将它们统称为 ALOGx

由于所有这些 API 均易于使用和理解,因此在整个 Android 平台中得到了广泛应用。尤其是 mediaserver 进程(其中包括 AudioFlinger 声音服务器),它使用了大量 ALOGx

不过,ALOGx 和相关项也存在一些限制:

  • 它们容易受到“日志垃圾”的影响:日志缓冲区是一种共享资源,因此容易因不相关的日志条目而溢出,进而导致信息缺失。默认情况下,ALOGV 变体在编译时处于停用状态。但是,毋庸置疑,如果启用了该变体,就连它也会导致产生日志垃圾。
  • 底层内核系统调用会出现阻塞,这可能会导致优先级倒置,因而造成测量干扰和不准确。对于 FastMixerFastCapture 等对时间要求严格的线程来说,这是需要特别关注的问题。
  • 如果出于减少日志垃圾的目的而停用特定日志,则会丢失该日志本应捕获的所有信息。 当明确了特定日志本该是有意义的日志后,已不可能以追溯方式启用它。

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 之前的架构

图 1. 引入 media.log 之前的架构

下面几点值得注意:

  • init 可创建 mediaserver 分支并执行该进程。
  • init 可检测到 mediaserver 故障,并根据需要重新创建分支。
  • 未显示 ALOGx 日志记录。

以下示意图展示了将 media.log 添加到架构之后组件之间的新关系:

添加 media.log 之后的架构

图 2. 添加 media.log 之后的架构

重要变更:

  • 客户端使用 NBLOG API 构建日志条目,并将它们附加到共享内存中的循环缓冲区。
  • MediaLogService 可以随时转储循环缓冲区的内容。
  • 循环缓冲区的设计方式使其具有以下特点:任何共享内存损坏都不会导致 MediaLogService 崩溃,该服务仍然能够转储尽可能多的不受损坏影响的缓冲区内容。
  • 无论是写入新条目还是读取现有条目,循环缓冲区都是非阻塞和无锁的。
  • 无需内核系统调用即可对循环缓冲区(可选时间戳除外)执行写入或读取操作。

使用范围

自 Android 4.4 起,AudioFlinger 中只有几个日志点使用 media.log 系统。虽然新 API 不像 ALOGx 那样易于使用,但也不是特别难用。我们建议您学习如何使用新的日志记录系统,以便应对必须使用新系统的情况。 特别是,建议您针对必须频繁、定期且无阻塞运行的 AudioFlinger 线程(例如 FastMixerFastCapture 线程)使用新系统。

使用方法

添加日志

首先,您需要将日志添加到代码中。

FastMixerFastCapture 线程中,请使用如下代码:

logWriter->log("string");
logWriter->logf("format", parameters);
logWriter->logTimestamp();

由于此 NBLog 时间轴仅由 FastMixerFastCapture 线程使用,因此不需要互斥。

在其他 AudioFlinger 线程中,请使用 mNBLogWriter

mNBLogWriter->log("string");
mNBLogWriter->logf("format", parameters);
mNBLogWriter->logTimestamp();

对于除 FastMixerFastCapture 之外的线程,线程的 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