Audiofunktionen

Bei Android TV-Geräten können mehrere Audioausgänge gleichzeitig angeschlossen sein: Fernsehlautsprecher, HDMI-Heimkino, Bluetooth-Kopfhörer usw. Diese Audioausgabegeräte können verschiedene Audiofunktionen unterstützen, z. B. Codierungen (Dolby Digital+, DTS und PCM), Abtastrate und Kanäle. So unterstützen beispielsweise per HDMI angeschlossene Fernseher eine Vielzahl von Codecs, während verbundene Bluetooth-Kopfhörer in der Regel nur PCM unterstützen.

Die Liste der verfügbaren Audiogeräte und das weitergeleitete Audiogerät können sich auch ändern, wenn HDMI-Geräte per Hot-Plug verbunden werden, Bluetooth-Kopfhörer angeschlossen oder getrennt werden oder der Nutzer die Audioeinstellungen ändert. Da sich die Audioausgabefunktionen auch ändern können, wenn in Apps Medien wiedergegeben werden, müssen sich Apps an diese Änderungen anpassen und die Wiedergabe auf dem neuen Audiogerät und mit seinen Funktionen fortsetzen. Die Ausgabe des falschen Audioformats kann zu Fehlern oder zum Ausbleiben von Ton führen.

Apps können dieselben Inhalte in mehreren Codecs ausgeben, um Nutzern je nach Audiogerätefunktionen die bestmögliche Audioqualität zu bieten. Beispielsweise wird ein mit Dolby Digital codierter Audiostream wiedergegeben, wenn der Fernseher dies unterstützt. Wenn Dolby Digital nicht unterstützt wird, wird ein allgemeiner unterstützter PCM-Audiostream ausgewählt. Die Liste der integrierten Android-Decodierer, die zur Umwandlung eines Audiostreams in PCM verwendet werden, finden Sie unter Unterstützte Medienformate.

Während der Wiedergabe sollte die Streaming-App eine AudioTrack mit der besten AudioFormat erstellen, die vom Ausgabeaudiogerät unterstützt wird.

Einen Track im richtigen Format erstellen

Apps sollten einen AudioTrack erstellen, die Wiedergabe starten und getRoutedDevice() aufrufen, um das Standardaudiogerät zu ermitteln, über das der Ton wiedergegeben werden soll. Dabei kann es sich beispielsweise um einen sicheren, kurz-stummen PCM-codierten Track handeln, der nur zur Bestimmung des weitergeleiteten Geräts und seiner Audiofunktionen verwendet wird.

Unterstützte Codierungen

Mit getAudioProfiles() (API-Level 31 und höher) oder getEncodings() (API-Level 23 und höher) kannst du die Audioformate bestimmen, die auf dem Standard-Audiogerät verfügbar sind.

Unterstützte Audioprofile und -formate prüfen

Mit AudioProfile (API-Level 31 und höher) oder isDirectPlaybackSupported() (API-Level 29 und höher) kannst du unterstützte Kombinationen aus Format, Kanalanzahl und Abtastrate prüfen.

Einige Android-Geräte unterstützen mehr Codierungen als das Ausgabeaudiogerät. Diese zusätzlichen Formate sollten über isDirectPlaybackSupported() erkannt werden. In diesen Fällen werden die Audiodaten in ein Format umgewandelt, das vom Ausgabeaudiogerät unterstützt wird. Mit isDirectPlaybackSupported() kannst du die Unterstützung für das gewünschte Format prüfen, auch wenn es nicht in der Liste enthalten ist, die von getEncodings() zurückgegeben wird.

Antizipatorische Audioroute

Mit Android 13 (API-Level 33) wurden vorausschauende Audiorouten eingeführt. Du kannst die Unterstützung von Geräte-Audioattributen antizipieren und Titel für das aktive Audiogerät vorbereiten. Mit getDirectPlaybackSupport() kannst du prüfen, ob die direkte Wiedergabe auf dem aktuell gerouteten Audiogerät für ein bestimmtes Format und bestimmte Attribute unterstützt wird:

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
}

Alternativ können Sie abfragen, welche Profile für die direkte Medienwiedergabe über das aktuell geroutete Audiogerät unterstützt werden. Dies schließt alle Profile aus, die nicht unterstützt werden oder beispielsweise vom Android-Framework transcodiert werden würden:

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

In diesem Beispiel ist preferredFormats eine Liste von AudioFormat-Instanzen. Die Liste ist so sortiert, dass die am besten geeignete Option zuerst und die am wenigsten geeignete Option zuletzt aufgeführt wird. getDirectProfilesForAttributes() gibt eine Liste der unterstützten AudioProfile-Objekte für das aktuell geroutete Audiogerät mit dem angegebenen AudioAttributes zurück. Die Liste der bevorzugten AudioFormat-Elemente wird durchlaufen, bis eine passende unterstützte AudioProfile gefunden wird. AudioProfile wird als bestAudioProfile gespeichert. Die optimalen Stichprobenraten und Kanalmasken werden mithilfe von bestAudioProfile ermittelt. Schließlich wird eine geeignete AudioFormat-Instanz erstellt.

Audiotrack erstellen

Apps sollten diese Informationen verwenden, um eine AudioTrack für die AudioFormat mit der höchsten Qualität zu erstellen, die vom Standardaudiogerät unterstützt wird und für die ausgewählten Inhalte verfügbar ist.

Änderungen am Audiogerät abfangen

Damit Apps Audiogeräteänderungen abfangen und darauf reagieren können, müssen sie Folgendes tun:

  • Füge bei API-Levels von 24 oder höher ein OnRoutingChangedListener hinzu, um Änderungen an Audiogeräten (HDMI, Bluetooth usw.) zu überwachen.
  • Registrieren Sie für API-Level 23 eine AudioDeviceCallback, um Änderungen an der Liste der verfügbaren Audiogeräte zu erhalten.
  • Bei den API-Levels 21 und 22 solltest du HDMI-Steckerereignisse überwachen und die zusätzlichen Daten aus den Übertragungen verwenden.
  • Registrieren Sie auch eine BroadcastReceiver, um Statusänderungen von BluetoothDevice für Geräte mit einer niedrigeren API-Version als 23 zu überwachen, da AudioDeviceCallback noch nicht unterstützt wird.

Wenn eine Änderung des Audiogeräts für die AudioTrack erkannt wurde, sollte die App die aktualisierten Audiofunktionen prüfen und die AudioTrack bei Bedarf mit einem anderen AudioFormat neu erstellen. Tun Sie dies, wenn eine Codierung mit höherer Qualität jetzt unterstützt wird oder die zuvor verwendete Codierung nicht mehr unterstützt wird.

Beispielcode

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