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 niewłaściwym formacie audio może spowodować błędy lub brak dźwięku.
Aplikacje mogą wyświetlać te same treści w różnych formatach kodowania, 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ć 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ć obsługę wybranego formatu, nawet jeśli nie jest on obecny na liście zwracanej przez funkcję getEncodings()
.
Wyprzedzające ścieżki audio
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 kierowane są dane, dla danego formatu i atrybutów, możesz użyć parametru 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łu 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 w 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 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 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);