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 formatów kodowania, a słuchawki Bluetooth zwykle tylko PCM.

Lista dostępnych urządzeń audio i urządzenie, do którego kierowany jest dźwięk, mogą się też zmieniać w wyniku podłączania i odłączania urządzeń HDMI, podłączania i odłączania słuchawek Bluetooth oraz zmiany ustawień dźwięku przez użytkownika. Możliwości wyjścia audio mogą się zmieniać nawet podczas odtwarzania multimediów przez aplikacje, dlatego muszą one płynnie dostosowywać się do tych zmian i kontynuować odtwarzanie na nowym urządzeniu audio oraz wykorzystywać jego możliwości. Wybranie nieprawidłowego formatu dźwięku może spowodować błędy lub brak dźwięku.

Aplikacje mogą wyświetlać te same treści w różnych formatach kodowania, aby zapewnić użytkownikowi najlepszą jakość dźwięku w zależności od możliwości urządzenia audio. Na przykład strumień audio zakodowany w Dolby Digital jest odtwarzany, jeśli telewizor go obsługuje, a strumień audio PCM, który jest obsługiwany przez więcej urządzeń, jest wybierany, gdy Dolby Digital nie jest obsługiwany. Listę wbudowanych dekoderów Androida używanych do przekształcania strumienia audio w PCM znajdziesz w sekcji Obsługiwane formaty multimediów.

Podczas odtwarzania aplikacja do strumieniowania powinna utworzyć obiekt AudioTrack z najlepszym formatem AudioFormat obsługiwanym przez wyjściowe urządzenie audio.

Tworzenie ścieżki w odpowiednim formacie

Aplikacje powinny utworzyć AudioTrack, rozpocząć odtwarzanie i wywołać getRoutedDevice() w celu określenia domyślnego urządzenia audio, z którego ma być odtwarzany dźwięk. Może to być na przykład bezpieczny, krótki, cichy utwór zakodowany w formacie PCM, który służy wyłącznie do określania urządzenia, do którego kierowany jest dźwięk, i jego możliwości audio.

Pobieranie obsługiwanych kodowań

Użyj funkcji 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 obsługują kodowania inne niż te, które są 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 isDirectPlaybackSupported(), aby prawidłowo sprawdzić obsługę wybranego formatu, nawet jeśli nie ma go na liście zwróconej przez getEncodings().

Przewidywana ścieżka audio

W Androidzie 13 (poziom API 33) wprowadzono przewidywane trasy audio. Możesz przewidzieć obsługę atrybutów dźwięku urządzenia i przygotować ścieżki dla aktywnego urządzenia audio. Możesz użyć getDirectPlaybackSupport() aby sprawdzić, czy odtwarzanie bezpośrednie jest obsługiwane na aktualnie przekierowanym urządzeniu audio 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 aktualnie używanym urządzeniu audio. Nie obejmuje to profili, które są nieobsługiwane lub które byłyby na przykład transkodowane 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. Jest ona uporządkowana 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 AudioFormat produktów jest iterowana, dopóki nie zostanie znaleziony pasujący obsługiwany 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.

Tworzenie ścieżki audio

Aplikacje powinny używać tych informacji do tworzenia AudioTrack o najwyższej jakości AudioFormat obsługiwanej przez domyślne urządzenie audio (i dostępnej w przypadku wybranych treści).

Przechwytywanie zmian urządzenia audio

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

  • W przypadku interfejsów API w wersji 24 lub nowszej dodaj OnRoutingChangedListener , aby monitorować zmiany urządzeń audio (HDMI, Bluetooth itp.).
  • W przypadku poziomu interfejsu API 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 korzystaj z dodatkowych danych z transmisji.
  • Zarejestruj też BroadcastReceiver, aby monitorować BluetoothDevice zmiany stanu na urządzeniach z interfejsem API w wersji niższej niż 23, ponieważ AudioDeviceCallback nie jest jeszcze obsługiwany.

Gdy w przypadku AudioTrack zostanie wykryta zmiana urządzenia audio, aplikacja powinna sprawdzić zaktualizowane możliwości audio i w razie potrzeby ponownie utworzyć AudioTrack z innym AudioFormat. Zrób to, jeśli jest teraz obsługiwane 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);