Funzionalità audio

I dispositivi Android TV possono avere più uscite audio collegate contemporaneamente: altoparlanti TV, home cinema connesso tramite HDMI, cuffie Bluetooth e così via. Questi dispositivi di output audio possono supportare diverse funzionalità audio, come codifiche (Dolby Digital+, DTS e PCM), frequenza di campionamento e canali. Ad esempio, le TV connesse tramite HDMI supportano una moltitudine di codifiche, mentre le cuffie Bluetooth connesse di solito supportano solo il PCM.

L'elenco dei dispositivi audio disponibili e il dispositivo audio indirizzato possono cambiare anche collegando i dispositivi HDMI a caldo, collegando o scollegando le cuffie Bluetooth o modificando le impostazioni audio dall'utente. Poiché le funzionalità di output audio possono cambiare anche durante la riproduzione di contenuti multimediali nelle app, le app devono adattarsi a questi cambiamenti e continuare la riproduzione sul nuovo dispositivo audio instradato e le relative funzionalità. La generazione di un formato audio sbagliato può causare errori o l'audio potrebbe non essere riprodotto.

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

In fase di riproduzione, l'app di streaming deve creare una AudioTrack con il miglior AudioFormat supportato dal dispositivo audio di output.

Crea una traccia con il formato giusto

Le app dovrebbero creare un AudioTrack, iniziare a riprodurlo e chiamare il numero getRoutedDevice() per stabilire il dispositivo audio predefinito da cui riprodurre l'audio. Può essere, ad esempio, una traccia codificata in PCM con silenzio breve e sicura, utilizzata solo per determinare il dispositivo instradato e le sue funzionalità audio.

Ottieni codifiche supportate

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

Controllare i profili e i formati audio supportati

Utilizza AudioProfile (livello API 31 e successivi) o isDirectPlaybackSupported() (livello API 29 e successivi) per verificare le combinazioni supportate di formato, conteggio 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 ricodificati in un formato supportato dal dispositivo audio di output. Utilizza isDirectPlaybackSupported() per verificare correttamente il supporto del formato desiderato anche se non è presente nell'elenco restituito da getEncodings().

Percorso audio anticipato

Android 13 (livello API 33) ha introdotto percorsi audio anticipatamente. Puoi prevedere il supporto degli attributi 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 multimediale diretta tramite il dispositivo audio attualmente instradato. Sono esclusi tutti i profili non supportati o che, ad esempio, sarebbero 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. Viene ordinato in base al più preferito dell'elenco e al meno preferito per ultimo. getDirectProfilesForAttributes() restituisce un elenco di oggetti AudioProfile supportati per il dispositivo audio attualmente instradato con l'oggetto AudioAttributes fornito. L'elenco di elementi AudioFormat preferiti viene ripetuto fino a quando non viene trovato un AudioProfile supportato corrispondente. Questo AudioProfile è memorizzato come bestAudioProfile. Le frequenze di campionamento e le maschere del canale ottimali sono stabilite a partire dal giorno bestAudioProfile. Infine, viene creata un'istanza AudioFormat appropriata.

Crea traccia audio

Le app devono usare queste informazioni per creare un'AudioTrack per la massima qualità AudioFormat supportata dal dispositivo audio predefinito (e disponibile per i contenuti selezionati).

Intercetta modifiche dispositivo audio

Per intercettare e reagire ai cambiamenti del dispositivo audio, le app devono:

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

Quando viene rilevata una modifica del dispositivo audio per AudioTrack, l'app deve verificare 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);