Os dispositivos Android TV podem ter várias saídas de áudio conectadas ao mesmo tempo: alto-falantes da 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 oferecem suporte a várias codificações, enquanto fones de ouvido Bluetooth conectados geralmente oferecem suporte apenas a PCM.
A lista de dispositivos de áudio disponíveis e o dispositivo de áudio roteado também podem mudar com a conexão a quente de dispositivos HDMI, a conexão ou desconexão de fones de ouvido Bluetooth ou a mudança das 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 nenhuma reprodução de som.
Os apps podem gerar o mesmo conteúdo em várias codificações para oferecer ao usuário a melhor experiência, dependendo dos recursos do dispositivo. Por exemplo, um fluxo de áudio codificado Dolby Digital é reproduzido se a TV tiver suporte a ele, enquanto um fluxo de áudio PCM com suporte mais amplo é escolhido quando não há suporte para Dolby Digital. A lista de decodificadores integrados do Android 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 uma
AudioTrack
com o melhor
AudioFormat
com suporte do 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 compatíveis
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 oferecem suporte a codificações além das
aceitas pelo dispositivo de áudio de saída. Esses formatos adicionais precisam ser
detectados pelo isDirectPlaybackSupported()
. Nesses casos, os dados de áudio são recodificados para um formato compatível com o 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) lançou 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 correspondente seja encontrado. Este AudioProfile
está 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).
Intercepção de 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 deBluetoothDevice
para dispositivos anteriores à API 23, já que oAudioDeviceCallback
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);