Capacidades de audio

Los dispositivos Android TV pueden tener varias salidas de audio conectadas al mismo tiempo: bocinas de 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), la tasa de muestreo y los canales. Por ejemplo, las TVs conectadas por HDMI admiten una gran variedad de codificaciones, mientras que los auriculares Bluetooth conectados, por lo general, solo admiten PCM.

La lista de dispositivos de audio disponibles y el dispositivo de audio enrutado también puede cambiar si se conectan dispositivos HDMI en caliente, se conectan o desconectan auriculares Bluetooth, 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, estas deben adaptarse correctamente a estos cambios y continuar con la reproducción en el nuevo dispositivo de audio enrutado y sus capacidades. Si transmites un formato de audio incorrecto, se pueden producir errores o no se reproduce sonido.

Las apps tienen la capacidad de generar el mismo contenido en varias codificaciones para ofrecer al usuario la mejor experiencia de audio según las capacidades del dispositivo de audio. Por ejemplo, se reproduce una transmisión de audio codificada en Dolby Digital si la TV lo admite, mientras que se elige una transmisión de audio PCM más compatible cuando no se admite Dolby Digital. Puedes encontrar la lista de decodificadores integrados de Android que se usan para transformar una transmisión de audio en PCM en Formatos multimedia compatibles.

En el momento de la reproducción, la app de transmisión debe crear un AudioTrack con el mejor AudioFormat compatible con 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. Puede ser, por ejemplo, una pista segura codificada en PCM de silencio corto que se usa solo para determinar el dispositivo enrutado y sus capacidades de audio.

Cómo obtener 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 tasa de muestreo.

Algunos dispositivos Android son capaces de admitir codificaciones más allá de las que admite el dispositivo de audio de salida. Estos formatos adicionales deben detectarse 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 anticipatorias. Puedes anticipar la compatibilidad con atributos de audio del dispositivo y preparar pistas para el dispositivo de audio activo. Puedes usar getDirectPlaybackSupport() para comprobar si la reproducción directa es compatible con el dispositivo de audio enrutado en ese momento 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 para la reproducción directa de contenido multimedia a través del dispositivo de audio enrutado en ese momento. Esto excluye los perfiles que no son compatibles o que podrían transcodificarse, por ejemplo, por 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 AudioFormat. Se ordena con las más preferidas primero de la lista y las menos preferidas al final. getDirectProfilesForAttributes() muestra una lista de objetos AudioProfile compatibles para el dispositivo de audio enrutado en el momento con el AudioAttributes proporcionado. La lista de elementos AudioFormat preferidos se itera hasta que se encuentra un AudioProfile compatible que coincida. Este AudioProfile está almacenado 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.

Crear pista de audio

Las apps deben usar esta información para crear un AudioTrack para el AudioFormat de mayor calidad compatible con el dispositivo de audio predeterminado (y disponible para el contenido seleccionado).

Cómo interceptar los cambios en el dispositivo de audio

Para interceptar y reaccionar a los cambios en los dispositivos de audio, las apps deben hacer lo siguiente:

  • En el caso de niveles de API iguales o superiores a 24, agrega un OnRoutingChangedListener para supervisar los cambios en el dispositivo de audio (HDMI, Bluetooth, etcétera).
  • En el nivel de API 23, registra un AudioDeviceCallback para recibir los cambios en la lista de dispositivos de audio disponibles.
  • En el caso de los niveles de API 21 y 22, supervisa los eventos de conector HDMI y usa los datos adicionales de las transmisiones.
  • Además, registra un BroadcastReceiver para supervisar los cambios de estado de BluetoothDevice en dispositivos anteriores a la API 23, ya que aún no se admite AudioDeviceCallback.

Cuando se detecta un cambio en el dispositivo de audio para AudioTrack, la app debe verificar las capacidades de audio actualizadas y, si es necesario, volver a crear el AudioTrack con un AudioFormat diferente. Haz esto si ahora se admite una codificación de mayor calidad o si la codificación usada anteriormente deja de ser compatible.

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);