Funkcje audio

Urządzenia z Androidem TV mogą mieć jednocześnie wiele połączeń z wyjściami audio: głośniki telewizora, kina domowego podłączonego przez HDMI, słuchawki Bluetooth itp. Te urządzenia wyjściowe mogą obsługiwać różne funkcje audio, takie jak kodowania (Dolby Digital+, DTS i PCM), częstotliwość próbkowania i kanały. Na przykład telewizory podłączone przez HDMI obsługują wiele kodowań, podczas gdy podłączone słuchawki Bluetooth zwykle obsługują tylko PCM.

Lista dostępnych urządzeń audio i przekierowanych urządzeń audio może się zmieniać w wyniku podłączania urządzeń HDMI w trybie „gorącego” podłączania, podłączania i rozłączania słuchawek Bluetooth lub zmiany ustawień audio przez użytkownika. Ponieważ możliwości wyjścia audio mogą się zmieniać nawet wtedy, gdy aplikacje odtwarzają multimedia, muszą one odpowiednio reagować na te zmiany i kontynuować odtwarzanie na nowym urządzeniu audio z nowymi możliwościami. Wyjście w nieprawidłowym formacie audio może spowodować błędy lub brak dźwięku.

Aplikacje mogą wyświetlać te same treści w różnych kodowaniach, aby zapewnić użytkownikom najlepsze wrażenia słuchowe w zależności od możliwości urządzenia audio. Na przykład strumień audio zakodowany w formacie Dolby Digital jest odtwarzany, jeśli telewizor go obsługuje, a gdy nie obsługuje Dolby Digital, wybierany jest strumień audio w formacie PCM, który jest bardziej powszechny. Lista wbudowanych dekoderów Androida służących do przekształcania strumienia audio w PCM znajduje się w sekcji Obsługiwane formaty multimediów.

Podczas odtwarzania aplikacja do strumieniowego przesyłania danych powinna utworzyć AudioTrack z najlepszym AudioFormat obsługiwanym przez urządzenie audio wyjściowe.

Utwórz ścieżkę w odpowiednim formacie

Aplikacje powinny utworzyć AudioTrack, rozpocząć odtwarzanie i wywołać funkcję getRoutedDevice(), aby określić domyślne urządzenie audio, na którym ma być odtwarzany dźwięk. Może to być na przykład krótki, bezpieczny, zakodowany w formacie PCM ścieżka z ciszą, która służy tylko do określenia przekierowanego urządzenia i jego możliwości audio.

Obsługiwane kodowania

Użyj getAudioProfiles() (interfejs API na poziomie 31 lub wyższym) lub getEncodings() (interfejs API na poziomie 23 lub wyższym), aby określić formaty audio dostępne na domyślnym urządzeniu audio.

Sprawdź obsługiwane profile i formaty audio

Użyj AudioProfile (poziom interfejsu API 31 lub nowszy) lub isDirectPlaybackSupported() (poziom interfejsu API 29 lub nowszy), aby sprawdzić obsługiwane kombinacje formatu, liczby kanałów i częstotliwości próbkowania.

Niektóre urządzenia z Androidem mogą obsługiwać kodowania inne niż te obsługiwane przez urządzenie audio wyjściowe. Te dodatkowe formaty powinny być wykrywane za pomocą isDirectPlaybackSupported(). W takich przypadkach dane audio są ponownie kodowane w formacie obsługiwanym przez urządzenie wyjściowe audio. Użyj parametru isDirectPlaybackSupported(), aby sprawdzić, czy obsługiwany jest odpowiedni format, nawet jeśli nie jest on obecny na liście zwracanej przez funkcję getEncodings().

Wyprzedzający dźwięk

W Androidzie 13 (poziom 33 interfejsu API) wprowadzono przewidujące ścieżki audio. Możesz przewidzieć obsługę atrybutu audio urządzenia i przygotować utwory dla aktywnego urządzenia audio. Aby sprawdzić, czy odtwarzanie bezpośrednie jest obsługiwane na urządzeniu audio, na które kierowany jest dźwięk, w przypadku danego formatu i atrybutów, możesz użyć elementu getDirectPlaybackSupport():

Kotlin

val format = AudioFormat.Builder()
    .setEncoding(AudioFormat.ENCODING_E_AC3)
    .setChannelMask(AudioFormat.CHANNEL_OUT_5POINT1)
    .setSampleRate(48000)
    .build()
val attributes = AudioAttributes.Builder()
    .setUsage(AudioAttributes.USAGE_MEDIA)
    .build()

if (AudioManager.getDirectPlaybackSupport(format, attributes) !=
    AudioManager.DIRECT_PLAYBACK_NOT_SUPPORTED
) {
    // The format and attributes are supported for direct playback
    // on the currently active routed audio path
} else {
    // The format and attributes are NOT supported for direct playback
    // on the currently active routed audio path
}

Java

AudioFormat format = new AudioFormat.Builder()
        .setEncoding(AudioFormat.ENCODING_E_AC3)
        .setChannelMask(AudioFormat.CHANNEL_OUT_5POINT1)
        .setSampleRate(48000)
        .build();
AudioAttributes attributes = new AudioAttributes.Builder()
        .setUsage(AudioAttributes.USAGE_MEDIA)
        .build();

if (AudioManager.getDirectPlaybackSupport(format, attributes) !=
        AudioManager.DIRECT_PLAYBACK_NOT_SUPPORTED) {
    // The format and attributes are supported for direct playback
    // on the currently active routed audio path
} else {
    // The format and attributes are NOT supported for direct playback
    // on the currently active routed audio path
}

Możesz też zapytać, które profile są obsługiwane w przypadku bezpośredniego odtwarzania multimediów na urządzeniu audio, które jest obecnie kierowane. Wyklucza to wszystkie profile, które nie są obsługiwane lub które są kodowane ponownie przez platformę Android:

Kotlin

private fun findBestAudioFormat(audioAttributes: AudioAttributes): AudioFormat {
    val preferredFormats = listOf(
        AudioFormat.ENCODING_E_AC3,
        AudioFormat.ENCODING_AC3,
        AudioFormat.ENCODING_PCM_16BIT,
        AudioFormat.ENCODING_DEFAULT
    )
    val audioProfiles = audioManager.getDirectProfilesForAttributes(audioAttributes)
    val bestAudioProfile = preferredFormats.firstNotNullOf { format ->
        audioProfiles.firstOrNull { it.format == format }
    }
    val sampleRate = findBestSampleRate(bestAudioProfile)
    val channelMask = findBestChannelMask(bestAudioProfile)
    return AudioFormat.Builder()
        .setEncoding(bestAudioProfile.format)
        .setSampleRate(sampleRate)
        .setChannelMask(channelMask)
        .build()
}

Java

private AudioFormat findBestAudioFormat(AudioAttributes audioAttributes) {
    Stream<Integer> preferredFormats = Stream.<Integer>builder()
            .add(AudioFormat.ENCODING_E_AC3)
            .add(AudioFormat.ENCODING_AC3)
            .add(AudioFormat.ENCODING_PCM_16BIT)
            .add(AudioFormat.ENCODING_DEFAULT)
            .build();
    Stream<AudioProfile> audioProfiles =
            audioManager.getDirectProfilesForAttributes(audioAttributes).stream();
    AudioProfile bestAudioProfile = (AudioProfile) preferredFormats.map(format ->
            audioProfiles.filter(profile -> profile.getFormat() == format)
                    .findFirst()
                    .orElseThrow(NoSuchElementException::new)
    );
    Integer sampleRate = findBestSampleRate(bestAudioProfile);
    Integer channelMask = findBestChannelMask(bestAudioProfile);
    return new AudioFormat.Builder()
            .setEncoding(bestAudioProfile.getFormat())
            .setSampleRate(sampleRate)
            .setChannelMask(channelMask)
            .build();
}

W tym przykładzie preferredFormats to lista instancji AudioFormat. Jest ona uporządkowana według preferencji – na początku listy znajdują się najbardziej preferowane opcje, a na końcu – najmniej preferowane. getDirectProfilesForAttributes() zwraca listę obsługiwanych obiektów AudioProfile dla aktualnie przekierowywanego urządzenia audio z podanym AudioAttributes. Lista preferowanych elementów AudioFormat jest iterowana, aż zostanie znaleziony pasujący obsługiwany element AudioProfile. Ten AudioProfile jest przechowywany jako bestAudioProfile. Optymalne częstotliwości próbkowania i maski kanałów są określane na podstawie bestAudioProfile. Na koniec tworzona jest odpowiednia instancja AudioFormat.

Tworzenie ścieżki audio

Aplikacje powinny używać tych informacji do tworzenia AudioTrack dla AudioFormat najwyższej jakości obsługiwanej przez domyślne urządzenie audio (i dostępnej dla wybranych treści).

Przechwytywanie zmian urządzenia audio

Aby przechwytywać zmiany na urządzeniu audio i na nie reagować, aplikacje powinny:

  • W przypadku poziomów interfejsu API równych lub wyższych niż 24 dodawaj OnRoutingChangedListener, aby monitorować zmiany urządzeń audio (HDMI, Bluetooth itp.).
  • W przypadku poziomu API 23 zarejestruj AudioDeviceCallback, aby otrzymywać zmiany na liście dostępnych urządzeń audio.
  • W przypadku poziomów interfejsu API 21 i 22 sprawdzaj zdarzenia podłączenia HDMI i używaj dodatkowych danych z transmisji.
  • Zarejestruj też BroadcastReceiver, aby monitorować zmiany stanu BluetoothDevice na urządzeniach z poziomem interfejsu API niższym niż 23, ponieważ AudioDeviceCallback nie jest jeszcze obsługiwany.

Gdy wykryto zmianę urządzenia audio w przypadku AudioTrack, aplikacja powinna sprawdzić zaktualizowane możliwości audio i w razie potrzeby utworzyć ponownie AudioTrack z innym AudioFormat. Zmień kodowanie, jeśli obecnie obsługiwane jest kodowanie o wyższej jakości lub jeśli używane wcześniej kodowanie nie jest już obsługiwane.

Kod demonstracyjny

Kotlin

// audioPlayer is a wrapper around an AudioTrack
// which calls a callback for an AudioTrack write error
audioPlayer.addAudioTrackWriteErrorListener {
    // error code can be checked here,
    // in case of write error try to recreate the audio track
    restartAudioTrack(findDefaultAudioDeviceInfo())
}

audioPlayer.audioTrack.addOnRoutingChangedListener({ audioRouting ->
    audioRouting?.routedDevice?.let { audioDeviceInfo ->
        // use the updated audio routed device to determine
        // what audio format should be used
        if (needsAudioFormatChange(audioDeviceInfo)) {
            restartAudioTrack(audioDeviceInfo)
        }
    }
}, handler)

Java

// audioPlayer is a wrapper around an AudioTrack
// which calls a callback for an AudioTrack write error
audioPlayer.addAudioTrackWriteErrorListener(new AudioTrackPlayer.AudioTrackWriteError() {
    @Override
    public void audioTrackWriteError(int errorCode) {
        // error code can be checked here,
        // in case of write error try to recreate the audio track
        restartAudioTrack(findDefaultAudioDeviceInfo());
    }
});

audioPlayer.getAudioTrack().addOnRoutingChangedListener(new AudioRouting.OnRoutingChangedListener() {
    @Override
    public void onRoutingChanged(AudioRouting audioRouting) {
        if (audioRouting != null && audioRouting.getRoutedDevice() != null) {
            AudioDeviceInfo audioDeviceInfo = audioRouting.getRoutedDevice();
            // use the updated audio routed device to determine
            // what audio format should be used
            if (needsAudioFormatChange(audioDeviceInfo)) {
                restartAudioTrack(audioDeviceInfo);
            }
        }
    }
}, handler);