Audiofunktionen

Auf Android TV-Geräten können mehrere Audioausgänge gleichzeitig verbunden sein, z. B. TV-Lautsprecher, über HDMI angeschlossenes Heimkino oder Bluetooth-Kopfhörer. Diese Audioausgabegeräte können unterschiedliche Audiofunktionen unterstützen, z. B. Codierungen (Dolby Digital+, DTS und PCM), Abtastrate und Kanäle. HDMI-fähige Fernseher unterstützen beispielsweise eine Vielzahl von Codierungen, während 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 im laufenden Betrieb angeschlossen oder getrennt werden, Bluetooth-Kopfhörer verbunden oder getrennt werden oder der Nutzer die Audioeinstellungen ändert. Da sich die Audioausgabefunktionen auch während der Medienwiedergabe ändern können, müssen Apps sich an diese Änderungen anpassen und die Wiedergabe auf dem neuen Audiogerät und mit seinen Funktionen fortsetzen. Wenn das falsche Audioformat ausgegeben wird, kann es zu Fehlern kommen oder es wird kein Ton wiedergegeben.

Apps können dieselben Inhalte in mehreren Codierungen ausgeben, um dem Nutzer je nach den Funktionen des Audiogeräts die beste Audioqualität zu bieten. Wenn der Fernseher Dolby Digital unterstützt, wird beispielsweise ein mit Dolby Digital codierter Audiostream wiedergegeben. Wenn Dolby Digital nicht unterstützt wird, wird ein PCM-Audiostream ausgewählt, der häufiger unterstützt wird. Die Liste der integrierten Android-Decodierer, die zum Transformieren eines Audio-Streams in PCM verwendet werden, finden Sie unter Unterstützte Media-Formate.

Bei der Wiedergabe sollte die Streaming-App einen AudioTrack mit dem besten AudioFormat erstellen, das vom Audioausgabegerät unterstützt wird.

Titel im richtigen Format erstellen

Apps sollten ein AudioTrack erstellen, die Wiedergabe starten und getRoutedDevice() aufrufen, um das Standardaudiogerät zu ermitteln, auf dem der Ton wiedergegeben werden soll. Das kann beispielsweise ein sicherer, kurzer, PCM-codierter Stille-Track sein, der nur dazu dient, das weitergeleitete Gerät und seine Audiofunktionen zu ermitteln.

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 ermitteln.

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, Anzahl der Kanäle und Samplerate zu prüfen.

Einige Android-Geräte können Codierungen unterstützen, die über die vom Ausgabegerät unterstützten Codierungen hinausgehen. Diese zusätzlichen Formate sollten über isDirectPlaybackSupported() erkannt werden. In diesen Fällen werden die Audiodaten in ein Format neu codiert, das vom Audio-Ausgabegerät unterstützt wird. Verwenden Sie isDirectPlaybackSupported(), um die Unterstützung für das gewünschte Format richtig 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 Audio-Routen eingeführt. Sie können die Unterstützung von Audioattributen für Geräte vorhersagen und Titel für das aktive Audiogerät vorbereiten. Mit getDirectPlaybackSupport() können Sie 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 weitergeleitete Audiogerät unterstützt werden. Dies schließt alle Profile aus, die nicht unterstützt werden oder beispielsweise vom Android-Framework transcodiert 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 bevorzugteste Option zuerst und die am wenigsten bevorzugte Option zuletzt aufgeführt wird. getDirectProfilesForAttributes() gibt eine Liste der unterstützten AudioProfile-Objekte für das aktuell weitergeleitete Audiogerät mit dem angegebenen AudioAttributes zurück. Die Liste der bevorzugten AudioFormat-Elemente wird durchlaufen, bis ein passendes unterstütztes AudioProfile gefunden wird. Dieser AudioProfile wird als bestAudioProfile gespeichert. Optimale Stichprobenraten und Channel-Masken werden aus bestAudioProfile abgeleitet. Schließlich wird eine geeignete AudioFormat-Instanz erstellt.

Audio-Track erstellen

Apps sollten diese Informationen verwenden, um ein 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 des Audiogeräts abfangen

Um Audiogeräteänderungen abzufangen und darauf zu reagieren, sollten Apps Folgendes tun:

  • Fügen Sie für API-Levels ab 24 ein OnRoutingChangedListener hinzu, um Änderungen am Audiogerät (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.
  • Bei API-Level 21 und 22 müssen Sie HDMI-Plug-Ereignisse überwachen und die zusätzlichen Daten aus den Broadcasts verwenden.
  • Registrieren Sie außerdem einen BroadcastReceiver, um BluetoothDevice-Statusänderungen für Geräte unter API 23 zu überwachen, da AudioDeviceCallback noch nicht unterstützt wird.

Wenn eine Änderung des Audiogeräts für AudioTrack erkannt wurde, sollte die App die aktualisierten Audiofunktionen prüfen und bei Bedarf AudioTrack mit einem anderen AudioFormat neu erstellen. Das ist sinnvoll, wenn eine hochwertigere Codierung 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);