AAudio и MMAP

AAudio — это аудио API, представленный в выпуске Android 8.0. В версии Android 8.1 есть усовершенствования для уменьшения задержки при использовании вместе с HAL и драйвером, поддерживающим MMAP. В этом документе описывается уровень аппаратной абстракции (HAL) и изменения драйвера, необходимые для поддержки функции MMAP AAudio в Android.

Для поддержки AAudio MMAP требуется:

  • отчет о возможностях MMAP HAL
  • реализация новых функций в HAL
  • опционально реализация пользовательского ioctl() для буфера режима EXCLUSIVE
  • предоставление дополнительного аппаратного пути данных
  • настройка системных свойств, которые включают функцию MMAP

Аудио архитектура

AAudio — это новый собственный API C, который представляет собой альтернативу Open SL ES. Он использует шаблон проектирования Builder для создания аудиопотоков.

AAudio обеспечивает путь данных с малой задержкой. В режиме EXCLUSIVE эта функция позволяет коду клиентского приложения записывать непосредственно в отображаемый в память буфер, который используется совместно с драйвером ALSA. В режиме SHARED буфер MMAP используется микшером, работающим на AudioServer. В режиме EXCLUSIVE задержка значительно меньше, потому что данные обходят микшер.

В режиме EXCLUSIVE служба запрашивает буфер MMAP из HAL и управляет ресурсами. Буфер MMAP работает в режиме NOIRQ, поэтому нет общих счетчиков чтения/записи для управления доступом к буферу. Вместо этого клиент поддерживает временную модель оборудования и прогнозирует, когда будет считан буфер.

На приведенной ниже диаграмме мы видим, как данные импульсно-кодовой модуляции (ИКМ) передаются через MMAP FIFO в драйвер ALSA. Временные метки периодически запрашиваются службой AAudio, а затем передаются модели синхронизации клиента через атомарную очередь сообщений.

Схема потока данных ИКМ.
Рисунок 1. Поток данных PCM через FIFO в ALSA

В режиме SHARED также используется временная модель, но она находится в AAudioService.

Для захвата звука используется аналогичная модель, но данные PCM передаются в противоположном направлении.

HAL изменения

Для крошечной ALSA см.:

external/tinyalsa/include/tinyalsa/asoundlib.h
external/tinyalsa/include/tinyalsa/pcm.c
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);

Для устаревшего HAL см.:

hardware/libhardware/include/hardware/audio.h
hardware/qcom/audio/hal/audio_hw.c
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);

Для HIDL-аудио HAL:

hardware/interfaces/audio/2.0/IStream.hal
hardware/interfaces/audio/2.0/types.hal
hardware/interfaces/audio/2.0/default/Stream.h
start() generates (Result retval);
stop() generates (Result retval) ;
createMmapBuffer(int32_t minSizeFrames)
       generates (Result retval, MmapBufferInfo info);
getMmapPosition()
       generates (Result retval, MmapPosition position);

Отчетность о поддержке MMAP

Системному свойству «aaudio.mmap_policy» должно быть присвоено значение 2 (AAUDIO_POLICY_AUTO), чтобы звуковая структура знала, что режим MMAP поддерживается звуковым HAL. (см. «Включение пути данных AAudio MMAP» ниже.)

Файл audio_policy_configuration.xml также должен содержать выходные и входные профили, специфичные для режима MMAP/NO IRQ, чтобы диспетчер политик аудио знал, какой поток открывать при создании клиентов MMAP:

<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>

Открытие и закрытие потока MMAP

createMmapBuffer(int32_t minSizeFrames)
            generates (Result retval, MmapBufferInfo info);

Поток MMAP можно открывать и закрывать, вызывая функции Tinyalsa.

Запрос положения MMAP

Временная метка, переданная обратно в временную модель, содержит положение кадра и МОНОТОННОЕ время в наносекундах:

getMmapPosition()
        generates (Result retval, MmapPosition position);

HAL может получить эту информацию от драйвера ALSA, вызвав новую функцию Tinyalsa:

int pcm_mmap_get_hw_ptr(struct pcm* pcm,
                        unsigned int *hw_ptr,
                        struct timespec *tstamp);

Файловые дескрипторы для общей памяти

Путь данных AAudio MMAP использует область памяти, совместно используемую оборудованием и аудиослужбой. На общую память ссылаются с помощью файлового дескриптора, созданного драйвером ALSA.

Изменения ядра

Если дескриптор файла напрямую связан с файлом драйвера /dev/snd/ , то он может использоваться службой AAudio в режиме SHARED. Но дескриптор не может быть передан клиентскому коду для ЭКСКЛЮЗИВНОГО режима. Файловый дескриптор /dev/snd/ обеспечивает слишком широкий доступ к клиенту, поэтому он заблокирован SELinux.

Для поддержки режима EXCLUSIVE необходимо преобразовать дескриптор /dev/snd/ в файловый дескриптор anon_inode:dmabuf . SELinux позволяет передать этот файловый дескриптор клиенту. Он также может использоваться AAudioService.

Дескриптор файла anon_inode:dmabuf можно создать с помощью библиотеки памяти Android Ion.

Дополнительные сведения см. на следующих внешних ресурсах:

  1. «Распределитель памяти Android ION» https://lwn.net/Articles/480055/
  2. «Обзор Android ION» https://wiki.linaro.org/BenjaminGaignard/ion
  3. "Интеграция распределителя памяти ION" https://lwn.net/Articles/565469/

HAL изменения

Службе AAudio необходимо знать, поддерживается ли этот anon_inode:dmabuf . До Android 10.0 единственным способом сделать это было передать размер буфера MMAP в виде отрицательного числа, например. -2048 вместо 2048, если поддерживается. В Android 10.0 и более поздних версиях вы можете установить флаг AUDIO_MMAP_APPLICATION_SHAREABLE .

mmapBufferInfo |= AUDIO_MMAP_APPLICATION_SHAREABLE;

Изменения в звуковой подсистеме

Для AAudio требуется дополнительный путь данных на внешнем аудиоинтерфейсе аудиоподсистемы, чтобы он мог работать параллельно с исходным путем AudioFlinger. Этот устаревший путь используется для всех других системных звуков и звуков приложений. Эта функциональность может быть обеспечена программным микшером в DSP или аппаратным микшером в SOC.

Включение пути данных AAudio MMAP

AAudio будет использовать устаревший путь данных AudioFlinger, если MMAP не поддерживается или не может открыть поток. Таким образом, AAudio будет работать с аудиоустройством, которое не поддерживает путь MMAP/NOIRQ.

При тестировании поддержки MMAP для AAudio важно знать, действительно ли вы тестируете путь данных MMAP или просто тестируете устаревший путь данных. Ниже описано, как включить или принудительно указать определенные пути данных и как запросить путь, используемый потоком.

Свойства системы

Вы можете установить политику MMAP через свойства системы:

  • 1 = AAUDIO_POLICY_NEVER — использовать только устаревший путь. Даже не пытайтесь использовать MMAP.
  • 2 = AAUDIO_POLICY_AUTO — попробуйте использовать MMAP. Если это не удается или недоступно, используйте устаревший путь.
  • 3 = AAUDIO_POLICY_ALWAYS — использовать только путь MMAP. Не возвращайтесь к устаревшему пути.

Их можно установить в Makefile устройств, например:

# 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

Вы также можете переопределить эти значения после загрузки устройства. Вам нужно будет перезапустить аудиосервер, чтобы изменения вступили в силу. Например, чтобы включить режим AUTO для MMAP:

adb root
adb shell setprop aaudio.mmap_policy 2
adb shell killall audioserver

В ndk/sysroot/usr/include/aaudio/AAudioTesting.h , позволяющие переопределить политику использования пути MMAP:

aaudio_result_t AAudio_setMMapPolicy(aaudio_policy_t policy);

Чтобы узнать, использует ли поток путь MMAP, вызовите:

bool AAudioStream_isMMapUsed(AAudioStream* stream);