Android TV admite varias salidas de audio simultáneas: bocinas de TV, cine en casa con HDMI, Bluetooth y mucho más. Estos dispositivos admiten diferentes codificaciones (como Dolby Digital+, DTS y PCM), tasas de muestreo y canales. Las TVs conectadas por HDMI admiten muchos formatos, pero Bluetooth solo admite PCM.
Los dispositivos de audio disponibles y el enrutamiento pueden cambiar durante la reproducción: los usuarios pueden conectar un cable HDMI, conectar un dispositivo Bluetooth o modificar la configuración. Las apps deben adaptarse correctamente para garantizar la reproducción continua con las capacidades del dispositivo nuevo. Los formatos no compatibles pueden causar errores o silencio.
Ofrece varias codificaciones para brindar la mejor experiencia según las capacidades del dispositivo. Por ejemplo, se usa Dolby Digital si es compatible; de lo contrario, la app recurre a PCM. Consulta los formatos de contenido multimedia compatibles para conocer los decodificadores de Android que transforman las transmisiones en PCM.
En el momento de la reproducción, tu app de transmisión debe crear un objeto AudioTrack con el mejor AudioFormat que admita el dispositivo de audio de salida.
Crea una pista con el formato correcto
Las apps deben crear un 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 segura con codificación PCM de silencio breve que se use solo para determinar el dispositivo enrutado y sus capacidades de audio.
Obtén las 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.
Verifica los perfiles y formatos 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 tasa 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 elegido, incluso si no está presente en la lista que muestra getEncodings().
Ruta de audio anticipada
En Android 13 (nivel de API 33), se introdujeron las rutas de audio anticipadas. Puedes anticipar la compatibilidad con los atributos de audio del dispositivo y preparar las pistas para el dispositivo de audio activo. Puedes usar getDirectPlaybackSupport() para verificar si se admite la reproducción directa en el dispositivo de audio enrutado 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 son compatibles con la reproducción directa de contenido multimedia a través del dispositivo de audio enrutado actualmente. Esto excluye cualquier perfil que no sea compatible o que, por ejemplo, se transcodifique con 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 el más preferido primero en la lista y el menos preferido
al final. getDirectProfilesForAttributes() devuelve una lista de objetos AudioProfile compatibles para el dispositivo de audio enrutado actualmente con el AudioAttributes proporcionado. Se itera la lista de elementos AudioFormat preferidos hasta que se encuentra un AudioProfile compatible que coincida. Este AudioProfile se almacena como bestAudioProfile. Las tasas de muestreo y las máscaras de canal óptimas se determinan a partir de bestAudioProfile. Por último, se crea una instancia
AudioFormat adecuada.
Crea una pista de audio
Las apps deben usar esta información para crear un AudioTrack para el AudioFormat de mayor calidad que admita el dispositivo de audio predeterminado (y que esté disponible para el contenido seleccionado).
Cómo interceptar cambios en el dispositivo de audio
Para interceptar los cambios en el dispositivo de audio y reaccionar a ellos, las apps deben hacer lo siguiente:
- Para los niveles de API iguales o superiores a 24, agrega un
OnRoutingChangedListenerpara supervisar los cambios en el dispositivo de audio (HDMI, Bluetooth, etcétera). - Para el nivel de API 23, registra un
AudioDeviceCallbackpara recibir cambios en la lista de dispositivos de audio disponibles. - En 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
BroadcastReceiverpara supervisar los cambios de estado deBluetoothDeviceen dispositivos con un nivel de API inferior a 23, ya que aún no se admiteAudioDeviceCallback.
Cuando se detecte un cambio en el dispositivo de audio para el AudioTrack, la app debería 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 ya no se admite la codificación que se usaba antes.
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);