Los dispositivos Android TV pueden tener varias salidas de audio conectadas al mismo tiempo: bocinas de la TV, cine en casa conectado por HDMI, auriculares Bluetooth, etcétera. Estos dispositivos de salida de audio pueden admitir diferentes capacidades de audio, como codificaciones (Dolby Digital+, DTS y PCM), frecuencia de muestreo y canales. Por ejemplo, las TVs conectadas por HDMI admiten una gran cantidad de codificaciones, mientras que los auriculares Bluetooth conectados suelen admitir solo PCM.
La lista de dispositivos de audio disponibles y el dispositivo de audio enrutado también pueden cambiar si se conectan o desconectan auriculares Bluetooth, si se conectan dispositivos HDMI con conexión en caliente o si el usuario cambia la configuración de audio. Dado que las capacidades de salida de audio pueden cambiar incluso cuando las apps reproducen contenido multimedia, las apps deben adaptarse correctamente a estos cambios y continuar la reproducción en el nuevo dispositivo de audio enrutado y sus capacidades. Si se genera el formato de audio incorrecto, es posible que se produzcan errores o que no se reproduzca el sonido.
Las apps pueden generar el mismo contenido en varias codificaciones para ofrecerle al usuario la mejor experiencia de audio según las capacidades del dispositivo de audio. Por ejemplo, si la TV lo admite, se reproduce un flujo de audio codificado en Dolby Digital, mientras que, si no es compatible con Dolby Digital, se elige un flujo de audio PCM más ampliamente admitido. En Formatos de medios compatibles, puedes encontrar la lista de decodificadores integrados de Android que se usan para transformar una transmisión de audio en PCM.
En el momento de la reproducción, la app de transmisión debe crear un objeto AudioTrack
con el objeto AudioFormat
más adecuado que admita el dispositivo de audio de salida.
Crea una pista con el formato correcto
Las apps deben crear un objeto AudioTrack
, comenzar a reproducirlo y llamar a getRoutedDevice()
para determinar el dispositivo de audio predeterminado desde el que se reproducirá el sonido.
Por ejemplo, puede ser una pista codificada en PCM de silencio corto y seguro que se usa solo para determinar el dispositivo de enrutamiento y sus capacidades de audio.
Obtén codificaciones compatibles
Usa getAudioProfiles()
(nivel de API 31 y versiones posteriores) o getEncodings()
(nivel de API 23 y versiones posteriores) para determinar los formatos de audio disponibles en el dispositivo de audio predeterminado.
Cómo verificar los formatos y perfiles de audio compatibles
Usa AudioProfile
(nivel de API 31 y versiones posteriores) o isDirectPlaybackSupported()
(nivel de API 29 y versiones posteriores) para verificar las combinaciones admitidas de formato, recuento de canales y frecuencia de muestreo.
Algunos dispositivos Android pueden admitir codificaciones más allá de las que admite el dispositivo de audio de salida. Estos formatos adicionales se deben detectar a través de isDirectPlaybackSupported()
. En estos casos, los datos de audio se vuelven a codificar en un formato compatible con el dispositivo de audio de salida. Usa isDirectPlaybackSupported()
para verificar correctamente la compatibilidad con el formato deseado, incluso si no está presente en la lista que muestra getEncodings()
.
Ruta de audio anticipada
Android 13 (nivel de API 33) introdujo rutas de audio anticipadas. Puedes anticipar la compatibilidad con los atributos de audio del dispositivo y preparar pistas para el dispositivo de audio activo. Puedes usar getDirectPlaybackSupport()
para verificar si la reproducción directa es compatible con el dispositivo de audio al que se dirige el audio actualmente para un formato y atributos determinados:
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 }
Como alternativa, puedes consultar qué perfiles se admiten para la reproducción directa de contenido multimedia a través del dispositivo de audio que se está enrutando actualmente. Esto excluye los perfiles que no son compatibles o que, por ejemplo, transcodificaría el framework de 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(); }
En este ejemplo, preferredFormats
es una lista de instancias de AudioFormat
. Se ordena con la opción más preferida primero en la lista y la menos preferida al final.
getDirectProfilesForAttributes()
devuelve una lista de objetos AudioProfile
compatibles para el dispositivo de audio actualmente enrutado con el AudioAttributes
proporcionado. Se itera la lista de elementos AudioFormat
preferidos hasta que se encuentra un AudioProfile
compatible coincidente. Este AudioProfile
se almacena como bestAudioProfile
.
Las tasas de muestreo y las máscaras de canales óptimas se determinan a partir de bestAudioProfile
.
Por último, se crea una instancia de AudioFormat
adecuada.
Crea una pista de audio
Las apps deben usar esta información para crear un objeto AudioTrack
para el objeto AudioFormat
de mayor calidad que admita el dispositivo de audio predeterminado (y que esté disponible para el contenido seleccionado).
Cómo interceptar los cambios en el dispositivo de audio
Para interceptar los cambios en los dispositivos de audio y reaccionar a ellos, las apps deben hacer lo siguiente:
- Para los niveles de API iguales o superiores a 24, agrega un
OnRoutingChangedListener
para supervisar los cambios en los dispositivos de audio (HDMI, Bluetooth, etcétera). - Para el nivel de API 23, registra un
AudioDeviceCallback
para recibir cambios en la lista de dispositivos de audio disponibles. - Para los niveles de API 21 y 22, supervisa los eventos de conexión HDMI y usa los datos adicionales de las transmisiones.
- También registra un
BroadcastReceiver
para supervisar los cambios de estado deBluetoothDevice
en dispositivos con un nivel de API inferior a 23, ya queAudioDeviceCallback
aún no se admite.
Cuando se detecta un cambio en el dispositivo de audio para el AudioTrack
, la app debe verificar las capacidades de audio actualizadas y, si es necesario, volver a crear el AudioTrack
con un AudioFormat
diferente. Hazlo si ahora se admite una codificación de mayor calidad o si la codificación que se usaba anteriormente ya no se admite.
Código de muestra
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);