音频调试

本文介绍了一些与 Android 音频调试有关的提示和技巧。

Tee Sink

“tee sink”是一种 AudioFlinger 调试功能,仅在定制版本中提供,用于获取最近音频的短片段以供日后分析。这方便我们比较实际播放或录制的内容与预期内容。

出于隐私考虑,tee sink 在编译时和运行时均默认处于停用状态。要使用 tee sink,您需要通过重新编译以及设置属性来启用它。完成调试后,请务必停用此功能;tee sink 在正式版中不能保持启用状态。

本节其余部分的说明适用于 Android 5.x 和 6.x。对于 Android 7.x,请将 /data/misc/media 替换为 /data/misc/audioserver。此外,您还必须使用 userdebug 或 eng 版本。如果您使用 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/media

    确认输出是:

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

    如果目录不存在,请使用如下格式创建:

    mkdir /data/misc/media
    chown media:media /data/misc/media
    
  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/media/20131010101147_2.wav
    这是一个 PCM .wav 文件。
  4. 然后 adb pull 任何感兴趣的 /data/misc/media/*.wav 文件;请注意,曲目专用的转储文件名不会显示在 dumpsys 输出中,但仍会在曲目关闭后保存到 /data/misc/media
  5. 在与其他人分享之前,请先查看转储文件是否涉及隐私权问题。

建议

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

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

恢复

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

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

media.log

ALOGx 宏

Android SDK 中的标准 Java 语言 Logging 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++ Logging 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_harness1 时才会启用。启用方式如下:

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。您应该在主 logcatmedia.log 中看到一个转储,其中会显示导致最终崩溃的所有日志。

dumpsys media.log