blob: 7ee2f6e1b029c5b9e1ddc085446299cc4f869aeb [file] [log] [blame]
<html devsite>
<head>
<title>AAudio and 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 is an audio API introduced in the Android 8.0 release. The Android 8.1
release has enhancements to reduce latency when used in conjunction with a HAL
and driver that support MMAP. This document describes the hardware abstraction
layer (HAL) and driver changes needed to support AAudio's MMAP feature in
Android.
</p>
<p>
Support for AAudio MMAP requires:
</p>
<ul>
<li>reporting the MMAP capabilities of the HAL
<li>implementing new functions in the HAL
<li>optionally implementing a custom ioctl() for the EXCLUSIVE mode buffer
<li>providing an additional hardware data path</li>
<li>setting system properties that enable MMAP feature</li>
</ul>
<aside class="note"><strong>Note:</strong> Enable the MMAP feature
only on Android O MR1 or higher.</aside>
<h2 id="aaudio-architecture">AAudio architecture</h2>
<p>
<a
href="https://developer.android.com/ndk/guides/audio/aaudio/aaudio.html">AAudio</a>
is a new native C API that provides an alternative to Open SL ES. It uses a
Builder design pattern to create audio streams.
</p>
<p>
AAudio provides a low-latency data path. In EXCLUSIVE mode, the feature
allows client application code to write directly into a memory mapped buffer
that is shared with the ALSA driver. In SHARED mode, the MMAP buffer is used by
a mixer running in the AudioServer. In EXCLUSIVE mode, the latency is
significantly less because the data bypasses the mixer.
</p>
<p>
In EXCLUSIVE mode, the service requests the MMAP buffer from the HAL and manages
the resources. The MMAP buffer is running in NOIRQ mode, so there are no shared
read/write counters to manage access to the buffer. Instead, the client
maintains a timing model of the hardware and predicts when the buffer will be
read.
</p>
<p>
In the diagram below, we can see the Pulse-code modulation (PCM) data flowing
down through the MMAP FIFO into the ALSA driver. Timestamps are periodically
requested by the AAudio service and then passed up to the client's timing model
through an atomic message queue.
</p>
<figure id="pcm-data-flow">
<img src="/devices/audio/images/pcm_data_flow.png"
width="630"
alt="PCM data flow diagram.">
<figcaption><b>Figure 1.</b> PCM data flow through FIFO to ALSA</figcaption>
</figure>
<p>
In SHARED mode, a timing model is also used, but it lives in the AAudioService.
</p>
<p>
For audio capture, a similar model is used, but the PCM data flows in the
opposite direction.
</p>
<h2 id="hal-changes">HAL changes</h2>
<p>
For tinyALSA see:
</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>
For the legacy HAL, see:
</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>
For HIDL audio 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">Reporting MMAP support</h3>
<p>
System property "aaudio.mmap_policy" should be set to 2 (AAUDIO_POLICY_AUTO) so
the audio framework knows that MMAP mode is supported by the audio HAL. (see
"Enabling AAudio MMAP Data Path" below.)
</p>
<p>
audio_policy_configuration.xml file must also contain an output and input
profile specific to MMAP/NO IRQ mode so that the Audio Policy Manager knows
which stream to open when MMAP clients are created:
</p>
<pre
class="prettyprint">
&lt;mixPort name="mmap_no_irq_out" role="source"
flags="AUDIO_OUTPUT_FLAG_DIRECT|AUDIO_OUTPUT_FLAG_MMAP_NOIRQ"&gt;
&lt;profile name="" format="AUDIO_FORMAT_PCM_16_BIT"
samplingRates="48000"
channelMasks="AUDIO_CHANNEL_OUT_STEREO"/&gt;
&lt;/mixPort&gt;
&lt;mixPort name="mmap_no_irq_in" role="sink" flags="AUDIO_INPUT_FLAG_MMAP_NOIRQ"&gt;
&lt;profile name="" format="AUDIO_FORMAT_PCM_16_BIT"
samplingRates="48000"
channelMasks="AUDIO_CHANNEL_IN_STEREO"/&gt;
&lt;/mixPort&gt;
</pre>
<h3 id="opening-and-closing-an-mmap-stream">Opening and closing an MMAP
stream</h3>
<pre
class="prettyprint">
createMmapBuffer(int32_t minSizeFrames)
generates (Result retval, MmapBufferInfo info);
</pre>
<p>
The MMAP stream can be opened and closed by calling Tinyalsa functions.
</p>
<h3 id="querying-mmap-position">Querying MMAP position</h3>
<p>
The timestamp passed back to the Timing Model contains a frame position and a
MONOTONIC time in nanoseconds:
</p>
<pre
class="prettyprint">
getMmapPosition()
generates (Result retval, MmapPosition position);
</pre>
<p>
The HAL can obtain this information from the ALSA driver by calling a new
Tinyalsa function:
</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">Kernel changes</h2>
<p>
Enable MMAP/NOIRQ mode in the driver.
</p>
<p>
The shared memory is referenced using a file descriptor that is generated by the
ALSA driver. If the file descriptor is directly associated with a
<code>/dev/snd/</code> driver file, then it can be used by the AAudio service in
SHARED mode. But the descriptor cannot be passed to the client code for
EXCLUSIVE mode. The <code>/dev/snd/</code> file descriptor would provide too
broad of access to the client, so it is blocked by SELinux.
</p>
<p>
In order to support EXCLUSIVE mode, it is necessary to convert the
<code>/dev/snd/</code> descriptor to an <code>anon_inode:dmabuffer</code> file
descriptor. SELinux allows that file descriptor to be passed to the client. It
can also be used by the AAudioService.
</p>
<p>
An <code>anon_inode:dmabuffer</code> file descriptor can be generated using the
Android Ion memory library.
</p>
<p>
For additional information, see these external resources:
</p>
<ol>
<li>"The Android ION memory allocator" <a
href="https://lwn.net/Articles/480055/">https://lwn.net/Articles/480055/</a>
<li>"Android ION overview" <a
href="https://wiki.linaro.org/BenjaminGaignard/ion">https://wiki.linaro.org/BenjaminGaignard/ion</a>
<li>"Integrating the ION memory allocator" <a
href="https://lwn.net/Articles/565469/">https://lwn.net/Articles/565469/</a></li>
</ol>
<p>
The AAudio service needs to know if this <code>anon_inode:dmabuffer</code> is
supported. Currently, the only way to do that is to pass the size of the MMAP
buffer as a negative number, eg. -2048 instead of 2048, if supported. A better
way of reporting this without having to open the stream is planned.
</p>
<h2 id="audio-subsystem-changes">Audio subsystem changes</h2>
<p>
AAudio requires an additional data path at the audio front end of the audio
subsystem so it can operate in parallel with the original AudioFlinger path.
That legacy path is used for all other system sounds and application sounds.
This functionality could be provided by a software mixer in a DSP or a hardware
mixer in the SOC.
</p>
<h2 id="enabling-aaudio-mmap-data-path">Enabling AAudio MMAP Data Path</h2>
<p>
AAudio will use the legacy AudioFlinger data path if MMAP is not supported or
fails to open a stream. So AAudio will work with an audio device that does not
support MMAP/NOIRQ path.
</p>
<p>
When testing MMAP support for AAudio, it is important to know whether you are
actually testing the MMAP data path or merely testing the legacy data path. The
following describes how to enable or force specific data paths, and how to query
the path used by a stream.
</p>
<h3 id="system-properties">System properties</h3>
<p>
You can set the MMAP policy through system properties:
</p>
<ul>
<li>1 = AAUDIO_POLICY_NEVER - Only use legacy path. Do not even try to use MMAP.
<li>2 = AAUDIO_POLICY_AUTO - Try to use MMAP. If that fails or is not available,
then use legacy path.
<li>3 = AAUDIO_POLICY_ALWAYS - Only use MMAP path. Do not fall back to legacy
path.</li>
</ul>
<p>
These may be set in the devices Makefile, like so:
</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>
You can also override these values after the device has booted.
You will need to restart the audioserver for the change to take effect.
For example, to enable AUTO mode for 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>
There are functions provided in
<code>ndk/sysroot/usr/include/aaudio/AAudioTesting.h</code> that allow you to
override the policy for using MMAP path:
</p>
<pre
class="prettyprint">aaudio_result_t AAudio_setMMapPolicy(aaudio_policy_t policy);
</pre>
<p>
To find out whether a stream is using MMAP path, call:
</p>
<pre
class="prettyprint">bool AAudioStream_isMMapUsed(AAudioStream* stream);
</pre>
</body>
</html>