Funkcje audio

Do urządzeń z Androidem TV można jednocześnie podłączyć wiele wyjść audio: głośniki telewizora, kino domowe podłączone przez HDMI, słuchawki Bluetooth itd. Te urządzenia wyjściowe audio obsługują 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 rodzajów kodowania, a podłączone słuchawki Bluetooth obsługują zwykle tylko PCM.

Listę dostępnych urządzeń audio i przekierowanego urządzenia audio można też zmieniać przez podłączenie urządzenia HDMI z pamięci, podłączenie lub odłączenie słuchawek Bluetooth bądź zmianę ustawień dźwięku przez użytkownika. Wyjście audio może się zmieniać nawet wtedy, gdy aplikacje odtwarzają multimedia, więc aplikacje muszą płynnie się dostosować do tych zmian i kontynuować odtwarzanie na nowym przekierowanym urządzeniu audio oraz jego możliwościach. Użycie nieprawidłowego formatu dźwięku może spowodować błędy lub brak odtwarzania dźwięku.

Aplikacje mogą odtwarzać te same treści w różnych kodach, co pozwala uzyskać najwyższą jakość dźwięku w zależności od możliwości urządzenia audio. Na przykład odtwarzany jest strumień audio zakodowany w formacie Dolby Digital, jeśli telewizor go obsługuje, a jeśli nie obsługuje Dolby Digital, wybierany jest szerzej obsługiwany strumień audio PCM. Listę wbudowanych dekoderów Androida używanych do przekształcania strumienia audio do formatu PCM znajdziesz w sekcji Obsługiwane formaty multimediów.

W momencie odtwarzania aplikacja do odtwarzania strumieniowego powinna utworzyć AudioTrack z najlepszym komponentem AudioFormat obsługiwanym przez wyjściowe urządzenie audio.

Utwórz ścieżkę we właściwym formacie

Aplikacje powinny utworzyć element AudioTrack, rozpocząć 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ć np. bezpieczna, krótko wyciszona ścieżka zakodowana w formacie PCM, używana tylko do określenia, na które urządzenie jest kierowane, i jego możliwości audio.

Uzyskaj obsługiwane kodowanie

Aby określić formaty audio dostępne na domyślnym urządzeniu audio, użyj getAudioProfiles() (poziom interfejsu API 31 lub wyższym) lub getEncodings() (interfejs API na poziomie 23 lub wyższym).

Sprawdzanie obsługiwanych profili audio i formatów

Aby sprawdzić obsługiwane kombinacje formatu, liczby kanałów i częstotliwości próbkowania, użyj poziomu AudioProfile (poziom interfejsu API 31 lub wyższym) lub isDirectPlaybackSupported() (interfejs API na poziomie 29 lub wyższym).

Niektóre urządzenia z Androidem obsługują kodowanie inne niż obsługiwane przez wyjściowe urządzenie audio. Te dodatkowe formaty należy wykrywać za pomocą isDirectPlaybackSupported(). W takich przypadkach dane audio są ponownie kodowane do formatu obsługiwanego przez wyjściowe urządzenie audio. Użyj operatora isDirectPlaybackSupported(), aby prawidłowo sprawdzić obsługę wybranego formatu, nawet jeśli nie ma go na liście zwróconej przez getEncodings().

Przewidująca trasa audio

W Androidzie 13 (poziom interfejsu API 33) wprowadzono ścieżki audio przewidziane do przewidywania. Możesz przewidzieć obsługę atrybutów audio urządzenia i przygotować ścieżki dla aktywnych urządzeń audio. Za pomocą parametru getDirectPlaybackSupport() możesz sprawdzić, czy na aktualnie przekierowanym urządzeniu audio obsługiwane jest odtwarzanie bezpośrednie w przypadku danych 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ż zapytać, które profile są obsługiwane w przypadku bezpośredniego odtwarzania multimediów przez aktualnie kierowane urządzenie audio. Wyklucza to profile, które nie są obsługiwane lub byłyby na przykład 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 jest listą instancji AudioFormat. Są one uporządkowane od najbardziej preferowanej pozycji na liście, a najniższe – od najmniejszej. getDirectProfilesForAttributes() zwraca listę obsługiwanych obiektów AudioProfile dla aktualnie kierowanego urządzenia audio z dostarczonym elementem AudioAttributes. Lista preferowanych elementów AudioFormat jest powtarzana tak długo, aż znajdzie pasujące, obsługiwane elementy: AudioProfile. Ta AudioProfile jest przechowywana jako bestAudioProfile. Optymalne częstotliwości próbkowania i maski kanałów są określane na podstawie wartości bestAudioProfile. Na koniec tworzona jest odpowiednia instancja AudioFormat.

Utwórz ścieżkę audio

Aplikacje powinny korzystać z tych informacji, aby utworzyć plik AudioTrack o najwyższej jakości (AudioFormat), który jest obsługiwany przez domyślne urządzenie audio (i dostępny w przypadku wybranych treści).

Przechwytywanie zmian na urządzeniu audio

Aby przechwytywać zmiany w urządzeniu audio i na nie reagować, aplikacje powinny:

  • W przypadku poziomów API równych lub wyższym niż 24 dodaj OnRoutingChangedListener, aby monitorować zmiany w urządzeniach audio (HDMI, Bluetooth itd.).
  • W przypadku interfejsu API poziomu 23 zarejestruj AudioDeviceCallback, aby otrzymywać zmiany na liście dostępnych urządzeń audio.
  • W przypadku interfejsów API poziomów 21 i 22 obserwuj zdarzenia związane z wtyczką HDMI i korzystaj z dodatkowych danych z transmisji.
  • Zarejestruj też BroadcastReceiver w celu monitorowania zmian stanu BluetoothDevice w przypadku urządzeń w wersji starszej niż API 23, ponieważ usługa AudioDeviceCallback nie jest jeszcze obsługiwana.

Gdy na urządzeniu AudioTrack zostanie wykryta zmiana urządzenia audio, aplikacja powinna sprawdzić zaktualizowane funkcje audio i w razie potrzeby odtworzyć AudioTrack z innym elementem typu AudioFormat. Zrób to, jeśli obecnie obsługiwane jest kodowanie w wyższej jakości lub używane wcześniej 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);