Funkcje audio

Urządzenia z Androidem TV mogą mieć podłączonych jednocześnie wiele wyjść audio: Głośniki telewizora, kino domowe podłączone przez HDMI, słuchawki Bluetooth itp. Te wyjściowe urządzenia 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ą 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 również może ulec zmianie poprzez podłączenie urządzeń HDMI na gorąco, podłączenie lub rozłączenie słuchawek Bluetooth, lub zmianę ustawień dźwięku. Ponieważ możliwości wyjścia audio mogą zmienia się nawet wtedy, gdy odtwarza multimedia, aplikacje muszą płynnie dostosowywać się do tych zmieni się i kontynuuj odtwarzanie na nowym przekierowanym urządzeniu audio funkcje zabezpieczeń. Wygenerowanie nieprawidłowego formatu audio może spowodować błędy lub Nie słychać dźwięku.

Aplikacje mają możliwość wyświetlania tej samej treści w różnych kodach aby zapewnić użytkownikowi najlepsze wrażenia dźwiękowe w zależności od urządzenia audio funkcje zabezpieczeń. Na przykład jest odtwarzany strumień audio w kodowaniu Dolby Digital jeśli telewizor ją obsługuje, a szersze rozwiązanie to wybierana, jeśli nie obsługuje ona Dolby Digital. Lista wbudowanych urządzeń z Androidem dekodery używane do przekształcania strumienia audio w PCM znajdziesz tutaj Obsługiwane formaty multimediów

W trakcie odtwarzania aplikacja do odtwarzania strumieniowego powinna utworzyć element AudioTrack z najlepszymi AudioFormat obsługiwane przez dane wyjściowe urządzenia audio.

Tworzenie ścieżki we właściwym formacie

Aplikacje powinny utworzyć obiekt AudioTrack, rozpocząć jego odtwarzanie i dzwonić 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 bezpieczną, krótką, wyciszoną ścieżkę zakodowaną w PCM, służącą wyłącznie do: określić kierowane urządzenie i jego możliwości audio.

Pobierz obsługiwane kodowanie

Używaj getAudioProfiles() (poziom API 31 lub wyższy) lub getEncodings() (interfejs API na poziomie 23 lub wyższym) w celu określenia formatów audio dostępnych domyślne urządzenie audio.

Sprawdzanie obsługiwanych profili audio i formatów

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

Niektóre urządzenia z Androidem obsługują też inne kodowanie obsługiwane przez wyjściowe urządzenie audio. Te dodatkowe formaty powinny być wykryte przez aplikację isDirectPlaybackSupported(). W takich przypadkach dane dźwiękowe zostanie ponownie zakodowany do formatu obsługiwanego przez wyjściowe urządzenie audio. Używaj 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. Dostępne opcje przewidzieć obsługę atrybutów dźwięku i przygotować ścieżki do wykorzystania. urządzenia audio. Za pomocą getDirectPlaybackSupport() by sprawdzić, czy aktualnie przekierowywany dźwięk obsługuje odtwarzanie bezpośrednie. urządzenia dla 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ą media bezpośrednie odtwarzania za pomocą aktualnie kierowanego urządzenia audio. Nie obejmuje to profili, które nie są obsługiwane lub zostałyby transkodowane przez Androida, platforma:

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. Kolejność z najbardziej preferowanej pozycji na liście i najmniej preferowanej na końcu. getDirectProfilesForAttributes() zwraca listę obsługiwanych AudioProfile obiektów dla aktualnie kierowane urządzenie audio z AudioAttributes. Lista Preferowane elementy (AudioFormat) są powtarzane, dopóki dopasowanie nie będzie obsługiwane Znaleziono urządzenie 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 dodaj odpowiednią AudioFormat po utworzeniu instancji.

Utwórz ścieżkę audio

Aplikacje powinny użyć tych informacji, aby utworzyć AudioTrack dla AudioFormat najwyższej jakości obsługiwana przez domyślne urządzenie audio (i dostępne w przypadku wybranych treści).

Przechwytywanie zmian urządzeń audio

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

  • W przypadku poziomów interfejsu API równych 24 lub wyższym dodaj OnRoutingChangedListener do monitorowania zmian 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 dotyczące wtyczki HDMI i wykorzystywać dodatkowe dane z transmisji.
  • Zarejestruj też BroadcastReceiver do monitorowania Zmiany stanu w: BluetoothDevice dla urządzeń starszych niż API 23, ponieważ AudioDeviceCallback nie jest jeszcze obsługiwane.

Gdy na urządzeniu AudioTrack zostanie wykryta zmiana urządzenia audio, aplikacja sprawdzić zaktualizowane możliwości audio i w razie potrzeby odtworzyć AudioTrack z innym elementem AudioFormat. Należy to zrobić, jeśli reklama o wyższej jakości jest teraz obsługiwane lub poprzednio używane kodowanie zostanie który nie jest już obsługiwany.

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);