AAudio i MMAP

AAudio to interfejs API do obsługi dźwięku wprowadzony w wersji Androida 8.0. Wersja Androida 8.1 zawiera ulepszenia, które zmniejszają opóźnienie w połączeniu z interfejsem HAL i sterownikami obsługującymi MMAP. W tym dokumencie opisujemy warstwę abstrakcji sprzętowej (HAL) i zmiany w sterowniku, które są potrzebne do obsługi funkcji MMAP w AAudio na Androidzie.

Obsługa MMAP AAudio wymaga:

  • raportowanie możliwości MMAP interfejsu HAL;
  • implementowanie nowych funkcji w HAL;
  • opcjonalnie implementowanie niestandardowej funkcji ioctl() dla bufora trybu EXCLUSIVE;
  • udostępnianie dodatkowej ścieżki danych sprzętowych;
  • ustawienia właściwości systemowych, które umożliwiają korzystanie z funkcji MMAP;

Architektura AAudio

AAudio to nowy natywny interfejs API w języku C, który stanowi alternatywę dla OpenSL ES. Do tworzenia strumieni audio używa wzorca projektowania Builder.

AAudio zapewnia ścieżkę danych o krótkim czasie oczekiwania. W trybie wyłącznym funkcja ta umożliwia kodowi aplikacji klienta zapisywanie danych bezpośrednio w buforze zmapowanym na pamięć, który jest współdzielony z sterownikiem ALSA. W trybie współdzielonym bufor MMAP jest używany przez mikser działający na serwerze AudioServer. W trybie EXCLUSIVE opóźnienie jest znacznie mniejsze, ponieważ dane omijają mikser.

W trybie EKSKLUZYWNYM usługa wysyła żądanie do bufora MMAP z interfejsu HAL i zarządza zasobami. Bufor MMAP działa w trybie NOIRQ, więc nie ma wspólnych liczników odczytu/zapisu, które umożliwiałyby zarządzanie dostępem do tego bufora. Zamiast tego klient utrzymuje model czasowy sprzętu i przewiduje, kiedy bufor zostanie odczytany.

Na diagramie poniżej widać dane PCM przesyłane przez MMAP FIFO do sterownika ALSA. Usługa AAudio okresowo prosi o sygnały czasowe, które są następnie przekazywane do modelu czasowego klienta za pomocą kolejki atomowych komunikatów.

Diagram przepływu danych PCM
Rysunek 1. Przepływ danych PCM przez FIFO do ALSA

W trybie współdzielonym jest też używany model czasowy, ale znajduje się on w AAudioService.

W przypadku przechwytywania dźwięku używany jest podobny model, ale dane PCM przepływają w przeciwnym kierunku.

Zmiany w HAL

W przypadku 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);

Informacje o starszej wersji HAL znajdziesz tutaj:

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

W przypadku HAL dźwięku HIDL:

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

Zgłaszanie problemów z obsługą MMAP

Właściwość systemowa „aaudio.mmap_policy” powinna być ustawiona na 2 (AAUDIO_POLICY_AUTO), aby platforma audio wiedziała, że tryb MMAP jest obsługiwany przez interfejs audio HAL. (patrz sekcja „Włączanie ścieżki danych MMAP AAudio” poniżej).

Plik audio_policy_configuration.xml musi też zawierać profil wyjściowy i wejściowy odpowiedni dla trybu MMAP/NO IRQ, aby Menedżer zasad dotyczących dźwięku wiedział, który strumień otworzyć podczas tworzenia klientów 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>

Otwieranie i zamykanie strumienia MMAP

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

Strumień MMAP można otwierać i zamykać, wywołując funkcje Tinyalsa.

Zapytanie o pozycję MMAP

Sygnatura czasowa przekazana do modelu czasowego zawiera pozycję klatki i czas monotoniczny w nanosekundach:

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

HAL może uzyskać te informacje z sterownika ALSA, wywołując nową funkcję Tinyalsa:

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

Deskryptory plików dla pamięci współdzielonej

Ścieżka danych AAudio MMAP korzysta z obszaru pamięci współdzielonego przez sprzęt i usługę audio. Odwołania do współdzielonej pamięci są tworzone za pomocą deskryptora pliku wygenerowanego przez sterownik ALSA.

Zmiany w jądrze

Jeśli deskryptor pliku jest bezpośrednio powiązany z plikiem sterownika /dev/snd/, usługa AAudio może go używać w trybie współdzielonym. Deskryptor nie może jednak zostać przekazany do kodu klienta w trybie EXCLUSIVE. Deskryptor pliku /dev/snd/ zapewnia zbyt szeroki dostęp do klienta, dlatego jest blokowany przez SELinux.

Aby obsługiwać tryb EXCLUSIVE, należy przekonwertować deskryptor /dev/snd/ na deskryptor pliku anon_inode:dmabuf. SELinux umożliwia przekazanie tego deskryptora pliku do klienta. Może być też używany przez AAudioService.

Deskryptor pliku anon_inode:dmabuf można wygenerować za pomocą biblioteki pamięci Ion na Androida.

Więcej informacji znajdziesz w tych materiałach zewnętrznych:

  1. „The Android ION memory allocator” https://lwn.net/Articles/480055/
  2. „Przegląd ION w Androidzie” https://wiki.linaro.org/BenjaminGaignard/ion
  3. „Integrating the ION memory allocator” (ang.) https://lwn.net/Articles/565469/

Zmiany w HAL

Usługa AAudio musi wiedzieć, czy ta wartość anon_inode:dmabuf jest obsługiwana. Przed Androidem 10.0 jedynym sposobem było przekazanie rozmiaru bufora MMAP jako liczby ujemnej, np. -2048 zamiast 2048, jeśli jest obsługiwane. W Androidzie 10.0 i nowszych możesz ustawić flagę AUDIO_MMAP_APPLICATION_SHAREABLE.

mmapBufferInfo |= AUDIO_MMAP_APPLICATION_SHAREABLE;

Zmiany w podsystemie audio

AAudio wymaga dodatkowej ścieżki danych na interfejsie audio podsystemu audio, aby mógł działać równolegle z pierwotną ścieżką AudioFlinger. Ten starszy szlak jest używany do wszystkich innych dźwięków systemowych i dźwięków aplikacji. Ta funkcja może być realizowana przez mikser programowy w DSP lub mikser sprzętowy w SOC.

Włączanie ścieżki danych AAudio MMAP

AAudio będzie używać starszej ścieżki danych AudioFlinger, jeśli MMAP nie jest obsługiwany lub nie uda się otworzyć strumienia. AAudio będzie działać z urządzeniem audio, które nie obsługuje ścieżki MMAP/NOIRQ.

Podczas testowania obsługi MMAP przez AAudio ważne jest, aby wiedzieć, czy testujesz ścieżkę danych MMAP, czy tylko starszą ścieżkę danych. Poniżej znajdziesz opis włączania lub wymuszania określonych ścieżek danych oraz sposobu wysyłania zapytań do ścieżki używanej przez strumień.

Właściwości systemowe

Zasady MMAP możesz skonfigurować za pomocą właściwości systemowych:

  • 1 = AAUDIO_POLICY_NEVER – używaj tylko starszego sposobu. Nie próbuj nawet używać MMAP.
  • 2 = AAUDIO_POLICY_AUTO – spróbuj użyć MMAP. Jeśli to się nie uda lub ta opcja jest niedostępna, użyj starszego sposobu.
  • 3 = AAUDIO_POLICY_ALWAYS – używaj tylko ścieżki MMAP. Nie używaj starszej ścieżki.

Można je ustawić w pliku Makefile urządzenia, na przykład w ten sposób:

# 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

Możesz też zastąpić te wartości po uruchomieniu urządzenia. Aby zmiany zaczęły obowiązywać, musisz ponownie uruchomić serwer audio. Aby na przykład włączyć tryb AUTO dla MMAP:

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

ndk/sysroot/usr/include/aaudio/AAudioTesting.h dostępne są funkcje, które umożliwiają zastąpienie zasady dotyczącej ścieżki MMAP:

aaudio_result_t AAudio_setMMapPolicy(aaudio_policy_t policy);

Aby sprawdzić, czy strumień korzysta z ścieżki MMAP, wywołaj:

bool AAudioStream_isMMapUsed(AAudioStream* stream);