Funzionalità audio

I dispositivi Android TV possono avere contemporaneamente collegate più uscite audio: altiparlanti della TV, home cinema con connessione HDMI, cuffie Bluetooth e così via. Questi dispositivi di uscita audio possono supportare diverse funzionalità audio, come le codifiche (Dolby Digital+, DTS e PCM), la frequenza di campionamento e i canali. Ad esempio, le TV con connessione HDMI supportano una moltitudine di codifiche, mentre le cuffie Bluetooth collegate in genere supportano solo PCM.

L'elenco dei dispositivi audio disponibili e il dispositivo audio instradato possono cambiare anche collegando dispositivi HDMI tramite hot-plug, collegando o scollegano cuffie Bluetooth o modificando le impostazioni audio da parte dell'utente. Poiché le funzionalità di output audio possono cambiare anche quando le app riproducono contenuti multimediali, le app devono adattarsi a queste modifiche e continuare la riproduzione sul nuovo dispositivo audio instradato e sulle relative funzionalità. L'output del formato audio errato può comportare errori o la mancata riproduzione dell'audio.

Le app hanno la capacità di riprodurre gli stessi contenuti in più codifiche per offrire all'utente la migliore esperienza audio in base alle funzionalità del dispositivo audio. Ad esempio, se la TV lo supporta, viene riprodotto uno stream audio codificato Dolby Digital, mentre se non è supportato viene scelto uno stream audio PCM più supportato. L'elenco dei decodificatori Android integrati utilizzati per trasformare uno stream audio in PCM è disponibile in Formati multimediali supportati.

Al momento della riproduzione, l'app di streaming deve creare un AudioTrack con il migliore AudioFormat supportato dal dispositivo audio di output.

Creare una traccia con il formato giusto

Le app devono creare un AudioTrack, avviarne la riproduzione e chiamare getRoutedDevice() per determinare il dispositivo audio predefinito da cui riprodurre l'audio. Può essere, ad esempio, una traccia codificata PCM di silenzio breve sicura utilizzata solo per determinare il dispositivo instradato e le sue funzionalità audio.

Visualizzare le codifiche supportate

Utilizza getAudioProfiles() (livello API 31 e versioni successive) o getEncodings() (livello API 23 e versioni successive) per determinare i formati audio disponibili sul dispositivo audio predefinito.

Controllare i formati e i profili audio supportati

Utilizza AudioProfile (livello API 31 e versioni successive) o isDirectPlaybackSupported() (livello API 29 e versioni successive) per controllare le combinazioni supportate di formato, numero di canali e frequenza di campionamento.

Alcuni dispositivi Android sono in grado di supportare codifiche diverse da quelle supportate dal dispositivo audio di output. Questi formati aggiuntivi dovrebbero essere rilevati tramite isDirectPlaybackSupported(). In questi casi, i dati audio vengono nuovamente codificati in un formato supportato dal dispositivo di output audio. Utilizza isDirectPlaybackSupported() per verificare correttamente il supporto del formato desiderato anche se non è presente nell'elenco restituito da getEncodings().

Route audio anticipato

Android 13 (livello API 33) ha introdotto i percorsi audio anticipativi. Puoi anticipare il supporto dell'attributo audio del dispositivo e preparare le tracce per il dispositivo audio attivo. Puoi utilizzare getDirectPlaybackSupport() per verificare se la riproduzione diretta è supportata sul dispositivo audio attualmente instradato per un determinato formato e attributi:

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
}

In alternativa, puoi eseguire una query sui profili supportati per la riproduzione diretta dei contenuti multimediali tramite il dispositivo audio attualmente instradato. Sono esclusi i profili che non sono supportati o che, ad esempio, verrebbero transcodificati dal framework Android:

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 questo esempio, preferredFormats è un elenco di istanze AudioFormat. È ordinato con l'opzione più preferita per prima nell'elenco e l'opzione meno preferita per ultima. getDirectProfilesForAttributes() restituisce un elenco di oggetti AudioProfile supportati per il dispositivo audio attualmente instradato con il fornito AudioAttributes. L'elenco degli elementi AudioFormat preferiti viene esaminato fino a quando non viene trovato un AudioProfile supportato corrispondente. Questo AudioProfile viene archiviato come bestAudioProfile. Le frequenze di campionamento e le maschere di canale ottimali vengono determinate in base a bestAudioProfile. Infine, viene creata un'istanza AudioFormat appropriata.

Creare una traccia audio

Le app devono utilizzare queste informazioni per creare un AudioTrack per il AudioFormat di massima qualità supportato dal dispositivo audio predefinito (e disponibile per i contenuti selezionati).

Intercettare le modifiche al dispositivo audio

Per intercettare e reagire alle modifiche dei dispositivi audio, le app devono:

  • Per i livelli API pari o superiori a 24, aggiungi un OnRoutingChangedListener per monitorare le modifiche dei dispositivi audio (HDMI, Bluetooth e così via).
  • Per il livello API 23, registra un AudioDeviceCallback per ricevere le modifiche nell'elenco dei dispositivi audio disponibili.
  • Per i livelli API 21 e 22, monitora gli eventi di collegamento HDMI e utilizza i dati aggiuntivi delle trasmissioni.
  • Registra anche un BroadcastReceiver per monitorare le modifiche dello stato di BluetoothDevice per i dispositivi precedenti all'API 23, poiché AudioDeviceCallback non è ancora supportato.

Quando viene rilevata una modifica del dispositivo audio per il AudioTrack, l'app deve controllare le funzionalità audio aggiornate e, se necessario, ricreare il AudioTrack con un AudioFormat diverso. Esegui questa operazione se ora è supportata una codifica di qualità superiore o se la codifica utilizzata in precedenza non è più supportata.

Codice di esempio

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);