Android TV obsługuje wiele równoczesnych wyjść audio: głośniki telewizora, kino domowe HDMI, Bluetooth i inne. Te urządzenia obsługują różne formaty kodowania (np. Dolby Digital+, DTS, PCM), częstotliwości próbkowania i kanały. Telewizory podłączone przez HDMI obsługują wiele formatów, ale Bluetooth obsługuje tylko PCM.
Dostępne urządzenia audio i routing mogą się zmieniać podczas odtwarzania: użytkownicy mogą podłączać HDMI, łączyć się przez Bluetooth lub modyfikować ustawienia. Aplikacje muszą się dostosowywać, aby zapewnić ciągłe odtwarzanie z wykorzystaniem nowych możliwości urządzenia. Nieobsługiwane formaty mogą powodować błędy lub ciszę.
oferować wiele wersji kodowania, aby zapewnić jak najlepsze wrażenia na podstawie możliwości urządzenia; Jeśli na przykład urządzenie obsługuje Dolby Digital, jest on używany. W przeciwnym razie aplikacja wraca do PCM. Listę dekoderów Androida, które przekształcają strumienie w PCM, znajdziesz w sekcji Obsługiwane formaty multimediów.
Podczas odtwarzania aplikacja do odtwarzania strumieniowego powinna utworzyć AudioTrack z najlepszym AudioFormat obsługiwanym przez wyjściowe urządzenie audio.
Tworzenie ścieżki w odpowiednim formacie
Aplikacje powinny utworzyć AudioTrack, rozpocząć odtwarzanie i wywołać funkcję getRoutedDevice(), aby określić domyślne urządzenie 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 jest używany tylko do określania urządzenia, na które jest kierowany dźwięk, i jego możliwości audio.
Pobieranie obsługiwanych kodowań
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.
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. Aby sprawdzić, czy odtwarzanie bezpośrednie jest obsługiwane na aktualnie kierowanym urządzeniu audio w przypadku danego formatu i atrybutów, możesz użyć funkcji 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ż sprawdzić, które profile są obsługiwane w przypadku bezpośredniego odtwarzania multimediów na obecnie używanym urządzeniu audio. Nie obejmuje to profili, które są nieobsługiwane lub np. byłyby 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 w taki sposób, że na początku listy znajduje się najbardziej preferowany język, a na końcu – najmniej preferowany. getDirectProfilesForAttributes() zwraca listę obsługiwanych obiektówAudioProfile dla aktualnie używanego urządzenia audio z podanym parametrem AudioAttributes. Lista preferowanych AudioFormat jest przeglądana, dopóki nie zostanie znaleziony pasujący obsługiwany AudioProfile. Ten plik 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 dla wybranych treści).
Przechwytywanie zmian urządzenia audio
Aby przechwytywać zmiany urządzeń audio i na nie reagować, aplikacje powinny:
- W przypadku poziomów API 24 lub wyższych dodaj
OnRoutingChangedListener, aby monitorować zmiany urządzenia 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 używaj dodatkowych danych z transmisji.
- Zarejestruj też
BroadcastReceiver, aby monitorować zmiany stanuBluetoothDevicena urządzeniach z interfejsem API w wersji starszej niż 23, ponieważAudioDeviceCallbacknie 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.
Przykładowy kod
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);