Android TV supporta più uscite audio simultanee: altoparlanti della TV, home cinema HDMI, Bluetooth e altro ancora. Questi dispositivi supportano varie 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 e il routing disponibili possono cambiare durante la riproduzione: gli utenti potrebbero collegare a caldo l'HDMI, connettere il Bluetooth o modificare le impostazioni. Le app devono adattarsi in modo ottimale per garantire la riproduzione continua utilizzando le nuove funzionalità del dispositivo. I formati non supportati potrebbero causare errori o silenzio.
Offri più codifiche per la migliore esperienza in base alle funzionalità del dispositivo. Ad esempio, se supportato, viene utilizzato Dolby Digital; in caso contrario, l'app torna a PCM. Consulta 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 una traccia con il formato corretto
Le app devono creare un AudioTrack, iniziare a riprodurlo e chiamare
getRoutedDevice() per determinare il dispositivo audio predefinito da cui
riprodurre il suono. Ad esempio, può trattarsi di una traccia codificata PCM di breve silenzio sicuro
utilizzata solo per determinare il dispositivo di routing e le relative 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 profili e i formati 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 devono 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 scelto
anche se non è presente nell'elenco restituito da getEncodings().
Route audio anticipato
Android 13 (livello API 33) ha introdotto le route audio predittive. 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 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 per scoprire quali profili sono supportati per la riproduzione diretta di contenuti multimediali tramite il dispositivo audio attualmente instradato. Sono esclusi i profili non supportati o, ad esempio, 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.
È ordinato in modo che il più preferito sia il primo della lista e il meno preferito l'ultimo. getDirectProfilesForAttributes() restituisce un elenco di oggetti
AudioProfile supportati per il dispositivo audio attualmente instradato con il
AudioAttributes fornito. L'elenco degli elementi AudioFormat preferiti viene
iterato finché non viene trovato un AudioProfile supportato corrispondente. Questo
AudioProfile è 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 al dispositivo audio
Per intercettare e reagire alle modifiche del dispositivo audio, le app devono:
- Per i livelli API pari o superiori a 24, aggiungi un
OnRoutingChangedListenerper monitorare le modifiche al dispositivo audio (HDMI, Bluetooth e così via). - Per il livello API 23, registra un
AudioDeviceCallbackper ricevere le modifiche nell'elenco dei dispositivi audio disponibili. - Per i livelli API 21 e 22, monitora gli eventi di inserimento HDMI e utilizza i dati aggiuntivi delle trasmissioni.
- Registra anche un
BroadcastReceiverper monitorare le modifiche dello stato diBluetoothDeviceper i dispositivi con API precedenti alla 23, poichéAudioDeviceCallbacknon è 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);