Recursos de áudio

Os dispositivos Android TV podem ter várias saídas de áudio conectadas ao mesmo tempo: alto-falantes de TV, home theater conectado por HDMI, fones de ouvido Bluetooth e assim por diante. Esses dispositivos de saída de áudio podem oferecer suporte a diferentes recursos de áudio, como codificações (Dolby Digital+, DTS e PCM), taxa de amostragem e canais. Por exemplo, TVs conectadas por HDMI têm suporte para várias codificações, enquanto fones de ouvido Bluetooth conectados geralmente oferecem suporte apenas para PCM.

A lista de dispositivos de áudio disponíveis e o dispositivo de áudio roteado também podem mudar com a conexão direta de dispositivos HDMI, a conexão ou desconexão de fones de ouvido Bluetooth ou a mudança de configurações de áudio pelo usuário. Como os recursos de saída de áudio podem mudar mesmo quando os apps estão reproduzindo mídia, eles precisam se adaptar a essas mudanças e continuar a reprodução no novo dispositivo de áudio roteado e nos recursos dele. A saída do formato de áudio errado pode resultar em erros ou na não reprodução de som.

Os apps têm a capacidade de gerar o mesmo conteúdo em várias codificações para oferecer ao usuário a melhor experiência de áudio, dependendo dos recursos do dispositivo de áudio. Por exemplo, um stream de áudio codificado Dolby Digital é reproduzido se a TV tiver suporte a ele, enquanto um stream de áudio PCM com suporte mais amplo é escolhido quando não há suporte para Dolby Digital. A lista de decodificadores Android integrados usados para transformar um stream de áudio em PCM pode ser encontrada em Formatos de mídia compatíveis.

No momento da reprodução, o app de streaming precisa criar um AudioTrack com o melhor AudioFormat compatível com o dispositivo de áudio de saída.

Criar uma faixa com o formato correto

Os apps precisam criar uma AudioTrack, iniciar a reprodução e chamar getRoutedDevice() para determinar o dispositivo de áudio padrão em que o som será reproduzido. Por exemplo, pode ser uma faixa PCM codificada de silêncio curto e segura usada apenas para determinar o dispositivo roteado e os recursos de áudio dele.

Codificações com suporte

Use getAudioProfiles() (nível 31 da API e mais recente) ou getEncodings() (nível 23 da API e mais recente) para determinar os formatos de áudio disponíveis no dispositivo de áudio padrão.

Verificar os formatos e perfis de áudio aceitos

Use AudioProfile (nível 31 da API e mais recente) ou isDirectPlaybackSupported() (nível 29 da API e mais recente) para verificar as combinações de formato, contagem de canais e taxa de amostragem compatíveis.

Alguns dispositivos Android são capazes de oferecer suporte a codificações além das oferecidas pelo dispositivo de áudio de saída. Esses formatos adicionais precisam ser detectados pelo isDirectPlaybackSupported(). Nesses casos, os dados de áudio são codificados novamente em um formato aceito pelo dispositivo de áudio de saída. Use isDirectPlaybackSupported() para verificar corretamente o suporte ao formato desejado, mesmo que ele não esteja presente na lista retornada por getEncodings().

Rota de áudio antecipada

O Android 13 (nível 33 da API) introduziu rotas de áudio antecipadas. Você pode antecipar o suporte ao atributo de áudio do dispositivo e preparar faixas para o dispositivo de áudio ativo. Use getDirectPlaybackSupport() para verificar se a reprodução direta é compatível com o dispositivo de áudio roteado no momento para um determinado formato e atributos:

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, você pode consultar quais perfis são compatíveis com a reprodução de mídia direta pelo dispositivo de áudio roteado no momento. Isso exclui todos os perfis que não têm suporte ou que seriam, por exemplo, transcodificados pelo framework do 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();
}

Neste exemplo, preferredFormats é uma lista de instâncias de AudioFormat. Ela é ordenada com a opção mais preferida em primeiro lugar na lista e a menos preferida em último. getDirectProfilesForAttributes() retorna uma lista de objetos AudioProfile compatíveis para o dispositivo de áudio roteado no momento com o AudioAttributes fornecido. A lista de itens AudioFormat preferidos é iterada até que um AudioProfile compatível seja encontrado. Esse AudioProfile é armazenado como bestAudioProfile. As taxas de amostragem e máscaras de canal ideais são determinadas a partir de bestAudioProfile. Por fim, uma instância AudioFormat adequada é criada.

Criar faixa de áudio

Os apps precisam usar essas informações para criar um AudioTrack para o AudioFormat de maior qualidade aceito pelo dispositivo de áudio padrão (e disponível para o conteúdo selecionado).

Interceptar mudanças no dispositivo de áudio

Para interceptar e reagir a mudanças no dispositivo de áudio, os apps precisam:

  • Para níveis da API iguais ou superiores a 24, adicione um OnRoutingChangedListener para monitorar mudanças no dispositivo de áudio (HDMI, Bluetooth etc.).
  • Para o nível 23 da API, registre um AudioDeviceCallback para receber mudanças na lista de dispositivos de áudio disponíveis.
  • Para os níveis 21 e 22 da API, monitore eventos de plugue HDMI e use os dados extras das transmissões.
  • Registre também um BroadcastReceiver para monitorar mudanças de estado de BluetoothDevice para dispositivos anteriores à API 23, já que o AudioDeviceCallback ainda não tem suporte.

Quando uma mudança de dispositivo de áudio for detectada para o AudioTrack, o app vai verificar os recursos de áudio atualizados e, se necessário, recriar o AudioTrack com um AudioFormat diferente. Faça isso se uma codificação de qualidade superior tiver suporte ou se a codificação usada anteriormente não tiver mais suporte.

Exemplo de código

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