| <html devsite><head> |
| <title>AAudio 和 MMAP</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> |
| AAudio 是在 Android 8.0 版本中引入的一种音频 API。Android 8.1 版本具有增强功能,可在与支持 MMAP 的 HAL 和驱动程序结合使用时缩短延迟时间。本文档说明了需要进行哪些硬件抽象层 (HAL) 及驱动程序方面的更改才能在 Android 中支持 AAudio 的 MMAP 功能。 |
| </p> |
| |
| <p> |
| 要支持 AAudio MMAP,须执行以下操作:</p> |
| |
| <ul> |
| <li>报告 HAL 的 MMAP 功能</li><li>在 HAL 中实现新功能</li><li>为“专有”模式缓冲区实现自定义 ioctl()(可选)</li><li>提供一个额外的硬件数据路径</li> |
| <li>设置用于启用 MMAP 功能的系统属性</li> |
| </ul> |
| |
| <aside class="note"><strong>注意:</strong>请仅在 Android O MR1 或更高版本上启用 MMAP 功能。</aside> |
| |
| <h2 id="aaudio-architecture">AAudio 架构</h2> |
| |
| <p> |
| <a href="https://developer.android.com/ndk/guides/audio/aaudio/aaudio.html">AAudio</a> 是一种新的本地 C API,可提供 Open SL ES 的替代方案。它使用“生成器”设计模式来创建音频流。 |
| </p> |
| |
| <p> |
| AAudio 提供了一个低延迟数据路径。在“专有”模式下,该功能可让客户端应用代码直接写入到与 ALSA 驱动程序共享的内存映射缓冲区。在“共享”模式下,MMAP 缓冲区由在 AudioServer 中运行的混音器使用。在“专有”模式下,由于数据会绕过混音器,延迟时间会明显缩短。 |
| </p> |
| |
| <p> |
| 在“专有”模式下,服务可从 HAL 请求 MMAP 缓冲区并管理资源。MMAP 缓冲区会在 NOIRQ 模式下运行,因此没有共享的读/写计数器可用来管理对缓冲区的访问权限。相反,客户端会维护硬件的计时模型,并预测将在何时读取缓冲区。 |
| </p> |
| |
| <p> |
| 在下图中,我们可以看到脉冲编码调制 (PCM) 数据通过 MMAP FIFO 向下流入 ALSA 驱动程序。AAudio 服务会定期请求时间戳,然后通过原子消息队列将其传递给客户端的计时模型。 |
| </p> |
| |
| <figure id="pcm-data-flow"> |
| <img src="/devices/audio/images/pcm_data_flow.png" width="630" alt="PCM 数据流图。"/> |
| <figcaption><b>图 1.</b> PCM 数据通过 FIFO 流向 ALSA</figcaption> |
| </figure> |
| |
| <p> |
| 在“共享”模式下,也会用到计时模型,但它位于 AAudioService 中。 |
| </p> |
| |
| <p> |
| 在音频捕获过程中,会用到类似的模型,但 PCM 数据会以相反方向流动。 |
| </p> |
| |
| <h2 id="hal-changes">HAL 变更</h2> |
| <p> |
| 对于 tinyALSA,请参阅:</p> |
| |
| <pre class="prettyprint"> |
| external/tinyalsa/include/tinyalsa/asoundlib.h |
| external/tinyalsa/include/tinyalsa/pcm.c |
| </pre> |
| |
| <pre class="prettyprint"> |
| int pcm_start(struct pcm *pcm); |
| int pcm_stop(struct pcm *pcm); |
| int pcm_mmap_begin(struct pcm *pcm, void **areas, |
| unsigned int *offset, |
| unsigned int *frames); |
| int pcm_get_poll_fd(struct pcm *pcm); |
| int pcm_mmap_commit(struct pcm *pcm, unsigned int offset, |
| unsigned int frames); |
| int pcm_mmap_get_hw_ptr(struct pcm* pcm, unsigned int *hw_ptr, |
| struct timespec *tstamp); |
| </pre> |
| |
| <p> |
| 对于旧版 HAL,请参阅:</p> |
| |
| <pre class="prettyprint"> |
| hardware/libhardware/include/hardware/audio.h |
| hardware/qcom/audio/hal/audio_hw.c |
| </pre> |
| |
| <pre class="prettyprint"> |
| int start(const struct audio_stream_out* stream); |
| int stop(const struct audio_stream_out* stream); |
| int create_mmap_buffer(const struct audio_stream_out *stream, |
| int32_t min_size_frames, |
| struct audio_mmap_buffer_info *info); |
| int get_mmap_position(const struct audio_stream_out *stream, |
| struct audio_mmap_position *position); |
| </pre> |
| |
| <p> |
| 对于 HIDL 音频 HAL:</p> |
| |
| <pre class="prettyprint"> |
| hardware/interfaces/audio/2.0/IStream.hal |
| hardware/interfaces/audio/2.0/types.hal |
| hardware/interfaces/audio/2.0/default/Stream.h |
| </pre> |
| |
| <pre class="prettyprint"> |
| start() generates (Result retval); |
| stop() generates (Result retval) ; |
| createMmapBuffer(int32_t minSizeFrames) |
| generates (Result retval, MmapBufferInfo info); |
| getMmapPosition() |
| generates (Result retval, MmapPosition position); |
| </pre> |
| |
| <h3 id="reporting-mmap-support">报告 MMAP 支持</h3> |
| |
| <p> |
| 系统属性“aaudio.mmap_policy”应设置为 2 (AAUDIO_POLICY_AUTO),以便音频框架知道音频 HAL 支持 MMAP 模式(请参阅下面的“启用 AAudio MMAP 数据路径”)。 |
| </p> |
| |
| <p> |
| audio_policy_configuration.xml 文件还必须包含特定于 MMAP/NO IRQ 模式的输出和输入配置文件,以便音频政策管理器得知要在创建 MMAP 客户端时打开的流:</p> |
| |
| <pre class="prettyprint"> |
| <mixPort name="mmap_no_irq_out" role="source" |
| flags="AUDIO_OUTPUT_FLAG_DIRECT|AUDIO_OUTPUT_FLAG_MMAP_NOIRQ"> |
| <profile name="" format="AUDIO_FORMAT_PCM_16_BIT" |
| samplingRates="48000" |
| channelMasks="AUDIO_CHANNEL_OUT_STEREO"/> |
| </mixPort> |
| |
| <mixPort name="mmap_no_irq_in" role="sink" flags="AUDIO_INPUT_FLAG_MMAP_NOIRQ"> |
| <profile name="" format="AUDIO_FORMAT_PCM_16_BIT" |
| samplingRates="48000" |
| channelMasks="AUDIO_CHANNEL_IN_STEREO"/> |
| </mixPort> |
| </pre> |
| |
| <h3 id="opening-and-closing-an-mmap-stream">打开和关闭 MMAP 流</h3> |
| |
| <pre class="prettyprint"> |
| createMmapBuffer(int32_t minSizeFrames) |
| generates (Result retval, MmapBufferInfo info); |
| </pre> |
| |
| <p> |
| 调用 Tinyalsa 函数可以打开和关闭 MMAP 流。 |
| </p> |
| |
| <h3 id="querying-mmap-position">查询 MMAP 位置</h3> |
| |
| <p> |
| 传回到计时模型的时间戳包含帧位置和 MONOTONIC 时间(以纳秒为单位):</p> |
| |
| <pre class="prettyprint"> |
| getMmapPosition() |
| generates (Result retval, MmapPosition position); |
| </pre> |
| |
| <p> |
| HAL 可以通过调用新 Tinyalsa 函数从 ALSA 驱动程序中获取此信息:</p> |
| |
| <pre class="prettyprint"> |
| int pcm_mmap_get_hw_ptr(struct pcm* pcm, |
| unsigned int *hw_ptr, |
| struct timespec *tstamp); |
| </pre> |
| |
| <h2 id="kernel-changes">内核变更</h2> |
| |
| <p> |
| 在驱动程序中启用 MMAP/NOIRQ 模式。 |
| </p> |
| |
| <p> |
| 您可以使用由 ALSA 驱动程序生成的文件描述符引用共享内存。如果文件描述符直接与 <code>/dev/snd/</code> 驱动程序文件关联,则可用于“共享”模式下的 AAudio 服务。但是对于“专有”模式,则无法将此描述符传递给客户端代码。<code>/dev/snd/</code> 文件描述符会提供过于宽泛的客户端访问权限,因此 SELinux 会将其屏蔽。 |
| </p> |
| |
| <p> |
| 要支持“专有”模式,需将 <code>/dev/snd/</code> 描述符转换为 <code>anon_inode:dmabuffer</code> 文件描述符。SELinux 允许将此文件描述符传递给客户端。该文件描述符也可用于 AAudioService。 |
| </p> |
| |
| <p> |
| 可使用 Android Ion 内存库生成 <code>anon_inode:dmabuffer</code> 文件描述符。 |
| </p> |
| |
| <p> |
| 有关更多信息,请参阅下列外部资源:</p> |
| |
| <ol> |
| <li>“Android ION 内存分配器”<a href="https://lwn.net/Articles/480055/">https://lwn.net/Articles/480055/</a> |
| </li><li>“Android ION 概览”<a href="https://wiki.linaro.org/BenjaminGaignard/ion">https://wiki.linaro.org/BenjaminGaignard/ion</a> |
| </li><li>“集成 ION 内存分配器”<a href="https://lwn.net/Articles/565469/">https://lwn.net/Articles/565469/</a></li> |
| </ol> |
| |
| <p> |
| AAudio 服务需要了解 <code>anon_inode:dmabuffer</code> 是否受支持。目前,验证是否受支持的唯一方法是将 MMAP 缓冲区大小传递为负数(例如 -2048 而非 2048;如果支持的话)。我们已经在规划不必打开流即可报告此信息的更佳方法。 |
| </p> |
| |
| <h2 id="audio-subsystem-changes">音频子系统变更</h2> |
| |
| <p> |
| AAudio 在音频子系统的音频前端需要一个额外的数据路径,以便它可以与原始 AudioFlinger 路径并行运行。旧路径用于所有其他系统声音和应用声音。该功能可由 DSP 中的软件混音器或由 SOC 中的硬件混音器提供。</p> |
| |
| <h2 id="enabling-aaudio-mmap-data-path">启用 AAudio MMAP 数据路径</h2> |
| |
| <p> |
| 如果 MMAP 不受支持或无法打开流,AAudio 将使用旧的 AudioFlinger 数据路径。因此,AAudio 将可用于不支持 MMAP/NOIRQ 路径的音频设备。 |
| </p> |
| |
| <p> |
| 在测试 AAudio 的 MMAP 支持功能时,了解您是在实际测试 MMAP 数据路径还是仅测试旧的数据路径这一点尤为重要。下面介绍了如何启用或强制执行特定数据路径,以及如何查询流所使用的路径。 |
| </p> |
| |
| <h3 id="system-properties">系统属性</h3> |
| |
| <p> |
| 您可以通过系统属性设置 MMAP 政策:</p> |
| |
| <ul> |
| <li>1 = AAUDIO_POLICY_NEVER - 仅使用旧的路径,甚至不要尝试使用 MMAP。</li><li>2 = AAUDIO_POLICY_AUTO - 尝试使用 MMAP。如果此操作失败或 MMAP 不可用,则使用旧路径。 |
| </li><li>3 = AAUDIO_POLICY_ALWAYS - 仅使用 MMAP 路径。不返回到旧路径。</li> |
| </ul> |
| |
| <p> |
| 您可以在设备 Makefile 中设置这些项,如下所示:</p> |
| |
| <pre class="prettyprint"> |
| # Enable AAudio MMAP/NOIRQ data path. |
| # 2 is AAUDIO_POLICY_AUTO so it will try MMAP then fallback to Legacy path. |
| PRODUCT_PROPERTY_OVERRIDES += aaudio.mmap_policy=2 |
| # Allow EXCLUSIVE then fall back to SHARED. |
| PRODUCT_PROPERTY_OVERRIDES += aaudio.mmap_exclusive_policy=2 |
| </pre> |
| |
| <p> |
| 您也可以在设备启动后替换这些值。 |
| 您需要重新启动 audioserver 才能使更改生效。 |
| 例如,要为 MMAP 启用“自动”模式,请输入以下内容: |
| </p> |
| |
| <pre class="devsite-terminal devsite-click-to-copy"> |
| adb root |
| </pre> |
| <pre class="devsite-terminal devsite-click-to-copy"> |
| adb shell setprop aaudio.mmap_policy 2 |
| </pre> |
| <pre class="devsite-terminal devsite-click-to-copy"> |
| adb shell killall audioserver |
| </pre> |
| |
| <p> |
| <code>ndk/sysroot/usr/include/aaudio/AAudioTesting.h</code> 中提供了一些函数,可让您替换使用 MMAP 路径的政策: |
| </p> |
| |
| <pre class="prettyprint">aaudio_result_t AAudio_setMMapPolicy(aaudio_policy_t policy); |
| </pre> |
| |
| <p> |
| 要了解某个流是否使用 MMAP 路径,请调用: |
| </p> |
| |
| <pre class="prettyprint">bool AAudioStream_isMMapUsed(AAudioStream* stream); |
| </pre> |
| |
| </body></html> |