blob: 5f2c28db53a8eb8040e033e2da79cf3076a0c157 [file] [log] [blame]
<html devsite><head>
<title>音频调试</title>
<meta name="project_path" value="/_project.yaml"/>
<meta name="book_path" value="/_book.yaml"/>
</head>
<body>
<!--
Copyright 2017 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<p>
本文介绍了一些与 Android 音频调试有关的提示和技巧。
</p>
<h2 id="teeSink">Tee Sink</h2>
<p>
“tee sink”是一种 AudioFlinger 调试功能,仅在定制版本中提供,用于获取最近音频的短片段以供日后分析。这方便我们比较实际播放或录制的内容与预期内容。
</p>
<p>
出于隐私考虑,tee sink 在编译时和运行时均默认处于停用状态。要使用 tee sink,您需要通过重新编译以及设置属性来启用它。完成调试后,请务必停用此功能;tee sink 在正式版中不能保持启用状态。
</p>
<p>
本节其余部分的说明适用于 Android 7.x 及更高版本。对于 Android 5.x 和 6.x,请将 <code>/data/misc/audioserver</code> 替换为 <code>/data/misc/media</code>。此外,您还必须使用 userdebug 或 eng 版本。如果您使用 userdebug 版本,请使用以下命令停用 verity:</p>
<pre class="devsite-terminal devsite-click-to-copy">
adb root &amp;&amp; adb disable-verity &amp;&amp; adb reboot
</pre>
<h3 id="compile">编译时设置</h3>
<ol>
<li><code class="devsite-terminal">cd frameworks/av/services/audioflinger</code></li>
<li>修改 <code>Configuration.h</code></li>
<li>取消注释 <code>#define TEE_SINK</code></li>
<li>重新编译 <code>libaudioflinger.so</code></li>
<li><code class="devsite-terminal">adb root</code></li>
<li><code class="devsite-terminal">adb remount</code></li>
<li>将新的 <code>libaudioflinger.so</code> 推送或同步到设备的 <code>/system/lib</code></li>
</ol>
<h3 id="runtime">运行时设置</h3>
<ol>
<li><code class="devsite-terminal">adb shell getprop | grep ro.debuggable</code>
<br />确认输出是:<code>[ro.debuggable]: [1]</code>
</li>
<li><code class="devsite-terminal">adb shell</code></li>
<li><code class="devsite-terminal">ls -ld /data/misc/audioserver</code>
<br />
<p>
确认输出是:</p>
<pre>
drwx------ media media ... media
</pre>
<p>
如果目录不存在,请使用如下格式创建:</p>
<pre class="devsite-click-to-copy">
<code class="devsite-terminal">mkdir /data/misc/audioserver</code>
<code class="devsite-terminal">chown media:media /data/misc/audioserver</code>
</pre>
</li>
<li><code class="devsite-terminal">echo af.tee=# &gt; /data/local.prop</code>
<br />其中 <code>af.tee</code> 值是一个数字,在下文中有所说明。
</li>
<li><code class="devsite-terminal">chmod 644 /data/local.prop</code></li>
<li><code class="devsite-terminal">reboot</code></li>
</ol>
<h4><code>af.tee</code> 属性的值</h4>
<p>
<code>af.tee</code> 的值是 0-7 之间的数字,表示多个位的总和(每个功能一个位)。请参阅位于 <code>AudioFlinger.cpp</code> 中的 <code>AudioFlinger::AudioFlinger()</code> 的代码,了解各个位的简短说明:</p>
<ul>
<li>1 = 输出</li>
<li>2 = FastMixer 输出</li>
<li>4 = 各曲目的 AudioRecord 和 AudioTrack</li>
</ul>
<p>
目前还没有针对深度缓冲区和常规混合器的位,不过您可以使用“4”获取类似结果。</p>
<h3 id="test">测试和获取数据</h3>
<ol>
<li>运行您的音频测试。</li>
<li><code class="devsite-terminal">adb shell dumpsys media.audio_flinger</code></li>
<li><code>dumpsys</code> 输出中查找如下行:<br />
<code>tee copied to /data/misc/audioserver/20131010101147_2.wav</code>
<br />这是一个 PCM .wav 文件。
</li>
<li>然后 <code>adb pull</code> 任何相关的 <code>/data/misc/audioserver/*.wav</code> 文件;请注意,曲目专用的转储文件名不会显示在 <code>dumpsys</code> 输出中,但仍会在曲目关闭后保存到 <code>/data/misc/audioserver</code>
</li>
<li>在与其他人分享之前,请先查看转储文件是否涉及隐私权问题。</li>
</ol>
<h4>建议</h4>
<p>要获取更实用的结果,请尝试以下方法:</p>
<ul>
<li>停用触摸提示音和按键音,以减少测试输出过程中的中断。</li>
<li>将所有音量调到最大。</li>
<li>停用通过麦克风发出声音或录音的应用(如果这些应用与测试无关)。</li>
<li>曲目专用的转储仅在曲目关闭时保存;您可能需要强制关闭某个应用才能转储其曲目专用数据。</li>
<li>在测试后立即执行 <code>dumpsys</code>;可用的录制空间是有限的。</li>
<li>要确保转储文件不会丢失,请定期将其上传到您的主机。您只能保留有限数量的转储文件;达到此数量上限后,系统会移除较旧的转储。</li>
</ul>
<h3 id="restore">恢复</h3>
<p>
如上文所述,tee sink 功能不能保持启用状态。要恢复您的版本和设备,请执行以下操作:</p>
<ol>
<li>将源代码更改还原到 <code>Configuration.h</code></li>
<li>重新编译 <code>libaudioflinger.so</code></li>
<li>将恢复的 <code>libaudioflinger.so</code> 推送或同步到设备的 <code>/system/lib</code>
</li>
<li><code class="devsite-terminal">adb shell</code></li>
<li><code class="devsite-terminal">rm /data/local.prop</code></li>
<li><code class="devsite-terminal">rm /data/misc/audioserver/*.wav</code></li>
<li><code class="devsite-terminal">reboot</code></li>
</ol>
<h2 id="mediaLog">media.log</h2>
<h3 id="alogx">ALOGx 宏</h3>
<p>
Android SDK 中的标准 Java 语言 Logging API 是 <a href="http://developer.android.com/reference/android/util/Log.html">android.util.Log</a>
</p>
<p>
Android NDK 中的对应 C 语言 API 是 <code>&lt;android/log.h&gt;</code> 中声明的 <code>__android_log_print</code>
</p>
<p>
在 Android 框架的原生部分,我们倾向于使用名为 <code>ALOGE</code><code>ALOGW</code><code>ALOGI</code><code>ALOGV</code> 等的宏。这些宏会在 <code>&lt;utils/Log.h&gt;</code> 中声明,在本文中,我们将它们统称为 <code>ALOGx</code>
</p>
<p>
由于所有这些 API 均易于使用和理解,因此在整个 Android 平台中得到了广泛应用。尤其是 <code>mediaserver</code> 进程(其中包括 AudioFlinger 声音服务器),它使用了大量 <code>ALOGx</code>
</p>
<p>
不过,<code>ALOGx</code> 和相关项也存在一些限制:</p>
<ul>
<li>
它们容易受到“日志垃圾”的影响:日志缓冲区是一种共享资源,因此容易因不相关的日志条目而溢出,进而导致信息缺失。默认情况下,<code>ALOGV</code> 变量在编译时处于停用状态。但是,毋庸置疑,如果启用该变量,则会导致日志垃圾产生。
</li>
<li>
底层内核系统调用会出现阻塞,从而可能导致优先级倒置、测量干扰和不准确性。对于 <code>FastMixer</code><code>FastCapture</code> 等具有时效性的线程来说,这是需要特别关注的问题。
</li>
<li>
如果出于减少日志垃圾的目的而停用特定日志,则会丢失该日志本应捕获的所有信息。当明确了特定日志本该是有意义的日志后,已不可能以追溯方式启用它。<i></i>
</li>
</ul>
<h3 id="nblog">NBLOG、media.log 和 MediaLogService</h3>
<p>
<code>NBLOG</code> API 和关联的 <code>media.log</code> 进程以及 <code>MediaLogService</code> 服务共同形成较新的媒体日志记录系统,专为解决上述问题而设计。我们将使用术语“media.log”来泛指所有这三个元素。但是严格来说,<code>NBLOG</code> 是 C++ Logging API、<code>media.log</code> 是 Linux 进程名称,而 <code>MediaLogService</code> 是用于检查日志的 Android Binder 服务。
</p>
<p>
<code>media.log</code>“时间轴”是一系列相对排序得到保留的日志条目。按照惯例,每个线程都应该使用其自己的时间轴。
</p>
<h3 id="benefits">优势</h3>
<p>
<code>media.log</code> 系统的优势在于:
</p>
<ul>
<li>除非需要,否则它不会在主日志中产生垃圾内容。</li>
<li>即使在 <code>mediaserver</code> 崩溃或中断时,也可以对其进行检查。</li>
<li>在每个时间轴均是非阻塞的。</li>
<li>对性能的干扰较小(当然完全不会产生干扰的日志是不存在的)。
</li>
</ul>
<h3 id="architecture">架构</h3>
<p>
以下示意图展示了在引入 <code>media.log</code> 之前,<code>mediaserver</code> 进程和 <code>init</code> 进程之间的关系:</p>
<img src="images/medialog_before.png" alt="引入 media.log 前的架构" id="figure1"/>
<p class="img-caption">
<strong>图 1. </strong> 引入 media.log 前的架构</p>
<p>
值得注意的几点是:</p>
<ul>
<li><code>init</code> 会派生并执行 <code>mediaserver</code></li>
<li><code>init</code> 可检测到 <code>mediaserver</code> 故障,并根据需要重新派生。</li>
<li><code>ALOGx</code> 记录不会显示。</li>
</ul>
<p>
以下示意图展示了将 <code>media.log</code> 添加到架构之后组件之间的新关系:</p>
<img src="images/medialog_after.png" alt="添加 media.log 后的架构" id="figure2"/>
<p class="img-caption">
<strong>图 2. </strong> 添加 media.log 后的架构</p>
<p>
重要变化:</p>
<ul>
<li>
客户端使用 <code>NBLOG</code> API 构建日志条目,并将它们附加到共享内存中的环形缓冲区。
</li>
<li>
<code>MediaLogService</code> 可以随时转储循环缓冲区的内容。
</li>
<li>
环形缓冲区的设计方式使其具有以下特点:任何共享内存损坏都不会导致 <code>MediaLogService</code> 崩溃,并且仍然能够转储尽可能多的不受损坏影响的缓冲区内容。
</li>
<li>
无论是写入新条目还是读取现有条目,环形缓冲区都是非阻塞和无锁定的。
</li>
<li>
无需内核系统调用即可对环形缓冲区(可选时间戳除外)执行写入或读取操作。
</li>
</ul>
<h4>使用范围</h4>
<p>
自 Android 4.4 起,AudioFlinger 中只有几个日志点使用 <code>media.log</code> 系统。虽然新 API 不像 <code>ALOGx</code> 那样易于使用,但也不是特别难用。我们建议您学习如何使用新的日志记录系统,以便应对必须使用新系统的情况。具体而言,建议您针对必须频繁、定期且无阻塞运行的 AudioFlinger 线程(例如 <code>FastMixer</code><code>FastCapture</code> 线程)使用新系统。
</p>
<h3 id="how">使用方法</h3>
<h4>添加日志</h4>
<p>
首先,您需要将日志添加到代码中。
</p>
<p>
<code>FastMixer</code><code>FastCapture</code> 线程中,请使用如下代码:
</p>
<pre class="devsite-click-to-copy">
logWriter-&gt;log("string");
logWriter-&gt;logf("format", parameters);
logWriter-&gt;logTimestamp();
</pre>
<p>
由于该 <code>NBLog</code> 时间轴仅供 <code>FastMixer</code><code>FastCapture</code> 线程使用,因此不需要互斥。
</p>
<p>
在其他 AudioFlinger 线程中,请使用 <code>mNBLogWriter</code>
</p>
<pre class="devsite-click-to-copy">
mNBLogWriter-&gt;log("string");
mNBLogWriter-&gt;logf("format", parameters);
mNBLogWriter-&gt;logTimestamp();
</pre>
<p>
对于除 <code>FastMixer</code><code>FastCapture</code> 之外的线程,线程的 <code>NBLog</code> 时间轴既可以由线程本身使用,也可由 binder 操作使用。由于 <code>NBLog::Writer</code> 不会按时间轴提供任何隐式互斥,因此请确保所有日志都发生在线程的互斥 <code>mLock</code> 所在的上下文中。
</p>
<p>
添加日志后,请重新编译 AudioFlinger。
</p>
<p class="caution"><strong>注意</strong>:由于时间轴的设计略去了互斥,因此每个线程需要有单独的 <code>NBLog::Writer</code> 时间轴,以确保线程安全性。如果您希望多个线程使用同一时间轴,则可以使用现有的互斥进行保护(如上文中有关 <code>mLock</code> 的部分所述)。或者您也可以使用 <code>NBLog::LockedWriter</code> 封装容器而非 <code>NBLog::Writer</code>。不过,这会使该 API 的以下这项主要优势无效:非阻塞行为。
</p>
<p>完整的 <code>NBLog</code> API 位于 <code>frameworks/av/include/media/nbaio/NBLog.h</code>
</p>
<h4>启用 media.log</h4>
<p>
<code>media.log</code> 默认处于停用状态,仅当属性 <code>ro.test_harness</code><code>1</code> 时才会启用。启用方式如下:</p>
<pre class="devsite-click-to-copy">
<code class="devsite-terminal">adb root</code>
<code class="devsite-terminal">adb shell</code>
<code class="devsite-terminal">echo ro.test_harness=1 &gt; /data/local.prop</code>
<code class="devsite-terminal">chmod 644 /data/local.prop</code>
<code class="devsite-terminal">reboot</code>
</pre>
<p>
重新启动时会丢失连接,因此:
</p>
<pre class="devsite-terminal devsite-click-to-copy">
adb shell
</pre>
<code>ps media</code> 命令现在将显示两个进程:
<ul>
<li>media.log</li>
<li>mediaserver</li>
</ul>
<p>
请留意 <code>mediaserver</code> 的进程 ID,以供稍后使用。
</p>
<h4>显示时间轴</h4>
<p>
您随时可以手动请求日志转储。此命令会显示来自最近的所有活动中时间轴的日志,然后将其清除:</p>
<pre class="devsite-terminal devsite-click-to-copy">
dumpsys media.log
</pre>
<p>
请注意,根据设计,时间轴是独立的,没有用于合并时间轴的功能。
</p>
<h4>在出现 mediaserver 故障后恢复日志</h4>
<p>
现在尝试终止 <code>mediaserver</code> 进程:<code>kill -9 #</code>;其中 # 是之前提到的进程 ID。您应该会在主 <code>logcat</code><code>media.log</code> 中看到一个转储,其中会显示导致最终崩溃的所有日志。
</p>
<pre class="devsite-terminal devsite-click-to-copy">
dumpsys media.log
</pre>
</body></html>