AAudio

AAudio는 Android O 릴리스에 도입된 새로운 Android C API입니다. 이것은 필요한 지연 시간이 짧은 고성능 오디오 애플리케이션용으로 고안되었습니다. 앱은 스트림에 데이터를 읽고 써서 AAudio와 통신합니다.

AAudio API는 일부러 최소한의 수준으로 만들었으므로 다음과 같은 기능은 수행하지 않습니다.

  • 오디오 기기 열거
  • 오디오 엔드포인트간 자동 라우팅
  • 파일 I/O
  • 압축 오디오 디코딩
  • 콜백 하나의 모든 입력/스트림의 자동 표시.

오디오 스트림

AAudio는 사용자의 앱과 사용자의 Android 기기 오디오 입력 및 출력 사이를 오고 가며 오디오 데이터를 이동합니다. 앱은 오디오 스트림에서 데이터를 읽거나 여기에 데이터를 써서 이러한 데이터를 안팎으로 전달합니다. 이를 대표하는 것이 구조 AAudioStream입니다. 읽기/쓰기 호출은 블로킹일 수도 있고 비블로킹일 수도 있습니다.

스트림이란 다음과 같이 정의됩니다.

  • 스트림 내 데이터의 소스 또는 싱크인 오디오 기기.
  • 주어진 스트림이 어느 오디오 기기에 대한 독점적 액세스 권한이 있는지 판별하는 공유 모드로, 이러한 권한이 없는 경우 해당 기기는 복수의 스트림이 공유할 수 있음.
  • 스트림 내 오디오 데이터의 형식.

오디오 기기

각 스트림은 하나의 오디오 기기에 연결되어 있습니다.

오디오 기기란 하드웨어 인터페이스 또는 가상의 엔드포인트를 말하며, 이것은 디지털 오디오 데이터의 지속적인 스트림에 대한 소스 또는 싱크 역할을 합니다. 오디오 기기(내장 마이크 또는 블루투스 헤드셋)를 앱을 실행하는 Android 기기(전화 또는 워치)와 혼동해서는 안 됩니다.

AudioManager 방법의 getDevices()를 사용하여 Android 기기에서 사용할 수 있는 오디오 기기를 찾을 수 있습니다. 이 방법은 각 기기의 type 정보를 반환합니다.

각 오디오 기기에는 Android 기기에서의 고유 ID가 있습니다. 이 ID를 사용하여 오디오 스트림을 특정 오디오 기기에 바인딩할 수 있습니다. 다만 대부분의 경우 사용자가 직접 지정하기보다 AAudio에 기본 설정된 주 기기를 선택하도록 두면 됩니다.

스트림에 연결된 오디오 기기는 스트림이 출력용인지 입력용인지 결정합니다. 스트림은 데이터를 한 방향으로만 옮길 수 있습니다. 스트림을 정의하면 그 방향도 설정하게 됩니다. 스트림을 열면 Android가 오디오 기기와 스트림 방향이 일치하는지 확인합니다.

공유 모드

스트림에는 공유 모드가 있습니다.

  • AAUDIO_SHARING_MODE_EXCLUSIVE란 스트림이 오디오 기기에 독점 접근할 수 있다는 것을 의미합니다. 이 기기는 다른 오디오 스트림으로 사용할 수 없습니다. 오디오 기기가 이미 사용 중이라면 스트림에 독점 접근이 불가능할 수 있습니다. 독점 스트림은 지연 속도가 낮지만, 연결이 해제되기 쉽습니다. 독점 스트림이 필요하지 않게 되면 바로 닫아 다른 앱이 해당 기기에 접근할 수 있도록 해야 합니다. 독점 스트림은 가장 짧은 지연 시간을 제공합니다.
  • AAUDIO_SHARING_MODE_SHARED를 통해 AAudio가 오디오를 믹싱할 수 있습니다. AAudio는 동일한 기기에 할당된 모든 공유 스트림을 믹싱합니다.

스트림을 생성할 때 공유 모드를 명시적으로 설정할 수 있습니다. 공유 모드의 기본값은 SHARED입니다.

오디오 형식

스트림을 통해 전달된 데이터는 일반적인 디지털 오디오 속성을 보유합니다. 이는 다음과 같습니다.

  • 샘플 형식
  • 프레임당 샘플
  • 샘플링 레이트

AAudio는 다음과 같은 샘플 형식을 허용합니다.

aaudio_format_t C 데이터 유형 참고
AAUDIO_FORMAT_PCM_I16 int16_t 일반 16비트 샘플, Q0.15 형식
AAUDIO_FORMAT_PCM_FLOAT float -1.0 to +1.0

AAudio는 자체적으로 샘플 변환을 수행할 수 있습니다. 예를 들어, 앱이 FLOAT 데이터를 작성하지만 HAL은 PCM_I16을 사용하는 경우 AAudio가 샘플을 자동으로 변환할 수 있습니다. 변환은 어느 방향으로든 수행할 수 있습니다. 앱이 오디오 입력을 처리하는 경우 입력 형식을 확인한 다음 필요한 경우 데이터를 변환할 준비를 하는 것이 현명한 일입니다(다음 예시처럼).

aaudio_format_t dataFormat = AAudioStream_getDataFormat(stream);
//... later
if (dataFormat == AAUDIO_FORMAT_PCM_I16) {
     convertFloatToPcm16(...)
}

오디오 스트림 생성

AAudio 라이브러리는 빌더 디자인 패턴을 따르며, AAudioStreamBuilder를 제공합니다.

  1. AAudioStreamBuilder 생성:

    AAudioStreamBuilder *builder;
    aaudio_result_t result = AAudio_createStreamBuilder(&builder);
    

  2. 스트림 매개변수에 해당하는 빌더 함수를 사용하여 이 빌더에 오디오 스트림 구성을 설정합니다. 다음과 같은 선택적 설정 함수를 이용할 수 있습니다.

    AAudioStreamBuilder_setDeviceId(builder, deviceId);
    AAudioStreamBuilder_setDirection(builder, direction);
    AAudioStreamBuilder_setSharingMode(builder, mode);
    AAudioStreamBuilder_setSampleRate(builder, sampleRate);
    AAudioStreamBuilder_setChannelCount(builder, channelCount);
    AAudioStreamBuilder_setFormat(builder, format);
    AAudioStreamBuilder_setBufferCapacityInFrames(builder, frames);
    

    참고로 이러한 방법을 정의되지 않은 상수 또는 범위를 벗어난 값 등의 오류를 보고하지 않습니다.

    deviceId를 지정하지 않으면 기본값은 주 출력 기기가 됩니다. 스트림 방향을 지정하지 않으면 기본값은 출력 스트림이 됩니다. 모든 기타 매개변수에 대해 값을 명시적으로 설정하거나, 매개변수를 전혀 설정하지 않거나 AAUDIO_UNSPECIFIED로 설정하여 시스템이 최적의 값을 할당하도록 할 수 있습니다.

    만약을 위해 아래 4단계에서 설명한 것처럼 오디오 스트림을 생성한 후 그 상태를 확인합니다.

  3. AAudioStreamBuilder가 구성되면 이를 사용하여 스트림을 생성합니다.

    AAudioStream *stream;
    result = AAudioStreamBuilder_openStream(builder, &stream);
    

  4. 스트림을 생성한 후, 그 구성을 확인합니다. 샘플 형식, 샘플링 레이트 또는 프레임당 샘플을 지정한 경우 그 값은 변하지 않습니다. 공유 모드 또는 버퍼 용량을 지정한 경우, 이들은 스트림의 오디오 기기 및 이를 실행하는 Android 기기의 성능에 따라 변경될 수 있습니다. 모범적인 방어적 프로그래밍이라는 차원에서, 스트림의 구성을 점검한 뒤에 사용하는 것이 좋습니다. 각 빌더 설정에 상응하는 스트림 설정을 가져오는 함수가 있습니다.

    AAudioStreamBuilder_setDeviceId() AAudioStream_getDeviceId()
    AAudioStreamBuilder_setDirection() AAudioStream_getDirection()
    AAudioStreamBuilder_setSharingMode() AAudioStream_getSharingMode()
    AAudioStreamBuilder_setSampleRate() AAudioStream_getSampleRate()
    AAudioStreamBuilder_setChannelCount() AAudioStream_getChannelCount()
    AAudioStreamBuilder_setFormat() AAudioStream_getFormat()
    AAudioStreamBuilder_setBufferCapacityInFrames() AAudioStream_getBufferCapacityInFrames()
  5. 빌더는 저장했다가 나중에 더 많은 스트림을 만들 때 재사용해도 됩니다. 다만 더 사용할 계획이 없다면 삭제하는 것이 좋습니다.

    AAudioStreamBuilder_delete(builder);
    

오디오 스트림 사용하기

상태 전환

AAudio 스트림은 대개 다음과 같은 다섯 가지 안정 상태 중 하나입니다(오류 상태인 '연결 끊김'은 이 섹션의 마지막에 설명함).

  • 열림
  • 시작됨
  • 일시 중지됨
  • 플러시됨
  • 중지됨

데이터는 스트림이 시작됨 상태에 있는 경우에만 스트림을 통해 전달됩니다. 스트림의 상태를 변경하려면, 상태 전환을 요청하는 다음 함수 중 하나를 사용하세요.

aaudio_result_t result;
result = AAudioStream_requestStart(stream);
result = AAudioStream_requestStop(stream);
result = AAudioStream_requestPause(stream);
result = AAudioStream_requestFlush(stream);

참고로 출력 스트림에는 일시 중지 또는 플러시만 요청할 수 있습니다.

이러한 함수는 비동기이며, 상태 변화가 즉시 발생하지 않습니다. 상태 변화를 요청할 때 스트림은 각각 상응하는 일시적인 상태 중 하나로 이동합니다.

  • 시작 중
  • 일시 중지 중
  • 플러싱 중
  • 중지 중
  • 종료 중

아래의 상태 다이어그램에서는 안정 상태를 모서리가 둥근 직사각형, 일시적 상태를 점선 직사각형으로 표시했습니다. 그림에 표시되어 있지는 않지만 close()는 모든 상태에서 호출할 수 있습니다.

AAudio 수명

AAudio는 상태 변화를 알리는 콜백을 제공하지 않습니다. 특수 함수인 AAudioStream_waitForStateChange(stream, inputState, nextState, timeout)를 사용하여 상태 변경을 기다릴 수 있습니다.

이 함수는 자체적으로 상태 변경을 감지하지 않으며, 특정 상태를 기다리는 것이 아닙니다. 다만 현재의 상태가 inputState다를 때를 기다리며, 이는 사용자가 지정합니다.

예를 들어 일시 중지를 요청한 뒤에는 스트림이 즉시 일시 중지 중이라는 일시적 상태에 진입하고, 어느 정도 시간이 흐른 뒤 일시 중지됨 상태에 도달합니다(꼭 그렇게 된다는 보장은 없음). 일시 중지됨 상태를 기다릴 수 없으므로 waitForStateChange()를 사용하여 일시 중지 중 외의 모든 상태를 기다리는 것입니다. 그 방법은 다음과 같습니다.

aaudio_stream_state_t inputState = AAUDIO_STREAM_STATE_PAUSING;
aaudio_stream_state_t nextState = AAUDIO_STREAM_STATE_UNINITIALIZED;
int64_t timeoutNanos = 100 * AAUDIO_NANOS_PER_MILLISECOND;
result = AAudioStream_requestPause(stream);
result = AAudioStream_waitForStateChange(stream, inputState, &nextState, timeoutNanos);

스트림의 상태가 일시 중지 중(호출 시 inputState가 현재 상태라고 가정)이 아닌 경우, 이 함수는 즉시 반환됩니다. 그렇지 않으면, 함수는 더 이상 일시 중지 중 상태가 아니거나 제한시간이 만료될 때까지 차단합니다. 함수가 반환되면 매개변수 nextState이 스트림의 현재 상태를 나타냅니다.

요청 시작, 중지, 플러시를 호출한 후 해당 일시적 상태를 inputState로 이용하여 동일한 기법을 사용할 수 있습니다. AAudioStream_close()를 호출한 후 waitForStateChange()를 호출하지 말아야 합니다. 스트림이 닫히자마자 삭제되기 때문입니다. 또한, waitForStateChange()가 다른 스레드에서 실행 중일 때 AAudioStream_close()를 호출해서도 안 됩니다.

오디오 스트림 읽기 및 쓰기

스트림이 시작된 후, AAudioStream_read(stream, buffer, numFrames, timeoutNanos)AAudioStream_write(stream, buffer, numFrames, timeoutNanos)함수를 사용하여 읽거나 쓸 수 있습니다.

특정 프레임 수를 전송하는 블로킹 읽기 또는 블로킹 쓰기의 경우, timeoutNanos를 0보다 크게 설정합니다. 비블로킹 호출의 경우, timeoutNanos를 0으로 설정합니다. 이 경우 결과는 전송된 실제 프레임 수입니다.

입력을 읽을 때, 올바른 프레임 수가 읽혔는지 확인해야 합니다. 그렇지 않은 경우, 버퍼에 알 수 없는 데이터가 포함되어 있어 오디오 결함을 초래할 수 있습니다. 0으로 버퍼를 보완하여 자동 드롭아웃을 생성할 수 있습니다.

aaudio_result_t result =
    AAudioStream_read(stream, audioData, numFrames, timeout);
if (result < 0) {
  // Error!
}
if (result != numFrames) {
  // pad the buffer with zeros
  memset(static_cast<sample_type*>(audioData) + result * samplesPerFrame, 0,
      sizeof(sample_type) * (numFrames - result) * samplesPerFrame);
}

스트림을 시작하기 전에 여기에 데이터 또는 무음을 작성하여 스트림 버퍼에 대비할 수 있습니다. 이 작업은 timeoutNanos가 0으로 설정된 비블로킹 호출에서 이루어져야 합니다.

버퍼 내의 데이터는 반드시 AAudioStream_getDataFormat()가 반환한 데이터 형식과 일치해야 합니다.

오디오 스트림 종료

스트림 사용이 완료되면 스트림을 종료합니다.

AAudioStream_close(stream);

스트림을 종료하고 나면 이것을 AAudio 스트림 기반 함수로 사용할 수 없습니다.

오디오 스트림 연결 끊김

다음 이벤트 중 하나가 발생하면 언제라도 오디오 스트림의 연결이 끊길 수 있습니다.

  • 관련 오디오 기기가 더 이상 연결되어 있지 않습니다(예: 헤드폰을 뽑았을 때).
  • 내부에서 오류가 발생했습니다.
  • 오디오 기기가 더 이상 주 오디오 기기가 아닙니다.

스트림의 연결이 끊기면 "연결 끊김" 상태가 되며 write() 또는 AAUDIO_ERROR_DISCONNECTED를 반환하는 기타 함수 실행을 시도합니다. 스트림의 연결이 끊기면 가능한 작업은 스트림을 종료하는 것뿐입니다.

오디오 기기의 연결이 끊겼을 때 알림을 받고 싶다면, AAudioStream_errorCallback 함수를 작성하고 AAudioStreamBuilder_setErrorCallback()을 사용하여 등록하세요.

이 콜백은 다음 예시에 표시된 것처럼 스트림의 상태를 확인합니다. 스트림을 콜백에서 닫거나 다시 열지 말고, 다른 스레드를 사용해야 합니다. 새롭게 연 스트림은 원래 스트림(예: framesPerBurst)과 다른 특성을 가질 수 있습니다.

void errorCallback(AAudioStream *stream,
                   void *userData,
                   aaudio_result_t error){

  aaudio_stream_state_t streamState = AAudioStream_getState(stream);
  if (streamState == AAUDIO_STREAM_STATE_DISCONNECTED){
    // Handle stream disconnect on a separate thread
    ...
  }
}

성능 최적화

오디오 애플리케이션의 성능을 최적화하려면 내부 버퍼를 조정하고, 특수하고 우선순위가 높은 스레드를 사용하면 됩니다.

버퍼 조정을 통한 지연 시간 최소화

AAudio는 자신이 유지관리하는 내부 버퍼 안팎으로 데이터를 전달합니다. 내부 버퍼는 각 오디오 기기당 하나씩입니다.

참고: AAudio의 내부 버퍼를 AAudio 스트림 읽기 및 쓰기 함수의 버퍼 매개변수와 혼동해서는 안 됩니다.

버퍼의 용량은 버퍼가 유지할 수 있는 데이터의 총량입니다. AAudioStreamBuilder_setBufferCapacityInFrames()을 호출하여 용량을 설정할 수 있습니다. 이 방식은 기기에서 허용하는 최대 값에 할당할 수 있는 용량을 제한합니다. AAudioStream_getBufferCapacityInFrames()를 이용하여 버퍼의 실제 용량을 확인하세요.

앱은 버퍼의 전체 용량을 사용하지 않아도 됩니다. AAudio는 일정한 크기까지 버퍼를 채우는데, 이 크기는 사용자가 설정할 수 있습니다. 버퍼의 크기는 그 용량보다 크면 안 되고, 그보다 작은 경우가 많습니다. 버퍼 크기를 조절하면 이를 채우기 위해 필요한 버스트의 수를 결정할 수 있고, 나아가 지연 시간을 제어할 수 있습니다. AAudioStreamBuilder_setBufferSizeInFrames()AAudioStreamBuilder_getBufferSizeInFrames() 메서드를 사용하여 버퍼 크기를 다룹니다.

애플리케이션은 오디오를 재생할 때 버퍼에 쓰기 작업을 하며 쓰기가 완료될 때까지 차단합니다. AAudio는 개별 버스트의 버퍼에서 읽습니다. 각각의 버스트에는 여러 개의 오디오 프레임이 포함되며 대체로 읽히고 있는 버퍼보다 크기가 작습니다. 버스트 크기와 속도는 시스템이 제어하는데, 이들 속성은 대개 오디오 기기의 회로에 따라 다릅니다. 버스트의 크기나 버스트 속도를 사용자가 변경할 수는 없지만, 그에 포함된 버스트의 수에 따라 내부 버퍼의 크기를 설정할 수는 있습니다. 일반적으로, 가장 낮은 지연 시간을 얻으려면 AAudioStream의 버퍼 크기를 보고된 버스트 크기의 배수로 설정하면 됩니다.

      AAudio 버퍼링

버퍼 크기를 최적화하는 한 가지 방법으로 일단 큰 버퍼에서 시작하여 점차 크기를 줄여서 언더런이 시작되도록 한 다음 다시 조금씩 이동하여 크기를 늘리는 것이 있습니다. 아니면 작은 크기의 버퍼로 시작한 다음 여기서 언더런이 발생하면 버퍼 크기를 출력이 다시 깨끗하게 흐를 때까지 늘리는 것도 방법입니다.

이 프로세스는 사용자가 첫 사운드를 재생하기도 전에 매우 빠르게 일어날 수 있습니다. 첫 버퍼 크기 조정을 silence를 사용하여 먼저 수행하는 것이 좋습니다. 이렇게 하면 사용자가 오디오 결함을 듣지 않도록 방지할 수 있기 때문입니다. 시스템 성능은 시간이 지남에 따라 변화할 수 있습니다(예: 사용자가 비행기 모드를 끌 수 있음). 버퍼 조정은 매우 미세한 오버헤드를 추가하기 때문에, 앱이 데이터를 스트림에 읽거나 쓰는 동안 계속 수행할 수 있습니다.

다음은 버퍼 최적화 루프의 예시입니다.

int32_t previousUnderrunCount = 0;
int32_t framesPerBurst = AAudioStream_getFramesPerBurst(stream);
int32_t bufferSize = AAudioStream_getBufferSizeInFrames(stream);

int32_t bufferCapacity = AAudioStream_getBufferCapacityInFrames(stream);

while (go) {
    result = writeSomeData();
    if (result < 0) break;

    // Are we getting underruns?
    if (bufferSize < bufferCapacity) {
        int32_t underrunCount = AAudioStream_getXRunCount(stream);
        if (underrunCount > previousUnderrunCount) {
            previousUnderrunCount = underrunCount;
            // Try increasing the buffer size by one burst
            bufferSize += framesPerBurst;
            bufferSize = AAudioStream_setBufferSize(stream, bufferSize);
        }
    }
}

이 기법을 사용해서 입력 스트림에 대해 버퍼 크기를 최적화해도 큰 장점은 없습니다. 입력 스트림은 가능한 한 빨리 실행되어 버퍼링된 데이터의 양을 최소한의 수준으로 유지하려 하고, 앱이 선점되면 채우려 하기 때문입니다.

높은 우선순위 콜백 사용

앱이 일반적인 스레드에서 오디오 데이터를 읽거나 쓰는 경우, 이것은 선점되거나 타이밍 잡음을 경험할 수 있습니다. 이 때문에 오디오 결함이 발생할 수 있습니다. 큰 버퍼를 사용하면 그러한 결함을 방지할 수 잇지만, 큰 버퍼는 오디오 지연 시간을 늘리기도 합니다. 짧은 지연 시간이 필요한 애플리케이션의 경우, 오디오 스트림이 비동기 콜백 함수를 사용하여 데이터를 앱으로 전달하거나 가져올 수 있습니다. AAudio는 더 나은 성능을 갖춘 더 높은 우선순위 스레드의 콜백을 실행합니다.

이 콜백 함수에는 다음과 같은 프로토타입이 있습니다.

typedef aaudio_data_callback_result_t (*AAudioStream_dataCallback)(
        AAudioStream *stream,
        void *userData,
        void *audioData,
        int32_t numFrames);

이 스트림 빌드를 사용하여 콜백을 등록합니다.

AAudioStreamBuilder_setDataCallback(builder, myCallback, myUserData);

가장 간단한 경우, 이 스트림은 다음 버스트의 데이터를 획득하기 위해 주기적으로 콜백 함수를 실행합니다.

이 콜백 함수는 자신을 호출한 스트림에 읽기나 쓰기를 수행할 수 없습니다. 해당 콜백이 입력 스트림에 포함된 경우, 코드는 audioData 버퍼에 공급된 데이터를 처리해야 합니다(3번째 인수로 지정됨). 콜백이 출력 스트림에 포함된 경우, 코드는 데이터를 버퍼 내에 배치해야 합니다.

예를 들어, 콜백을 사용하여 다음과 같이 사인파 출력을 계속해서 생성할 수 있습니다.

aaudio_data_callback_result_t myCallback(
        AAudioStream *stream,
        void *userData,
        void *audioData,
        int32_t numFrames) {
    int64_t timeout = 0;

    // Write samples directly into the audioData array.
    generateSineWave(static_cast<float *>(audioData), numFrames);
    return AAUDIO_CALLABCK_RESULT_CONTINUE;
}

AAudio를 사용하여 하나 이상의 스트림을 처리할 수 있습니다. 하나의 스트림을 마스터로 사용하고, 포인터를 사용자 데이터 내 다른 스트림으로 전달할 수 있습니다. 콜백을 마스터 스트림에 등록합니다. 그런 다음 다른 스트림에 비블로킹 I/O를 사용합니다. 다음은 입력 스트림에서 출력 스트림으로 전달하는 왕복 콜백의 예시입니다. 마스터 호출 스트림은 출력 스트림입니다. 입력 스트림은 사용자 데이터 내에 포함되어 있습니다.

이 콜백은 출력 스트림의 버퍼에 데이터를 배치하는 입력 스트림 데이터에서 비블로킹 읽기를 수행합니다.

aaudio_data_callback_result_t myCallback(
        AAudioStream *stream,
        void *userData,
        void *audioData,
        int32_t numFrames) {
    AAudioStream *inputStream = (AAudioStream *) userData;
    int64_t timeout = 0;
    aaudio_result_t result =
        AAudioStream_read(inputStream, audioData, numFrames, timeout);

  if (result == numFrames)
      return AAUDIO_CALLABCK_RESULT_CONTINUE;
  if (result >= 0) {
      memset(static_cast<sample_type*>(audioData) + result * samplesPerFrame, 0,
          sizeof(sample_type) * (numFrames - result) * samplesPerFrame);
      return AAUDIO_CALLBACK_RESULT_CONTINUE;
  }
  return AAUDIO_CALLBACK_RESULT_STOP;
}

참고로 이 예시에서는 입력과 출력 스트림의 채널, 형식과 샘플링 레이트 수치가 같다고 가정하였습니다. 스트림의 형식은 코드가 변환을 올바르게 처리하는 한 서로 일치하지 않을 수 있습니다.

성능 모드 설정

모든 AAudioStream에는 앱의 동작에 큰 영향을 미치는 성능 모드가 있습니다. 모드는 모두 세 가지입니다.

  • AAUDIO_PERFORMANCE_MODE_NONE이 기본 모드입니다. 이 모드는 지연 속도와 절전의 균형을 이룬 기본 스트림을 사용합니다.
  • AAUDIO_PERFORMANCE_MODE_LOW_LATENCY는 작은 버퍼와 최적화된 데이터 경로를 사용하여 지연 시간을 단축합니다.
  • AAUDIO_PERFORMANCE_MODE_POWER_SAVING는 큰 내부 버퍼와 데이터 경로를 사용하여 지연 시간 면에서 타협하는 대신 전력을 절감합니다.

setPerformanceMode()를 호출하여 성능 모드를 선택할 수 있으며, 현재 모드는 getPerformanceMode()를 호출하여 발견할 수 있습니다.

애플리케이션에 짧은 지연 시간이 절전보다 더 중요하다면 AAUDIO_PERFORMANCE_MODE_LOW_LATENCY를 사용하세요. 이 모드는 게임이나 키보드 신디사이저와 같이 상호작용이 많은 앱에 유용합니다.

애플리케이션에 전력 절감보다 짧은 지연 시간이 중요하다면, AAUDIO_PERFORMANCE_MODE_POWER_SAVING을 사용하세요. 이 모드는 오디오 또는 MIDI 파일 플레이어 스트리밍과 같이 이전에 생성한 음악을 재생하는 앱에 일반적입니다.

현재 AAudio 버전에서 가능한 한 짧은 지연 시간을 달성하려면, 높은 우선순위 콜백과 함께 AAUDIO_PERFORMANCE_MODE_LOW_LATENCY 성능 모드를 사용해야 합니다. 다음 예를 따라해 보세요.

// Create a stream builder
AAudioStreamBuilder *streamBuilder;
AAudio_createStreamBuilder(&streamBuilder);
AAudioStreamBuilder_setDataCallback(streamBuilder, dataCallback, nullptr);
AAudioStreamBuilder_setPerformanceMode(streamBuilder, AAUDIO_PERFORMANCE_MODE_LOW_LATENCY);

// Use it to create the stream
AAudioStream *stream;
AAudioStreamBuilder_openStream(streamBuilder, &stream);

스레드 안전

AAudio API는 완전히 스레드에 안전하지 않습니다. 즉 일부 AAudio 함수는 한 번에 한 개 이상의 스레드에서 동시에 호출할 수 없습니다. 이는 AAudio가 스레드 선점이나 결함을 유발할 수 있는 뮤텍스 사용을 피하기 때문입니다.

만약을 위해, AAudioStream_waitForStateChange()를 호출하지 않거나 두 가지 다른 스레드에서 동일한 스트림에 읽기 또는 쓰기를 수행하지 말아야 합니다. 마찬가지로 한 스레드에서 읽기 또는 쓰기를 수행하는 중에 다른 스레드의 스트림을 닫지 말아야 합니다.

AAudioStream_getSampleRate()AAudioStream_getChannelCount()와 같은 스트림 설정을 반환하는 호출은 스레드에 안전합니다.

다음과 같은 호출도 스레드에 안전합니다.

  • AAudio_convert*ToText()
  • AAudio_createStreamBuilder()
  • AAudioStream_get*() 예외: AAudioStream_getTimestamp()

참고: 스트림이 콜백 함수를 사용하는 경우, 실행 중인 스레드의 스트림을 닫으면서 콜백 스레드에서 읽기/쓰기를 수행해도 됩니다.

코드 샘플

Google의 GitHub 페이지에서 두 개의 작은 AAudio 데모 앱을 이용할 수 있습니다.

  • Hello-Audio는 사인파를 생성하고 오디오를 재생합니다.
  • Echo는 입출력 왕복 오디오 루프를 구현하는 방법을 보여줍니다. 이 앱은 두 개의 스트림을 동기화하는 콜백 함수를 사용하여 지속적인 버퍼 조정을 수행합니다.

최적의 출력 지연 시간을 달성하고 OpenSL ES를 사용하여 오디오 글리치를 예방하는 방법에 관한 자세한 정보는 Simple Synth를 확인하세요.

알려진 문제

  • blocking write()의 경우, Android O DP2 릴리스가 FAST 트랙을 사용하지 않기 때문에 오디오 지연 시간이 깁니다. 콜백을 사용하여 지연 시간을 줄이세요.