Funkcje audio

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 stanu BluetoothDevice na urządzeniach z interfejsem API na poziomie niższym niż 23, ponieważ AudioDeviceCallback nie 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);