Funkcje audio

Do urządzeń z Androidem TV możesz mieć podłączonych jednocześnie wiele wyjść audio: głośniki telewizora, kino domowe podłączone przez HDMI, słuchawki Bluetooth itd. Te urządzenia wyjściowe audio mogą obsługiwać różne możliwości 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ą różne rodzaje kodowania, a połączone słuchawki Bluetooth zwykle obsługują tylko PCM.

Lista dostępnych urządzeń audio i przekierowane urządzenie audio może się też zmienić, podłączając urządzenia HDMI z pamięcią, podłączając lub odłączając słuchawki Bluetooth lub użytkownik zmieniając ustawienia dźwięku. Możliwości wyjścia audio mogą się zmieniać nawet wtedy, gdy aplikacje odtwarzają multimedia, dlatego aplikacje muszą płynnie dostosować się do tych zmian i kontynuować odtwarzanie na nowym, przekierowanym urządzeniu audio oraz jego funkcjach. Wygenerowanie dźwięku w nieprawidłowym formacie może spowodować błędy lub brak dźwięku.

Aplikacje mogą odtwarzać te same treści w różnych kodach, co zapewnia użytkownikom najlepsze wrażenia dźwiękowe w zależności od możliwości urządzenia audio. Na przykład strumień audio w formacie Dolby Digital jest odtwarzany, jeśli jest on obsługiwany przez telewizor, a jeśli nie obsługuje Dolby Digital, wybierany jest bardziej obsługiwany strumień audio w formacie PCM. 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 odtwarzania strumieniowego powinna tworzyć AudioTrack z najlepszymi AudioFormat obsługiwanymi przez wyjściowe urządzenie audio.

Tworzenie ścieżki we właściwym 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 bezpieczną, krótką ciszę zakodowaną w PCM, która służy wyłącznie do określenia kierowanego urządzenia i jego możliwości dźwiękowych.

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.

Sprawdzanie obsługiwanych profili audio i formatów

Użyj interfejsu AudioProfile (poziom API 31 lub wyższy) lub isDirectPlaybackSupported() (poziom API 29 lub wyższy), 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 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 isDirectPlaybackSupported(), aby prawidłowo sprawdzić obsługę żądanego formatu, nawet jeśli nie ma go na liście zwracanej przez funkcję getEncodings().

Przewidywana trasa audio

W Androidzie 13 (poziom interfejsu API 33) wprowadzono wyprzedzające trasy audio. Możesz przewidzieć obsługę atrybutów audio urządzenia i przygotować ścieżki 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ż sprawdzić, które profile obsługują bezpośrednie odtwarzanie multimediów przez aktualnie kierowane urządzenie audio. Wyklucza to wszystkie profile, które nie są obsługiwane lub zostałyby transkodowane np. 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. Jest ona uporządkowana od najbardziej preferowanej na początku i najmniej preferowanej na końcu. 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 powtarzana, aż znajdziemy pasujący element AudioProfile. Ta AudioProfile jest przechowywana 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

Na podstawie tych informacji aplikacje powinny utworzyć właściwość AudioTrack dla najwyższej jakości elementu (AudioFormat) obsługiwanej przez domyślne urządzenie audio (i dostępnej w przypadku wybranych treści).

Przechwytywanie zmian urządzeń audio

Aby przechwytywać zmiany urządzenia 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 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 wtyczki HDMI i wykorzystuj dodatkowe dane z transmisji.
  • Zarejestruj też BroadcastReceiver, aby monitorować zmiany stanu BluetoothDevice w przypadku urządzeń starszych niż API 23, ponieważ AudioDeviceCallback nie jest jeszcze obsługiwany.

Gdy zostanie wykryta zmiana urządzenia audio na urządzeniu AudioTrack, aplikacja powinna sprawdzić zaktualizowane funkcje audio i w razie potrzeby odtworzyć AudioTrack przy użyciu innego urządzenia 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);