Nagrywanie dźwięku w wysokiej jakości przy niskiej przepływności dzięki xHE-AAC

Od Androida 17 QPR1 (poziom interfejsu API 37.1 lubBuild.VERSION_CODES_FULL.CINNAMON_BUN_1) Android wprowadza obsługę platformy dla kodowania Extended High Efficiency Advanced Audio Coding (xHE-AAC).

Zdefiniowany w normach ISO/IEC 23003-3 (MPEG-D USAC) i ISO/IEC 23003-4 (MPEG-D DRC) kodek xHE-AAC łączy ujednolicone kodowanie mowy i dźwięku z obowiązkową normalizacją głośności i wbudowaną kontrolą zakresu dynamicznego. Dekodowanie xHE-AAC było obsługiwane i wymagane przez dokument definicji zgodności (CDD) Androida w wielu wersjach platformy. Dodanie enkodera xHE-AAC (c2.android.xheaac.encoder i implementacje sprzętowe OEM) umożliwia nagrywanie wiadomości głosowych i audio o niskiej przepływności, strumieni audio na żywo oraz pakietów danych komunikacyjnych z doskonałą czystością mowy i stałą głośnością na różnych urządzeniach.

Zalety xHE-AAC

Tradycyjne nagrywanie głosu w aplikacjach do obsługi wiadomości na Androidzie zwykle wykorzystuje starszy kodek AAC-LC (AAC Low Complexity) przy 28 kbps. Przy niskich szybkościach transmisji (<32 kb/s) kodek AAC-LC wykazuje zauważalne artefakty ograniczające pasmo, niewyraźną jakość mowy i nieoptymalną wydajność kompresji. Użytkownicy końcowi słyszą stłumiony, cienki dźwięk, w którym trudno odróżnić spółgłoski. Muszą oni ponownie odtwarzać wiadomości lub wytężać słuch, aby zrozumieć mowę w głośnym otoczeniu. Poza tym niespójne poziomy głośności w klipach głosowych zmuszają użytkowników do częstego dostosowywania głośności urządzenia.

xHE-AAC pokonuje te ograniczenia, stosując bardziej zaawansowane algorytmy kodowania dźwięku, np. dynamiczne przełączanie między dedykowanym kodowaniem mowy a ogólnymi narzędziami do kodowania dźwięku, przy jednoczesnym rozszerzaniu zakresu częstotliwości za pomocą ulepszonej replikacji pasma widma (eSBR) i zapewnianiu możliwości parametrycznego kodowania stereo przy bardzo małym dodatkowym obciążeniu kodowania.

Wbudowany MPEG-D DRC zapewnia obowiązkową kontrolę głośności w przypadku xHE-AAC, aby odtwarzać treści z równomierną głośnością, oraz oferuje przetwarzanie kontroli zakresu dynamicznego, aby zapewnić użytkownikom jak najlepsze wrażenia podczas słuchania na dowolnej platformie i w dowolnym środowisku.

Docelowe przypadki użycia

  • Usługi RCS (Rich Communication Services) i wiadomości głosowe: zgodne z GSMA RCC.71 Universal Profile w wersji 3.1 lub nowszej, która wymaga używania kodeka xHE-AAC w przypadku wiadomości audio o niskiej przepływności.
  • Strumieniowanie dźwięku na żywo i podcasty: zapewnia wysoką jakość dźwięku nawet przy słabym połączeniu z siecią komórkową lub satelitarną.

Dostępność platformy (interfejs API na poziomie 37.1 lub wyższym)

Obsługa kodowania xHE-AAC na platformie wymaga interfejsu API na poziomie 37.1 (Build.VERSION.SDK_INT_FULL >= Build.VERSION_CODES_FULL.CINNAMON_BUN_1 / pakietu SDK 3700001 lub nowszego).

Zapytanie o obsługę kodera xHE-AAC

Nie możesz zakładać, że kodowanie xHE-AAC jest dostępne na każdym urządzeniu, wywołując MediaCodec.createByCodecName(...) z parametrem "c2.android.xheaac.encoder".

  • Niektóre urządzenia mogą udostępniać akcelerowany sprzętowo koder xHE-AAC od dostawcy chipsetu zamiast kodera oprogramowania platformy.
  • Starsze wersje systemu operacyjnego lub dostosowane ROM-y nie obsługują kodowania xHE-AAC.
  • Przed zakodowaniem załącznika do wiadomości peer-to-peer (np. RCS lub czatu) aplikacje powinny też sprawdzić, czy klient odbierający reklamuje obsługę dekodowania USAC/xHE-AAC (np. za pomocą wykrywania możliwości SDP profile-level-id=55 / object=42), aby zapobiec problemom z interoperacyjnością na starszych odbiornikach.

Sprawdzanie możliwości MediaCodecList

Aby sprawdzić, czy bieżące urządzenie obsługuje kodowanie xHE-AAC, sprawdź profileLevels koderów obsługujących MediaFormat.MIMETYPE_AUDIO_AAC:

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
}

Java

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

Konfigurowanie i tworzenie instancji kodera xHE-AAC

Po sprawdzeniu, czy xHE-AAC jest obsługiwany, skonfiguruj MediaFormat, ustawiając KEY_AAC_PROFILE na MediaCodecInfo.CodecProfileLevel.AACObjectXHE (wartość 42).

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
}

Java

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

Limity obsługiwanych programowych koderów (c2.android.xheaac.encoder)

Podczas korzystania z dostarczonego przez system kodera oprogramowania xHE-AAC (c2.android.xheaac.encoder) komponent zgłasza te limity możliwości:

  • Liczba kanałów: do 2 kanałów (mono / stereo).
  • Częstotliwości próbkowania: 44100 Hz, 48000 Hz.
  • Zakres szybkości transmisji: 12,000 bps–400,000 bps (12–400 kb/s).
  • Multipleksowanie kontenera: zgodne z surowymi strumieniami elementarnymi MP4A i multipleksowaniem kontenera MP4/M4A na platformie (MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4).