Urządzenia z Androidem TV mogą mieć jednocześnie podłączonych kilka wyjść audio: głośniki telewizora, kino domowe podłączone przez HDMI, słuchawki Bluetooth itp. Te urządzenia wyjściowe audio mogą obsługiwać różne funkcje audio, takie jak kodowanie (Dolby Digital+, DTS i PCM), częstotliwość próbkowania i kanały. Na przykład telewizory podłączone przez HDMI obsługują wiele formatów kodowania, a słuchawki Bluetooth zwykle tylko PCM.
Lista dostępnych urządzeń audio i urządzenie, do którego kierowany jest dźwięk, mogą się też zmieniać w wyniku podłączania i odłączania urządzeń HDMI, podłączania i odłączania słuchawek Bluetooth oraz zmiany ustawień dźwięku przez użytkownika. Możliwości wyjścia audio mogą się zmieniać nawet podczas odtwarzania multimediów przez aplikacje, dlatego muszą one płynnie dostosowywać się do tych zmian i kontynuować odtwarzanie na nowym urządzeniu audio oraz wykorzystywać jego możliwości. Wybranie nieprawidłowego formatu dźwięku 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żytkownikowi najlepszą jakość dźwięku w zależności od możliwości urządzenia audio. Na przykład strumień audio zakodowany w Dolby Digital jest odtwarzany, jeśli telewizor go obsługuje, a strumień audio PCM, który jest obsługiwany przez więcej urządzeń, jest wybierany, gdy Dolby Digital nie jest obsługiwany. Listę wbudowanych dekoderów Androida używanych do przekształcania strumienia audio w PCM znajdziesz w sekcji Obsługiwane formaty multimediów.
Podczas odtwarzania aplikacja do strumieniowania powinna utworzyć obiekt AudioTrack
z najlepszym formatem AudioFormat
obsługiwanym przez wyjściowe urządzenie audio.
Tworzenie ścieżki w odpowiednim formacie
Aplikacje powinny utworzyć AudioTrack
, rozpocząć odtwarzanie i wywołać
getRoutedDevice()
w celu określenia domyślnego urządzenia audio, z którego ma być odtwarzany dźwięk.
Może to być na przykład bezpieczny, krótki, cichy utwór zakodowany w formacie PCM, który służy wyłącznie do określania urządzenia, do którego kierowany jest dźwięk, i jego możliwości audio.
Pobieranie obsługiwanych kodowań
Użyj funkcji
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.
Sprawdzanie obsługiwanych profili i formatów audio
Użyj AudioProfile
(API na poziomie 31 lub wyższym) lub isDirectPlaybackSupported()
(API na poziomie 29 lub wyższym), aby sprawdzić obsługiwane kombinacje formatu, liczby kanałów i częstotliwości próbkowania.
Niektóre urządzenia z Androidem obsługują kodowania inne niż te, które są 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
isDirectPlaybackSupported()
, aby prawidłowo sprawdzić obsługę wybranego formatu, nawet jeśli nie ma go na liście zwróconej przez getEncodings()
.
Przewidywana ścieżka audio
W Androidzie 13 (poziom API 33) wprowadzono przewidywane trasy audio. Możesz przewidzieć obsługę atrybutów dźwięku urządzenia i przygotować ścieżki dla aktywnego urządzenia audio. Możesz użyć
getDirectPlaybackSupport()
aby sprawdzić, czy odtwarzanie bezpośrednie jest obsługiwane na aktualnie przekierowanym urządzeniu audio
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ż sprawdzić, które profile są obsługiwane w przypadku bezpośredniego odtwarzania multimediów na aktualnie używanym urządzeniu audio. Nie obejmuje to profili, które są nieobsługiwane lub które byłyby na przykład transkodowane 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 od najbardziej preferowanego do najmniej preferowanego.
getDirectProfilesForAttributes()
zwraca listę obsługiwanych obiektów
AudioProfile
dla
aktualnie kierowanego urządzenia audio z podanym
AudioAttributes
. Lista preferowanych AudioFormat
produktów jest iterowana, dopóki nie zostanie znaleziony pasujący obsługiwany 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
o najwyższej jakości AudioFormat
obsługiwanej przez domyślne urządzenie audio (i dostępnej w przypadku wybranych treści).
Przechwytywanie zmian urządzenia audio
Aby przechwytywać zmiany urządzenia audio i na nie reagować, aplikacje powinny:
- W przypadku interfejsów API w wersji 24 lub nowszej dodaj
OnRoutingChangedListener
, aby monitorować zmiany urządzeń audio (HDMI, Bluetooth itp.). - W przypadku poziomu interfejsu API 23 zarejestruj
AudioDeviceCallback
, aby otrzymywać informacje o zmianach na liście dostępnych urządzeń audio. - W przypadku poziomów interfejsu API 21 i 22 monitoruj zdarzenia podłączenia HDMI i korzystaj z dodatkowych danych z transmisji.
- Zarejestruj też
BroadcastReceiver
, aby monitorowaćBluetoothDevice
zmiany stanu na urządzeniach z interfejsem API w wersji niższej niż 23, ponieważAudioDeviceCallback
nie jest jeszcze obsługiwany.
Gdy w przypadku AudioTrack
zostanie wykryta zmiana urządzenia audio, aplikacja powinna sprawdzić zaktualizowane możliwości audio i w razie potrzeby ponownie utworzyć AudioTrack
z innym AudioFormat
. Zrób to, jeśli jest teraz obsługiwane 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);