Android TV obsługuje wiele jednoczesnych 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łączyć HDMI, połączyć się przez Bluetooth lub zmienić ustawienia. Aplikacje muszą się płynnie dostosowywać, aby zapewnić ciągłe odtwarzanie przy użyciu nowych możliwości urządzenia. Nieobsługiwane formaty mogą powodować błędy lub ciszę.
Zapewnij wiele kodowań, aby zapewnić najlepsze wrażenia w zależności od możliwości urządzenia. Na przykład Dolby Digital jest używany, jeśli jest obsługiwany. W przeciwnym razie aplikacja przełącza się na PCM. Informacje o dekoderach Androida, które przekształcają strumienie w PCM, znajdziesz w artykule o obsługiwanych formatach multimediów.
Podczas odtwarzania aplikacja do odtwarzania strumieniowego powinna utworzyć obiekt AudioTrack z najlepszym formatem AudioFormat obsługiwanym przez wyjściowe urządzenie audio.
Tworzenie ścieżki audio w odpowiednim formacie
Aplikacje powinny utworzyć obiekt AudioTrack, rozpocząć odtwarzanie i wywołać metodę 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 bezpieczna, krótka ścieżka PCM, która służy tylko do określenia urządzenia 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 mogą obsługiwać kodowanie wykraczające poza zakres obsługiwany przez wyjściowe urządzenie audio. Te dodatkowe formaty powinny być wykrywane przez 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 zwracanej przez getEncodings().
Trasa audio z wyprzedzeniem
W Androidzie 13 (poziom API 33) wprowadzono przewidywane ścieżki audio. Możesz przewidzieć obsługę atrybutów dźwięku na urządzeniu i przygotować ścieżki dla aktywnego urządzenia audio. Możesz użyć funkcji getDirectPlaybackSupport(), aby sprawdzić, czy odtwarzanie bezpośrednie jest obsługiwane na urządzeniu audio, do którego jest obecnie kierowany dźwięk, 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 urządzeniu audio, do którego jest obecnie kierowany dźwięk. Nie obejmuje to profili, które nie są obsługiwane lub które zostałyby transkodowane przez platformę 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.
Są one uporządkowane 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 elementów AudioFormat jest sprawdzana, dopóki nie zostanie znaleziony pasujący obsługiwany element 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.
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 urządzeń audio i na nie reagować, aplikacje powinny:
- W przypadku interfejsów API na poziomie 24 lub wyższym dodaj
OnRoutingChangedListener, aby monitorować zmiany urządzeń audio (HDMI, Bluetooth itp.). - W przypadku interfejsu API na poziomie 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 na poziomie niższym niż 23, ponieważAudioDeviceCallbacknie jest jeszcze obsługiwany.
Gdy zostanie wykryta zmiana urządzenia audio dla AudioTrack, aplikacja powinna sprawdzić zaktualizowane możliwości audio i, jeśli to konieczne, ponownie utworzyć AudioTrack z innym AudioFormat. Zrób to, jeśli obecnie obsługiwane jest 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);