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, поэтому общие счетчики чтения/записи для управления доступом к буферу отсутствуют. Вместо этого клиент поддерживает временную модель оборудования и прогнозирует, когда будет прочитан буфер.

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

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

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

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

Изменения HAL

Для tinyALSA см.:

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. Но дескриптор не может быть передан клиентскому коду для режима EXCLUSIVE. Дескриптор файла /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

Вы также можете переопределить эти значения после загрузки устройства. Вам нужно будет перезапустить аудиосервер, чтобы изменения вступили в силу. Например, чтобы включить режим АВТО для 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);