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 najlepszą AudioFormat
obsługiwaną przez urządzenie audio wyjściowe.
Tworzenie utworu w odpowiednim formacie
Aplikacje powinny utworzyć obiekt AudioTrack
, rozpocząć jego odtwarzanie i wywołać getRoutedDevice()
, aby określić domyślne urządzenie audio, z którego ma odtwarzać 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.
Pobierz obsługiwane kodowanie
Użyj interfejsu getAudioProfiles()
(poziom API 31 lub wyższy) lub getEncodings()
(poziom interfejsu API 23 lub wyższy), 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 obsługują kodowanie inne niż obsługiwane przez wyjściowe urządzenie audio. Te dodatkowe formaty powinny być wykrywane za pomocą isDirectPlaybackSupported()
. W takich przypadkach dane audio są ponownie kodowane do formatu obsługiwanego przez wyjściowe urządzenie 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ę atrybutów audio urządzenia i przygotować utwory dla aktywnego urządzenia audio. Możesz użyć getDirectPlaybackSupport()
, aby sprawdzić, czy aktualnie kierowane urządzenie audio obsługuje odtwarzanie bezpośrednie w przypadku danego formatu i atrybutów:
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, na które jest obecnie kierowany dźwięk. Wyklucza to wszystkie profile, które nie są obsługiwane lub które są przekodowywane 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
. Lista jest uporządkowana tak, że na początku znajdują się najbardziej preferowane opcje, a na końcu najmniej preferowane.
getDirectProfilesForAttributes()
zwraca listę obsługiwanych obiektów AudioProfile
dla aktualnie kierowanego urządzenia audio z dołączonym obiektem AudioAttributes
. Lista preferowanych elementów AudioFormat
jest iterowana, aż zostanie znaleziony pasujący obsługiwany element AudioProfile
. Ten AudioProfile
jest przechowywany jako bestAudioProfile
.
Optymalne współczynniki próbkowania oraz maski kanałów są określane na podstawie tych danych: bestAudioProfile
.
Na koniec tworzona jest odpowiednia instancja AudioFormat
.
Utwórz ścieżkę audio
Aplikacje powinny używać tych informacji do tworzenia AudioTrack
dla najwyższej jakości AudioFormat
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:
- Jeśli poziomy interfejsu API są równe lub większe niż 24, dodaj
OnRoutingChangedListener
, aby monitorować zmiany w urządzeniach audio (HDMI, Bluetooth itp.). - W przypadku poziomu API 23 zarejestruj
AudioDeviceCallback
, aby otrzymywać zmiany w liście dostępnych urządzeń audio. - W przypadku interfejsów API poziomów 21 i 22 obserwuj zdarzenia wtyczki HDMI i wykorzystuj dodatkowe dane z transmisji.
- Zarejestruj też
BroadcastReceiver
, aby monitorować zmiany stanuBluetoothDevice
na urządzeniach z poziomem interfejsu API niższym niż 23, ponieważAudioDeviceCallback
nie jest jeszcze obsługiwany.
Gdy wykryto zmianę urządzenia audio AudioTrack
, aplikacja powinna sprawdzić zaktualizowane możliwości audio i w razie potrzeby utworzyć ponownie AudioTrack
z innym AudioFormat
. Zrób to, jeśli obsługiwane jest kodowanie o wyższej jakości lub wcześniej używane 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);