AAudio 및 MMAP

AAudio는 Android 8.0 버전에서 도입된 오디오 API입니다. Android 8.1 출시에서는 MMAP를 지원하는 HAL 및 드라이버와 함께 사용하는 경우 지연을 줄일 수 있도록 개선되었습니다. 이 문서에서는 Android에서 AAudio의 MMAP 기능을 지원하는 데 필요한 하드웨어 추상화 계층(HAL) 및 드라이버 변경사항을 설명합니다.

AAudio MMAP 지원은 다음을 필요로 합니다.

  • HAL의 MMAP 기능 보고
  • HAL에서 새 함수 구현
  • 선택적으로 EXCLUSIVE 모드 버퍼를 위한 맞춤 ioctl() 구현
  • 추가 하드웨어 데이터 경로 제공
  • MMAP 기능을 사용 설정하는 시스템 속성 설정

AAudio 아키텍처

AAudio는 Open SL ES 대신 사용할 수 있는 새로운 네이티브 C API입니다. 빌더 디자인 패턴을 사용하여 오디오 스트림을 만듭니다.

AAudio는 지연 시간이 짧은 데이터 경로를 제공합니다. 이 기능을 사용하면 EXCLUSIVE 모드에서 클라이언트 애플리케이션 코드가 ALSA 드라이버와 공유되는 메모리 매핑된 버퍼에 직접 쓸 수 있습니다. SHARED 모드에서 MMAP 버퍼는 AudioServer에서 실행되는 믹서에 의해 사용됩니다. EXCLUSIVE 모드에서는 데이터가 믹서를 우회하기 때문에 지연 시간이 현저하게 줄어듭니다.

EXCLUSIVE 모드에서 서비스는 HAL로부터 MMAP 버퍼를 요청하고 리소스를 관리합니다. MMAP 버퍼는 NOIRQ 모드에서 실행되므로 버퍼에 관한 액세스를 관리하기 위한 공유 읽기/쓰기 카운터가 없습니다. 대신 클라이언트는 하드웨어의 타이밍 모델을 유지하고 버퍼를 읽을 시점을 예측합니다.

아래 다이어그램에서 MMAP FIFO를 통해 ALSA 드라이버로 전달되는 PCM(Pulse-Code Modulation) 데이터를 확인할 수 있습니다. 타임스탬프는 AAudio 서비스에 의해 주기적으로 요청된 후 원자 메시지 대기열을 통해 클라이언트의 타이밍 모델로 전달됩니다.

PCM 데이터 흐름 다이어그램
그림 1. FIFO를 통해 ALSA로 가는 PCM 데이터 흐름

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)로 설정하여 오디오 프레임워크가 오디오 HAL에서 MMAP 모드를 지원한다는 것을 인식하도록 해야 합니다. 아래의 'AAudio MMAP 데이터 경로 사용 설정'을 참조하세요.

audio_policy_configuration.xml 파일에는 MMAP 클라이언트가 생성될 때 오디오 정책 관리자가 어떤 스트림이 열릴지 인식할 수 있도록 MMAP/NO IRQ 모드에 관한 출력 및 입력 프로필도 포함되어야 합니다.

<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 위치 쿼리

타이밍 모델에 다시 전달된 타임스탬프에는 프레임 위치와 나노초 단위의 MONOTONIC 시간이 포함됩니다.

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

HAL은 새 Tinyalsa 함수를 호출하여 ALSA 드라이버에서 이 정보를 가져올 수 있습니다.

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

공유 메모리의 파일 설명자

AAudio MMAP 데이터 경로는 하드웨어와 오디오 서비스 간에 공유되는 메모리 영역을 사용합니다. 공유 메모리는 ALSA 드라이버에서 생성된 파일 설명자를 사용하여 참조됩니다.

커널 변경사항

파일 설명자가 /dev/snd/ 드라이버 파일과 직접 연결된 경우 SHARED 모드로 AAudio 서비스에서 사용할 수 있습니다. 그러나 설명자는 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 데이터 경로 사용 설정

MMAP가 지원되지 않거나 스트림을 열지 못하는 경우 AAudio는 레거시 AudioFlinger 데이터 경로를 사용합니다. 이에 따라 AAudio는 MMAP/NOIRQ 경로를 지원하지 않는 오디오 기기에서 작동합니다.

AAudio에 관한 MMAP 지원을 테스트할 때는 실제로 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

MMAP 경로를 사용하기 위해 정책을 재정의할 수 있도록 ndk/sysroot/usr/include/aaudio/AAudioTesting.h에서 함수가 제공됩니다.

aaudio_result_t AAudio_setMMapPolicy(aaudio_policy_t policy);

스트림이 MMAP 경로를 사용하는지 확인하려면 다음을 호출합니다.

bool AAudioStream_isMMapUsed(AAudioStream* stream);