音频调试

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

编译时设置

  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.cppAudioFlinger::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 功能。请按如下方式恢复您的构建和设备

  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_print,声明在 <android/log.h> 中。

在 Android 框架的 native 部分,我们更喜欢名为 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 进程的关系

Architecture before media.log

图 1. media.log 之前的架构

重点

  • init fork 和 exec mediaserver
  • init 检测到 mediaserver 的终止,并在必要时重新 fork。
  • 未显示 ALOGx 日志记录。

下图显示了将 media.log 添加到架构后,组件的新关系

Architecture after 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。您应该在主 logcat 中看到来自 media.log 的转储,显示崩溃前的所有日志。

dumpsys media.log