Audiofunktionen

Android TV unterstützt mehrere gleichzeitige Audioausgaben: TV-Lautsprecher, HDMI-Heimkino, Bluetooth und mehr. Diese Geräte unterstützen unterschiedliche Codierungen (z. B. Dolby Digital+, DTS, PCM), Abtastraten und Kanäle. Über HDMI verbundene Fernseher unterstützen viele Formate, 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-Geräte verbinden oder Einstellungen ändern. Apps müssen sich anpassen, um eine kontinuierliche Wiedergabe mit den Funktionen des neuen Geräts zu gewährleisten. Nicht unterstützte Formate können zu Fehlern oder Stille führen.

Bieten Sie mehrere Codierungen an, um die bestmögliche Nutzererfahrung zu erzielen. Dolby Digital wird beispielsweise verwendet, wenn es unterstützt wird. Andernfalls greift die App auf PCM zurück. Unterstützte Media-Formate finden Sie Informationen zu Android-Decodern, die Streams in PCM umwandeln.

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

Track mit dem richtigen Format erstellen

Apps sollten einen AudioTrack erstellen, die Wiedergabe starten und getRoutedDevice() aufrufen, um das Standard-Audiogerät zu bestimmen, über das der Ton wiedergegeben werden soll. Das kann beispielsweise ein sicherer, kurzer, stummer PCM-codierter Track sein, der nur verwendet wird, um das weitergeleitete Gerät und seine Audiofunktionen zu bestimmen.

Unterstützte Codierungen abrufen

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

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 können Codierungen unterstützen, die über die vom Audioausgabegerä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 Audioausgabegerä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.

Antizipatives Audio-Routing

Mit Android 13 (API-Level 33) wurden antizipative Audio-Routen eingeführt. Sie können die Unterstützung für Audioattribute von Geräten antizipieren und Tracks für das aktive Audiogerät vorbereiten. Mit getDirectPlaybackSupport() können Sie prüfen, ob die direkte Wiedergabe auf dem derzeit 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 derzeit weitergeleitete Audiogerät unterstützt werden. Dabei werden alle Profile ausgeschlossen, 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 das bevorzugte Format zuerst und das am wenigsten bevorzugte Format zuletzt aufgeführt ist. getDirectProfilesForAttributes() gibt eine Liste der unterstützten AudioProfile-Objekte für das derzeit weitergeleitete Audiogerät mit den angegebenen AudioAttributes zurück. Die Liste der bevorzugten AudioFormat-Elemente wird durchlaufen, bis ein passendes unterstütztes AudioProfile gefunden wird. Dies AudioProfile wird als bestAudioProfile gespeichert. Optimale Abtastraten und Kanalmasken werden aus bestAudioProfile bestimmt. Schließlich wird eine geeignete AudioFormat-Instanz erstellt.

Audiotrack erstellen

Apps sollten diese Informationen verwenden, um einen AudioTrack für das AudioFormat mit der höchsten Qualität zu erstellen, das vom Standard-Audiogerät unterstützt wird (und für die ausgewählten Inhalte verfügbar ist).

Änderungen am Audiogerät abfangen

Wenn Apps auf Änderungen am Audiogerät reagieren sollen, müssen sie Folgendes tun:

  • Fügen Sie für API-Level 24 oder höher einen OnRoutingChangedListener hinzu, um Änderungen am Audiogerät zu beobachten (HDMI, Bluetooth usw.).
  • Registrieren Sie für API-Level 23 einen AudioDeviceCallback, um Änderungen an der Liste der verfügbaren Audiogeräte zu erhalten.
  • Beobachten Sie für API-Level 21 und 22 HDMI-Plug-Ereignisse und verwenden Sie die zusätzlichen Daten aus den Broadcasts.
  • Registrieren Sie außerdem einen BroadcastReceiver, um Statusänderungen von BluetoothDevice für Geräte mit einem niedrigeren API-Level als 23 zu beobachten, da AudioDeviceCallback noch nicht unterstützt wird.

Wenn eine Änderung am Audiogerät für den AudioTrack erkannt wurde, sollte die App die aktualisierten Audiofunktionen prüfen und gegebenenfalls den AudioTrack mit einem anderen AudioFormat neu erstellen. Tun Sie dies, wenn jetzt eine Codierung mit 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);