Funkcje audio

Urządzenia z Androidem TV mogą mieć jednocześnie podłączonych kilka wyjść audio: głośniki telewizora, kino domowe podłączone przez HDMI, słuchawki Bluetooth itp. Te urządzenia wyjściowe audio mogą obsługiwać 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 kodowań, a słuchawki Bluetooth zwykle obsługują tylko PCM.

Lista dostępnych urządzeń audio i urządzenie, do którego jest kierowany dźwięk, mogą się też zmieniać w wyniku podłączania i odłączania urządzeń HDMI, łączenia i rozłączania słuchawek Bluetooth lub zmiany ustawień audio przez użytkownika. Ponieważ możliwości wyjścia audio mogą się zmieniać nawet wtedy, gdy aplikacje odtwarzają multimedia, aplikacje muszą się płynnie dostosowywać do tych zmian i kontynuować odtwarzanie na nowym urządzeniu wyjściowym audio oraz korzystać z jego możliwości. Użycie nieprawidłowego formatu audio może spowodować błędy lub brak dźwięku.

Aplikacje mogą odtwarzać tę samą treść w różnych kodowaniach, aby zapewnić użytkownikowi najlepsze wrażenia dźwiękowe w zależności od możliwości urządzenia audio. Na przykład, jeśli telewizor obsługuje Dolby Digital, odtwarzany jest strumień audio zakodowany w tym formacie. Jeśli nie obsługuje Dolby Digital, wybierany jest strumień audio PCM, który jest bardziej powszechnie obsługiwany. Listę wbudowanych dekoderów Androida używanych do przekształcania strumienia audio w PCM znajdziesz w artykule Obsługiwane formaty multimediów.

Podczas odtwarzania aplikacja do odtwarzania strumieniowego powinna utworzyć element AudioTrack z najlepszym AudioFormat obsługiwanym przez wyjściowe 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 formacie 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 API 31 i wyższy) lub getEncodings() (poziom API 23 i wyższy).

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 API 31 i wyższy) lub isDirectPlaybackSupported() (poziom API 29 i wyższy).

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

Przewidywane routowanie audio

W Androidzie 13 (poziom API 33) wprowadzono przewidywane routowanie audio. Możesz przewidzieć obsługę atrybutów audio urządzenia i przygotować ścieżki dla aktywnego urządzenia audio. Aby sprawdzić, czy bezpośrednie odtwarzanie jest obsługiwane na obecnie używanym 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 przez obecnie używane urządzenie audio. Nie obejmuje to profili, które nie są obsługiwane lub które 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 to lista AudioFormat instancji. Jest ona uporządkowana w taki sposób, że najbardziej preferowane formaty znajdują się na początku listy, a najmniej preferowane – na końcu. 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 najwyższej jakości formatu AudioFormat 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 API 24 i wyższych dodać element OnRoutingChangedListener , aby monitorować zmiany urządzenia audio (HDMI, Bluetooth itp.).
  • W przypadku poziomu API 23 zarejestrować element AudioDeviceCallback, aby otrzymywać informacje o zmianach na liście dostępnych urządzeń audio.
  • W przypadku poziomów API 21 i 22 monitorować zdarzenia podłączenia HDMI i używać dodatkowych danych z transmisji.
  • Zarejestrować też element BroadcastReceiver, aby monitorować BluetoothDevice zmiany stanu w przypadku urządzeń z poziomem API niższym niż 23, ponieważ AudioDeviceCallback nie 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 ponownie utworzyć 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);