xHE-AAC로 고품질의 저비트레이트 오디오 녹음

Android 17 QPR1 (API 수준 37.1 또는 Build.VERSION_CODES_FULL.CINNAMON_BUN_1)부터 Android는 확장된 고효율 고급 오디오 코딩 (xHE-AAC) 인코딩을 위한 플랫폼 지원을 도입합니다.

ISO/IEC 23003-3 (MPEG-D USAC) 및 ISO/IEC 23003-4 (MPEG-D DRC)에 정의된 xHE-AAC는 통합 음성 및 오디오 코딩을 필수 음량 정규화 및 내장 동적 범위 제어와 결합합니다. xHE-AAC 디코딩은 여러 플랫폼 버전에서 Android 호환성 정의 문서 (CDD)에 의해 지원되고 요구되었지만 xHE-AAC 인코더(c2.android.xheaac.encoder 및 OEM 하드웨어 구현)를 추가하면 기기 전반에서 우수한 음성 선명도와 일관된 볼륨으로 낮은 비트 전송률 음성 및 오디오 메시지, 라이브 오디오 스트림, 통신 페이로드를 녹음할 수 있습니다.

xHE-AAC의 이점

Android 메시지 앱의 기존 음성 녹음은 일반적으로 28kbps의 이전 AAC-LC (AAC Low Complexity)를 사용합니다. 낮은 비트 전송률 (<32kbps)에서 AAC-LC는 눈에 띄는 대역 제한 아티팩트, 흐릿한 음성 품질, 최적화되지 않은 압축 효율성을 보여줍니다. 최종 사용자의 경우 자음을 구분하기 어려운 답답하고 깡통 같은 오디오가 생성되어 청취자가 메시지를 다시 재생하거나 시끄러운 환경에서 음성을 이해하기 위해 애써야 합니다. 또한 음성 클립 간에 볼륨 수준이 일관되지 않아 사용자가 기기 볼륨을 자주 조정해야 합니다.

xHE-AAC는 더 고급 오디오 코딩 알고리즘을 적용하여 이러한 제한사항을 극복합니다. 예를 들어 전용 음성 코딩과 일반 오디오 코딩 도구 간에 동적으로 전환하는 동시에 향상된 스펙트럼 대역 복제 (eSBR)로 주파수 범위를 확장하고 추가 코딩 오버헤드가 거의 없는 파라메트릭 스테레오 코딩 기능을 제공합니다.

내장된 MPEG-D DRC는 xHE-AAC의 필수 음량 제어를 제공하여 일관된 볼륨으로 콘텐츠를 재생하고, 모든 플랫폼과 환경에서 청취할 때 최상의 사용자 환경을 제공하는 동적 범위 제어 처리를 제공합니다.

타겟 사용 사례

  • 리치 커뮤니케이션 서비스 (RCS) 및 음성 메시지: GSMA RCC.71 유니버설 프로필 버전 3.1 이상을 준수하며, 이는 낮은 비트 전송률 오디오 메시지에 xHE-AAC를 요구합니다.
  • 라이브 오디오 스트리밍 및 팟캐스트: 저하된 모바일 또는 위성 네트워크 연결을 통해 안정적인 음질을 제공합니다.

플랫폼 사용 가능 여부 (API 수준 37.1 이상)

플랫폼 xHE-AAC 인코딩 지원에는 API 수준 37.1(Build.VERSION.SDK_INT_FULL >= Build.VERSION_CODES_FULL.CINNAMON_BUN_1/SDK 3700001 이상)이 필요합니다.

xHE-AAC 인코더 지원 쿼리

"c2.android.xheaac.encoder"로 MediaCodec.createByCodecName(...)를 호출하여 모든 기기에서 xHE-AAC 인코딩을 사용할 수 있다고 가정해서는 안 됩니다.

  • 일부 기기는 플랫폼 소프트웨어 인코더가 아닌 칩셋 공급업체에서 하드웨어 가속 xHE-AAC 인코더를 제공할 수 있습니다.
  • 이전 OS 버전이나 맞춤 ROM에는 xHE-AAC 인코딩 지원이 없습니다.
  • 앱은 RCS나 채팅과 같은 P2P 메시지를 위해 첨부파일을 인코딩하기 전에 수신 클라이언트가 USAC/xHE-AAC 디코딩 지원을 광고하는지(예: SDP 기능 검색 profile-level-id=55 / object=42 사용) 확인하여 기존 수신기에서 상호 운용성 실패를 방지해야 합니다.

MediaCodecList 기능 검사

현재 기기가 xHE-AAC 인코딩을 지원하는지 검사하려면 MediaFormat.MIMETYPE_AUDIO_AAC을 지원하는 인코더의 profileLevels을 검사하세요.

Kotlin

import android.media.MediaCodecInfo.CodecProfileLevel
import android.media.MediaCodecList
import android.media.MediaFormat
import android.os.Build

/**
 * Checks if the current device supports xHE-AAC audio encoding.
 */
fun isXheAacEncodingSupported(): Boolean {
    // Verify API Level >= 37.1 (CINNAMON_BUN_1 / SDK 3700001)
    if (Build.VERSION.SDK_INT_FULL < Build.VERSION_CODES_FULL.CINNAMON_BUN_1) {
        return false
    }

    val codecList = MediaCodecList(MediaCodecList.REGULAR_CODECS)
    for (codecInfo in codecList.codecInfos) {
        if (!codecInfo.isEncoder) continue

        if (MediaFormat.MIMETYPE_AUDIO_AAC in codecInfo.supportedTypes) {
            val capabilities =
                codecInfo.getCapabilitiesForType(MediaFormat.MIMETYPE_AUDIO_AAC)
            // Check if AACObjectXHE (42) is present in profileLevels
            val supportsXhe = capabilities.profileLevels.any { profileLevel ->
                profileLevel.profile == CodecProfileLevel.AACObjectXHE
            }
            if (supportsXhe) {
                return true
            }
        }
    }
    return false
}

자바

import android.media.MediaCodecInfo;
import android.media.MediaCodecInfo.CodecProfileLevel;
import android.media.MediaCodecList;
import android.media.MediaFormat;
import android.os.Build;

/**
 * Checks if the current device supports xHE-AAC audio encoding.
 */
public boolean isXheAacEncodingSupported() {
    // Verify API Level >= 37.1 (CINNAMON_BUN_1 / SDK 3700001)
    if (Build.VERSION.SDK_INT_FULL < Build.VERSION_CODES_FULL.CINNAMON_BUN_1) {
        return false;
    }

    MediaCodecList codecList = new MediaCodecList(MediaCodecList.REGULAR_CODECS);
    for (MediaCodecInfo codecInfo : codecList.getCodecInfos()) {
        if (!codecInfo.isEncoder()) continue;

        for (String type : codecInfo.getSupportedTypes()) {
            if (MediaFormat.MIMETYPE_AUDIO_AAC.equals(type)) {
                MediaCodecInfo.CodecCapabilities capabilities =
                        codecInfo.getCapabilitiesForType(MediaFormat.MIMETYPE_AUDIO_AAC);
                for (CodecProfileLevel profileLevel : capabilities.profileLevels) {
                    if (profileLevel.profile == CodecProfileLevel.AACObjectXHE) {
                        return true;
                    }
                }
            }
        }
    }
    return false;
}

xHE-AAC 인코더 구성 및 인스턴스화

xHE-AAC 지원을 확인한 후 KEY_AAC_PROFILE이 MediaCodecInfo.CodecProfileLevel.AACObjectXHE(값 42)으로 설정된 MediaFormat를 구성합니다.

Kotlin

fun setupXheAacEncoder(): MediaCodec? {
    if (!isXheAacEncodingSupported()) {
        return null
    }

    val mimeType = MediaFormat.MIMETYPE_AUDIO_AAC
    val sampleRate = 48000
    val channelCount = 1 // Mono voice recording
    val bitRate = 20000  // 20 kbps delivers superior speech clarity

    val format = MediaFormat.createAudioFormat(mimeType, sampleRate, channelCount).apply {
        setInteger(MediaFormat.KEY_BIT_RATE, bitRate)
        setInteger(MediaFormat.KEY_AAC_PROFILE, CodecProfileLevel.AACObjectXHE)
    }

    // Find the hardware or software encoder supporting this format and profile
    val codecList = MediaCodecList(MediaCodecList.REGULAR_CODECS)
    val encoderName = codecList.findEncoderForFormat(format) ?: return null
    val encoder = MediaCodec.createByCodecName(encoderName)
    encoder.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE)
    return encoder
}

자바

public MediaCodec setupXheAacEncoder() throws IOException {
    if (!isXheAacEncodingSupported()) {
        return null;
    }

    String mimeType = MediaFormat.MIMETYPE_AUDIO_AAC;
    int sampleRate = 48000;
    int channelCount = 1; // Mono voice recording
    int bitRate = 20000;  // 20 kbps delivers superior speech clarity

    MediaFormat format = MediaFormat.createAudioFormat(mimeType, sampleRate, channelCount);
    format.setInteger(MediaFormat.KEY_BIT_RATE, bitRate);
    format.setInteger(MediaFormat.KEY_AAC_PROFILE, CodecProfileLevel.AACObjectXHE);

    // Find the hardware or software encoder supporting this format and profile
    MediaCodecList codecList =
            new MediaCodecList(MediaCodecList.REGULAR_CODECS);
    String encoderName = codecList.findEncoderForFormat(format);
    if (encoderName == null) {
        return null;
    }

    MediaCodec encoder = MediaCodec.createByCodecName(encoderName);
    encoder.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
    return encoder;
}

지원되는 소프트웨어 인코더 제한 (c2.android.xheaac.encoder)

시스템 제공 소프트웨어 xHE-AAC 인코더(c2.android.xheaac.encoder)를 사용하는 경우 구성요소는 다음 기능 제한을 공지합니다.

  • 채널 수: 최대 2개 채널 (모노 / 스테레오)
  • 샘플링 레이트: 44100 Hz, 48000 Hz
  • 비트 전송률 범위: 12,000 bps~400,000 bps (12kbps~400kbps)
  • 컨테이너 멀티플렉싱: 원시 MP4A 기본 스트림 및 플랫폼 MP4/M4A 컨테이너 멀티플렉싱(MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4)과 호환됩니다.