Audiofunktionen

Android TV unterstützt mehrere gleichzeitige Audioausgänge: TV-Lautsprecher, HDMI-Heimkino, Bluetooth und mehr. Diese Geräte unterstützen unterschiedliche Codierungen (z. B. Dolby Digital+, DTS, PCM), Sampleraten und Kanäle. Über HDMI verbundene Fernseher unterstützen viele Formate, über Bluetooth jedoch nur PCM.

Verfügbare Audiogeräte und das Routing können sich während der Wiedergabe ändern. Nutzer können HDMI-Geräte anschließen, Bluetooth-Verbindungen herstellen oder Einstellungen ändern. Apps müssen sich nahtlos an die Funktionen des neuen Geräts anpassen, um eine kontinuierliche Wiedergabe zu gewährleisten. Nicht unterstützte Formate können zu Fehlern oder Stille führen.

Bieten Sie mehrere Codierungen an, um je nach Gerätefunktionen die bestmögliche Wiedergabe zu ermöglichen. Dolby Digital wird beispielsweise verwendet, sofern unterstützt. Andernfalls greift die App auf PCM zurück. Informationen zu Android-Decodern, die Streams in PCM umwandeln, finden Sie unter Unterstützte Media-Formate.

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

Track im richtigen Format erstellen

Apps sollten ein AudioTrack erstellen, die Wiedergabe starten und getRoutedDevice() aufrufen, um das Standardaudiogerät zu ermitteln, über das der Ton wiedergegeben werden soll. Das kann beispielsweise ein PCM-codierter Track mit einer kurzen Stille 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 Abtastrate zu prüfen.

Einige Android-Geräte unterstützen Codierungen, 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 Ausgabegerät unterstützt wird. Verwenden Sie isDirectPlaybackSupported(), um die Unterstützung für das ausgewählte 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 Geräte-Audioattributen vorhersagen 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. Ausgenommen sind alle Profile, die nicht unterstützt werden oder beispielsweise vom Android-Framework transcodiert 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. Die Liste ist so sortiert, dass die bevorzugteste Sprache zuerst und die am wenigsten bevorzugte Sprache 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. Diese AudioProfile wird als bestAudioProfile gespeichert. Optimale Sampling-Raten und Channel-Masken werden aus bestAudioProfile abgeleitet. Schließlich wird eine geeignete AudioFormat-Instanz erstellt.

Audiotrack 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:

  • Bei API-Levels ab 24 fügen Sie 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-Levels 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-Zustandsä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);