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 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ę odpowiednio dostosowywać, aby zapewnić ciągłe odtwarzanie przy użyciu możliwości nowego urządzenia. Nieobsługiwane formaty mogą powodować błędy lub ciszę.
Oferuj wiele kodowań, aby zapewnić najlepsze wrażenia w zależności od możliwości urządzenia. Na przykład jeśli jest obsługiwany format Dolby Digital, jest on używany. W przeciwnym razie aplikacja wraca do PCM. Zobacz Obsługiwane formaty multimediów, aby dowiedzieć się więcej o dekoderach Androida, które przekształcają strumienie w PCM.
Podczas odtwarzania aplikacja do odtwarzania strumieniowego powinna utworzyć element AudioTrack z najlepszym AudioFormat obsługiwanym przez urządzenie audio.
Tworzenie ścieżki w odpowiednim formacie
Aplikacje powinny utworzyć element AudioTrack, rozpocząć jego odtwarzanie i wywołać
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 bezpieczna, krótka, cicha ścieżka zakodowana w PCM, która służy tylko do określenia urządzenia, do którego jest kierowany dźwięk, i jego możliwości audio.
Pobieranie obsługiwanych kodowań
Aby określić formaty audio dostępne na
domyślnym urządzeniu audio, użyj funkcji getAudioProfiles() (poziom interfejsu API 31 lub nowszy) albo getEncodings()
(poziom interfejsu API 23 lub nowszy).
Sprawdzanie obsługiwanych profili i formatów audio
Aby sprawdzić obsługiwane
kombinacje formatu, liczby kanałów i częstotliwości próbkowania, użyj funkcji AudioProfile (poziom interfejsu API 31 lub nowszy) albo
isDirectPlaybackSupported() (poziom interfejsu API 29 lub nowszy).
Niektóre urządzenia z Androidem mogą obsługiwać kodowania, które nie są obsługiwane przez urządzenie wyjściowe audio. Te dodatkowe formaty powinny być wykrywane za pomocą funkcji isDirectPlaybackSupported(). W takich przypadkach dane audio są ponownie kodowane do formatu obsługiwanego przez urządzenie wyjściowe audio. Użyj funkcji isDirectPlaybackSupported(), aby prawidłowo sprawdzić obsługę wybranego formatu, nawet jeśli nie ma go na liście zwracanej przez funkcję getEncodings().
Przewidywany routing audio
W Androidzie 13 (poziom interfejsu API 33) wprowadzono przewidywane routingi audio. Możesz przewidzieć obsługę atrybutów audio urządzenia i przygotować ścieżki dla aktywnego urządzenia audio. Aby sprawdzić, czy getDirectPlaybackSupport() jest obsługiwane na obecnie używanym urządzeniu audio w przypadku danego
formatu i atrybutów, możesz użyć funkcji
bezpośrednie odtwarzanie:
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 nie są obsługiwane lub które np. byłyby transkodowane przez framework Androida:
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 format, a na końcu – najmniej preferowany. getDirectProfilesForAttributes() zwraca listę obsługiwanych
AudioProfile obiektów dla obecnie używanego urządzenia audio z
podanymi AudioAttributes. Lista preferowanych elementów AudioFormat jest iterowana do momentu znalezienia pasującego obsługiwanego elementu AudioProfile. Ten element
AudioProfile jest przechowywany jako bestAudioProfile. Optymalne częstotliwości próbkowania i maski kanałów są określane na podstawie elementu bestAudioProfile. Na koniec tworzona jest odpowiednia
AudioFormat instancja.
Tworzenie ścieżki audio
Aplikacje powinny używać tych informacji do utworzenia elementu AudioTrack dla formatu AudioFormat o najwyższej jakości obsługiwanego przez domyślne urządzenie audio (i dostępnego w przypadku wybranej treści).
Przechwytywanie zmian urządzenia audio
Aby przechwytywać zmiany urządzenia audio i na nie reagować, aplikacje powinny:
- W przypadku poziomów interfejsu API 24 lub nowszych dodać element
OnRoutingChangedListener, aby monitorować zmiany urządzenia audio (HDMI, Bluetooth itp.). - W przypadku poziomu interfejsu API 23 zarejestrować element
AudioDeviceCallback, aby otrzymywać informacje o zmianach na liście dostępnych urządzeń audio. - W przypadku poziomów interfejsu API 21 i 22 monitorować zdarzenia podłączenia HDMI i używać dodatkowych danych z transmisji.
- Zarejestrować też element
BroadcastReceiver, aby monitorować zmiany stanu elementuBluetoothDevicew przypadku urządzeń z poziomem interfejsu API niższym niż 23, ponieważ elementAudioDeviceCallbacknie jest jeszcze obsługiwany.
Gdy w przypadku elementu AudioTrack zostanie wykryta zmiana urządzenia audio, aplikacja powinna sprawdzić zaktualizowane możliwości audio i w razie potrzeby utworzyć ponownie element AudioTrack z innym formatem AudioFormat. Zrób to, jeśli jest teraz obsługiwane kodowanie o wyższej jakości lub jeśli 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);