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. Mit HDMI verbundene Fernseher unterstützen beispielsweise eine Vielzahl von Codierungen, während verbundene Bluetooth-Kopfhörer normalerweise nur PCM unterstützen.

Die Liste der verfügbaren Audiogeräte und des weitergeleiteten Audiogeräts kann sich auch ändern, indem Sie HDMI-Geräte per Hot-Anschluss anschließen, Bluetooth-Kopfhörer anschließen oder trennen oder der Nutzer die Audioeinstellungen ändert. Da sich die Funktionen für die Audioausgabe selbst dann ändern können, wenn Apps Medien wiedergeben, müssen sich die Anwendungen an diese Änderungen anpassen und die Wiedergabe auf dem neu weitergeleiteten Audiogerät und seinen Funktionen fortsetzen. Die Ausgabe des falschen Audioformats kann zu Fehlern oder zum Fehlen des Tons führen.

Anwendungen haben die Möglichkeit, den gleichen Inhalt in mehreren Codierungen auszugeben, um dem Nutzer je nach den Fähigkeiten der Audiogeräte die beste Audioerfahrung zu bieten. Beispielsweise wird ein Dolby Digital-codierter Audiostream abgespielt, sofern der Fernseher dies unterstützt. Ein weiter unterstützter PCM-Audiostream wird ausgewählt, wenn Dolby Digital nicht unterstützt wird. 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 dem besten AudioFormat erstellen, der vom Audioausgabegerät unterstützt wird.

Einen Track im richtigen Format erstellen

Apps sollten ein AudioTrack erstellen, die Wiedergabe starten und getRoutedDevice() aufrufen, um das Standard-Audiogerät zu bestimmen, über das Ton wiedergegeben werden soll. Dies kann beispielsweise ein PCM-codierter Track mit kurzzeitiger Stille sein, der nur zur Bestimmung des weitergeleiteten Geräts und seiner Audiofunktionen verwendet wird.

Unterstützte Codierungen abrufen

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 neben den vom Audioausgabegerät unterstützten Codierungen auch andere Codierungen. Diese zusätzlichen Formate sollten über isDirectPlaybackSupported() erkannt werden. In diesen Fällen werden die Audiodaten in ein Format neu codiert, das vom Audioausgabegerät unterstützt wird. Verwenden Sie isDirectPlaybackSupported(), um die Unterstützung für das gewünschte Format zu prüfen, auch wenn es nicht in der von getEncodings() zurückgegebenen Liste enthalten ist.

Antizipatorische Audioroute

Mit Android 13 (API-Level 33) wurden vorausschauende Audiorouten eingeführt. Sie können die Unterstützung von Audioattributen des Geräts voraussehen und Tracks für das aktive Audiogerät vorbereiten. Mit getDirectPlaybackSupport() können Sie prüfen, ob die direkte Wiedergabe auf dem aktuell weitergeleiteten 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 weitergeleitete 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. Sie wird so sortiert, dass sie ganz oben in der Liste und absteigend am wenigsten bevorzugt wird. getDirectProfilesForAttributes() gibt eine Liste der unterstützten AudioProfile-Objekte für das aktuell weitergeleitete Audiogerät mit dem bereitgestellten 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. Abschließend wird eine entsprechende AudioFormat-Instanz erstellt.

Audiotrack erstellen

Apps sollten anhand dieser Informationen eine AudioTrack für die AudioFormat mit der höchsten Qualität erstellen, die vom Standard-Audiogerät unterstützt wird und für den ausgewählten Inhalt verfügbar ist.

Änderungen an Audiogeräten abfangen

Um Änderungen an Audiogeräten abzufangen und darauf zu reagieren, müssen Apps:

  • Bei API-Levels ab 24 muss ein OnRoutingChangedListener hinzugefügt werden, um Änderungen an Audiogeräten (HDMI, Bluetooth usw.) zu überwachen.
  • Registriere für API-Level 23 eine AudioDeviceCallback, um Änderungen an der Liste der verfügbaren Audiogeräte zu erhalten.
  • Überwachen Sie für API-Ebenen 21 und 22 auf HDMI-Steckdosenereignisse und verwenden Sie die zusätzlichen Daten aus den Broadcasts.
  • Registriere außerdem eine BroadcastReceiver, um Statusänderungen von BluetoothDevice für Geräte unter API 23 zu überwachen, da AudioDeviceCallback noch nicht unterstützt wird.

Wenn für AudioTrack eine Änderung des Audiogeräts erkannt wurde, sollte die App die aktualisierten Audiofunktionen prüfen und das AudioTrack gegebenenfalls 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);