音频调试

使用集合让一切井井有条 根据您的偏好保存内容并对其进行分类。

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

三通水槽

“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 build。如果您使用 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.cpp 中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 功能。按如下方式恢复您的构建和设备:

  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

媒体日志

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 绑定服务。

media.log “时间线”是一系列日志条目,其相对顺序被保留。按照惯例,每个线程都应该使用自己的时间线。

好处

media.log系统的好处是:

  • 除非需要,否则不会向主日志发送垃圾邮件。
  • 即使在mediaserver崩溃或挂起时也可以检查。
  • 每个时间线都是非阻塞的。
  • 对性能的干扰较小。 (当然,没有任何形式的日志记录是完全非侵入性的。)

建筑学

下图展示了mediaserver进程和init进程的关系,在引入media.log之前:

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时间线。 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现在将显示两个进程:
  • 媒体日志
  • 媒体服务器

记下mediaserver的进程 ID 以备后用。

显示时间线

您可以随时手动请求日志转储。此命令显示所有活动和最近时间线的日志,然后清除它们:

dumpsys media.log

请注意,按照设计时间线是独立的,并且没有用于合并时间线的工具。

媒体服务器死亡后恢复日志

现在尝试杀死mediaserver进程: kill -9 # ,其中 # 是您之前记下的进程 ID。您应该在主logcat中看到来自media.log的转储,显示导致崩溃的所有日志。

dumpsys media.log