Audiofunktionen

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

Die verfügbaren Audiogeräte und das Routing können sich während der Wiedergabe ändern: Nutzer können HDMI-Geräte im laufenden Betrieb anschließen, Bluetooth-Geräte verbinden oder Einstellungen ändern. Apps müssen sich reibungslos anpassen, um eine kontinuierliche Wiedergabe mit den Funktionen des neuen Geräts zu ermöglichen. Nicht unterstützte Formate können zu Fehlern oder Stille führen.

Bieten Sie mehrere Codierungen an, um je nach Gerätefunktionen die beste Nutzererfahrung zu ermöglichen. Beispiel: Dolby Digital wird verwendet, wenn es unterstützt wird. Andernfalls greift die App auf PCM zurück. Informationen zu Android-Decodern, die Streams in PCM umwandeln, findest du unter Unterstützte Medienformate.

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

Track im richtigen Format erstellen

Apps sollten einen AudioTrack erstellen, die Wiedergabe starten und getRoutedDevice() aufrufen, um das Standard-Audiogerät für die Wiedergabe zu ermitteln. Das kann beispielsweise ein kurzer, sicherer PCM-codierter Track ohne Ton sein, der nur dazu dient, das weitergeleitete Gerät und seine Audiofunktionen zu ermitteln.

Unterstützte Codierungen abrufen

Verwende getAudioProfiles() (API-Level 31 und höher) oder getEncodings() (API-Level 23 und höher), um die auf dem Standard-Audiogerät verfügbaren Audioformate zu ermitteln.

Unterstützte Audioprofile und -formate prüfen

Verwende 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 Codierungen, 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 umcodiert, das vom Audioausgabegerät unterstützt wird. Verwende isDirectPlaybackSupported(), um die Unterstützung für das ausgewählte Format ordnungsgemäß 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 antizipatorische Audiorouten eingeführt. Du kannst die Unterstützung von Audioattributen auf dem Gerät antizipieren und Tracks für das aktive Audiogerät vorbereiten. Mit getDirectPlaybackSupport() kannst du prüfen, ob die direkte Wiedergabe auf dem aktuell ausgewählten 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 geroutete Audiogerät unterstützt werden. Dies schließt alle Profile aus, 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 am meisten bevorzugte Option am Anfang und die am wenigsten bevorzugte Option am Ende steht. getDirectProfilesForAttributes() gibt eine Liste der unterstützten AudioProfile-Objekte für das aktuell geroutete Audiogerät mit dem angegebenen AudioAttributes zurück. Die Liste der bevorzugten AudioFormat-Elemente wird durchlaufen, bis ein übereinstimmendes unterstütztes AudioProfile gefunden wird. Diese AudioProfile wird als bestAudioProfile gespeichert. Die optimalen Abtastraten und Kanalmasken werden aus bestAudioProfile ermittelt. Schließlich wird eine entsprechende AudioFormat-Instanz erstellt.

Audiotrack erstellen

Apps sollten diese Informationen verwenden, um ein 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 Änderungen an Audiogeräten abfangen und darauf reagieren sollen, müssen sie Folgendes tun:

  • Füge für API-Level ab 24 einen OnRoutingChangedListener hinzu, um Änderungen an Audiogeräten (HDMI, Bluetooth usw.) zu überwachen.
  • Registriere für API-Level 23 einen AudioDeviceCallback, um Änderungen an der Liste der verfügbaren Audiogeräte zu erhalten.
  • Bei den API-Levels 21 und 22 solltest du auf HDMI-Plug-Ereignisse achten und die zusätzlichen Daten aus den Broadcasts verwenden.
  • Registrieren Sie außerdem einen BroadcastReceiver, um BluetoothDevice-Statusänderungen für Geräte mit einer API-Version unter 23 zu überwachen, da AudioDeviceCallback noch nicht unterstützt wird.

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