Funkcje audio

Urządzenia z Androidem TV mogą mieć jednocześnie wiele połączeń z wyjściami audio: głośniki telewizora, kina domowego podłączonego przez HDMI, słuchawki Bluetooth itp. Te urządzenia wyjściowe mogą obsługiwać różne funkcje audio, takie jak kodowania (Dolby Digital+, DTS i PCM), częstotliwość próbkowania i kanały. Na przykład telewizory podłączone przez HDMI obsługują wiele kodowań, podczas gdy podłączone słuchawki Bluetooth zwykle obsługują tylko PCM.

Lista dostępnych urządzeń audio i przekierowanych urządzeń audio może się zmieniać w wyniku podłączania urządzeń HDMI w trybie „gorącego” podłączania, podłączania 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, muszą one odpowiednio reagować na te zmiany i kontynuować odtwarzanie na nowym urządzeniu audio z nowymi możliwościami. Wyjście w nieprawidłowym formacie audio może spowodować błędy lub brak dźwięku.

Aplikacje mogą wyświetlać te same treści w różnych kodowaniach, aby zapewnić użytkownikom najlepsze wrażenia słuchowe w zależności od możliwości urządzenia audio. Na przykład strumień audio zakodowany w formacie Dolby Digital jest odtwarzany, jeśli telewizor go obsługuje, a gdy nie obsługuje Dolby Digital, wybierany jest strumień audio w formacie PCM, który jest bardziej powszechny. Lista wbudowanych dekoderów Androida służących do przekształcania strumienia audio w PCM znajduje się w sekcji Obsługiwane formaty multimediów.

Podczas odtwarzania aplikacja do strumieniowego przesyłania danych powinna utworzyć AudioTrack z najlepszą AudioFormat obsługiwaną przez urządzenie audio wyjściowe.

Tworzenie utworu w odpowiednim formacie

Aplikacje powinny utworzyć obiekt AudioTrack, rozpocząć jego odtwarzanie i wywołać getRoutedDevice(), aby określić domyślne urządzenie audio, z którego ma odtwarzać dźwięk. Może to być na przykład krótki, bezpieczny, zakodowany w formacie PCM ścieżka z ciszą, która służy tylko do określenia przekierowanego urządzenia i jego możliwości audio.

Pobierz obsługiwane kodowanie

Użyj interfejsu getAudioProfiles() (poziom API 31 lub wyższy) lub getEncodings() (poziom interfejsu API 23 lub wyższy), aby określić formaty audio dostępne na domyślnym urządzeniu audio.

Sprawdź obsługiwane profile i formaty audio

Użyj AudioProfile (poziom interfejsu API 31 lub nowszy) lub isDirectPlaybackSupported() (poziom interfejsu API 29 lub nowszy), aby sprawdzić obsługiwane kombinacje formatu, liczby kanałów i częstotliwości próbkowania.

Niektóre urządzenia z Androidem obsługują kodowanie inne niż obsługiwane przez wyjściowe urządzenie audio. Te dodatkowe formaty powinny być wykrywane za pomocą isDirectPlaybackSupported(). W takich przypadkach dane audio są ponownie kodowane do formatu obsługiwanego przez wyjściowe urządzenie audio. Użyj parametru isDirectPlaybackSupported(), aby sprawdzić, czy obsługiwany jest odpowiedni format, nawet jeśli nie jest on obecny na liście zwracanej przez funkcję getEncodings().

Wyprzedzający dźwięk

W Androidzie 13 (poziom 33 interfejsu API) wprowadzono przewidujące ścieżki audio. Możesz przewidzieć obsługę atrybutów audio urządzenia i przygotować utwory dla aktywnego urządzenia audio. Możesz użyć getDirectPlaybackSupport(), aby sprawdzić, czy aktualnie kierowane urządzenie audio obsługuje odtwarzanie bezpośrednie 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ż zapytać, które profile są obsługiwane w przypadku bezpośredniego odtwarzania multimediów na urządzeniu audio, na które jest obecnie kierowany dźwięk. Wyklucza to wszystkie profile, które nie są obsługiwane lub które są przekodowywane przez platformę Android:

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. Lista jest uporządkowana tak, że na początku znajdują się najbardziej preferowane opcje, a na końcu najmniej preferowane. getDirectProfilesForAttributes() zwraca listę obsługiwanych obiektów AudioProfile dla aktualnie kierowanego urządzenia audio z dołączonym obiektem AudioAttributes. Lista preferowanych elementów AudioFormat jest iterowana, aż zostanie znaleziony pasujący obsługiwany element AudioProfile. Ten AudioProfile jest przechowywany jako bestAudioProfile. Optymalne współczynniki próbkowania oraz maski kanałów są określane na podstawie tych danych: 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 na urządzeniu audio i na nie reagować, aplikacje powinny:

  • Jeśli poziomy interfejsu API są równe lub większe niż 24, dodaj OnRoutingChangedListener, aby monitorować zmiany w urządzeniach audio (HDMI, Bluetooth itp.).
  • W przypadku poziomu API 23 zarejestruj AudioDeviceCallback, aby otrzymywać zmiany w liście dostępnych urządzeń audio.
  • W przypadku interfejsów API poziomów 21 i 22 obserwuj zdarzenia wtyczki HDMI i wykorzystuj dodatkowe dane z transmisji.
  • Zarejestruj też BroadcastReceiver, aby monitorować zmiany stanu BluetoothDevice na urządzeniach z poziomem interfejsu API niższym niż 23, ponieważ AudioDeviceCallback nie jest jeszcze obsługiwany.

Gdy wykryto zmianę urządzenia audio AudioTrack, aplikacja powinna sprawdzić zaktualizowane możliwości audio i w razie potrzeby utworzyć ponownie AudioTrack z innym AudioFormat. Zrób to, jeśli obsługiwane jest kodowanie o wyższej jakości lub wcześniej używane 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);