Audiofunktionen

Bei Android TV-Geräten können mehrere Audioausgänge gleichzeitig angeschlossen sein: Fernsehlautsprecher, über HDMI verbundenes Heimkino, Bluetooth-Kopfhörer usw. Diese Audioausgabegeräte unterstützen verschiedene Audiofunktionen, wie Codierungen (Dolby Digital+, DTS und PCM), Abtastrate und Kanäle. Beispielsweise unterstützen HDMI-verbundene Fernseher eine Vielzahl von Codierungen, während verbundene Bluetooth-Kopfhörer normalerweise nur PCM unterstützen.

Die Liste der verfügbaren Audiogeräte und des gerouteten Audiogeräts kann sich auch ändern, indem Sie HDMI-Geräte Hot-Plug-ins anschließen, Bluetooth-Kopfhörer anschließen oder trennen oder den Nutzer die Audioeinstellungen ändern. Da sich die Audioausgabefunktionen auch dann ändern können, wenn Apps Medien wiedergeben, müssen sich die Apps an diese Änderungen anpassen und die Wiedergabe auf dem neuen gerouteten Audiogerät und dessen Funktionen fortsetzen. Die Ausgabe des falschen Audioformats kann zu Fehlern führen oder dazu führen, dass der Ton nicht wiedergegeben wird.

Apps können denselben Inhalt in mehreren Codierungen ausgeben, um dem Nutzer je nach den Fähigkeiten des Audiogeräts die bestmögliche Audioqualität zu bieten. Beispielsweise wird ein Dolby Digital-codierter Audiostream wiedergegeben, wenn 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.

Zum Zeitpunkt der Wiedergabe sollte die Streaming-App ein AudioTrack mit dem besten AudioFormat erstellen, der vom Audioausgabegerät unterstützt wird.

Einen Track mit dem richtigen Format erstellen

Apps sollten ein AudioTrack erstellen, die Wiedergabe starten und getRoutedDevice() aufrufen, um das Standardaudiogerät zu bestimmen, über das der Ton abgespielt wird. Dies kann beispielsweise ein sicherer kurzer, PCM-codierter Track sein, der nur zur Bestimmung des gerouteten Geräts und seiner Audiofunktionen verwendet wird.

Unterstützte Codierungen abrufen

Verwenden Sie getAudioProfiles() (API-Level 31 und höher) oder getEncodings() (API-Level 23 und höher), um die auf dem Standardaudiogerät verfügbaren Audioformate zu bestimmen.

Unterstützte Audioprofile und ‐formate prüfen

Verwenden Sie AudioProfile (API-Level 31 und höher) oder isDirectPlaybackSupported() (API-Level 29 und höher), um unterstützte Kombinationen aus Format, Kanalanzahl und Abtastrate zu prüfen.

Einige Android-Geräte unterstützen andere Codierungen als die des Audioausgabegeräts. Diese zusätzlichen Formate sollten über isDirectPlaybackSupported() erkannt werden. In diesen Fällen werden die Audiodaten in ein Format umcodiert, das vom Audioausgabegerät unterstützt wird. Verwenden Sie isDirectPlaybackSupported(), um zu prüfen, ob das gewünschte Format unterstützt wird, auch wenn es nicht in der von getEncodings() zurückgegebenen Liste enthalten ist.

Antizipierende Audioroute

Mit Android 13 (API-Level 33) wurden antizipative Audiorouten eingeführt. Sie können mit der Unterstützung für Geräte-Audioattribute rechnen 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 transkodiert werden:

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 geordnet, dass das stärkste bevorzugte Element zuerst in der Liste und das schlechtste letzte erreicht wird. getDirectProfilesForAttributes() gibt eine Liste der unterstützten AudioProfile-Objekte für das derzeit weitergeleitete Audiogerät mit der bereitgestellten AudioAttributes zurück. Die Liste der bevorzugten AudioFormat-Elemente wird iteriert, bis eine übereinstimmende unterstützte AudioProfile gefunden wird. AudioProfile wird als bestAudioProfile gespeichert. Optimale Abtastraten und Kanalmasken werden aus bestAudioProfile ermittelt. Zum Schluss wird eine entsprechende AudioFormat-Instanz erstellt.

Audiotrack erstellen

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

Änderungen an Audiogeräten abfangen

Damit Apps Änderungen an Audiogeräten erfassen und darauf reagieren können, sollten sie Folgendes tun:

  • Fügen Sie für API-Level ab 24 einen OnRoutingChangedListener hinzu, um Änderungen an Audiogeräten (HDMI, Bluetooth usw.) zu überwachen.
  • Registrieren Sie für API-Level 23 einen AudioDeviceCallback, um Änderungen in der Liste der verfügbaren Audiogeräte zu erhalten.
  • Prüfen Sie bei API-Level 21 und 22 auf HDMI-Steckereignisse und verwenden Sie die zusätzlichen Daten aus den Broadcasts.
  • Registrieren Sie auch ein BroadcastReceiver, um Änderungen des BluetoothDevice-Status für Geräte unter API 23 zu beobachten, da AudioDeviceCallback noch nicht unterstützt wird.

Wenn für AudioTrack eine Änderung an einem Audiogerät erkannt wurde, sollte die App die aktualisierten Audiofunktionen prüfen und das AudioTrack bei Bedarf mit einer anderen AudioFormat neu erstellen. Das ist sinnvoll, wenn jetzt eine Codierung in höherer Qualität 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);