Funzionalità audio

Android TV supporta più uscite audio simultanee: altoparlanti della TV, home cinema HDMI, Bluetooth e altro ancora. Questi dispositivi supportano diverse codifiche (come Dolby Digital+, DTS, PCM), frequenze di campionamento e canali. Le TV connesse tramite HDMI supportano molti formati, ma il Bluetooth supporta solo PCM.

I dispositivi audio disponibili e il routing possono cambiare durante la riproduzione: gli utenti potrebbero collegare l'HDMI, connettere il Bluetooth o modificare le impostazioni. Le app devono adattarsi correttamente per garantire una riproduzione continua utilizzando le funzionalità del nuovo dispositivo. I formati non supportati potrebbero causare errori o silenzio.

Offri più codifiche per un'esperienza ottimale in base alle funzionalità del dispositivo. Ad esempio, Dolby Digital viene utilizzato se supportato; in caso contrario, l'app torna a PCM. Consulta la sezione Formati multimediali supportati per i decoder Android che trasformano gli stream in PCM.

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

Crea un canale 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. Ad esempio, può essere una traccia sicura con codifica PCM di breve silenzio utilizzata solo per determinare il dispositivo indirizzato e le sue funzionalità audio.

Ottieni 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.

Controlla 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 uscita. Questi formati aggiuntivi devono essere rilevati tramite isDirectPlaybackSupported(). In questi casi, i dati audio vengono ricodificati in un formato supportato dal dispositivo audio di uscita. Utilizza isDirectPlaybackSupported() per verificare correttamente il supporto del formato scelto, anche se non è presente nell'elenco restituito da getEncodings().

Route audio anticipatoria

Android 13 (livello API 33) ha introdotto i percorsi audio anticipatori. Puoi anticipare 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 indirizzato per un determinato formato e determinati 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 per sapere quali profili sono supportati per la riproduzione diretta di contenuti multimediali tramite il dispositivo audio attualmente utilizzato. Sono esclusi i profili non supportati o, ad esempio, quelli che 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. L'ordine è dal più preferito al meno preferito. getDirectProfilesForAttributes() restituisce un elenco di oggetti AudioProfile supportati per il dispositivo audio attualmente indirizzato con l'oggetto AudioAttributes fornito. L'elenco degli elementi AudioFormat preferiti viene ripetuto fino a trovare un AudioProfile supportato corrispondente. Questo AudioProfile viene archiviato come bestAudioProfile. Le frequenze di campionamento e le maschere dei canali ottimali vengono determinate da bestAudioProfile. Infine, viene creata un'istanza AudioFormat appropriata.

Crea traccia audio

Le app devono utilizzare queste informazioni per creare un AudioTrack per la AudioFormat di qualità più elevata supportata dal dispositivo audio predefinito (e disponibile per i contenuti selezionati).

Intercettare le modifiche ai dispositivi audio

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

  • Per i livelli API uguali o superiori a 24, aggiungi un OnRoutingChangedListener per monitorare le modifiche ai 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 allo stato di BluetoothDevice per i dispositivi con API precedente alla 23, poiché AudioDeviceCallback non è ancora supportato.

Quando viene rilevata una modifica del dispositivo audio per AudioTrack, l'app deve controllare le funzionalità audio aggiornate e, se necessario, ricreare 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);