Os dispositivos Android TV podem ter várias saídas de áudio conectadas ao mesmo tempo: alto-falantes da TV, home cinema conectado por HDMI, fones de ouvido com 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, as TVs conectadas por HDMI oferecem suporte a várias codificações, enquanto os fones de ouvido com 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 ao conectar dispositivos HDMI, conectar ou desconectar fones de ouvido com Bluetooth ou quando o usuário muda as configurações de áudio. 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 ausência 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 em Dolby Digital é reproduzido se a TV oferecer 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 integrados do Android usados para transformar um stream de áudio em PCM pode ser encontrada em Formatos de mídia com suporte.
No momento da reprodução, o app de streaming precisa criar um
AudioTrack com o melhor
AudioFormat com suporte do dispositivo
de áudio de saída.
Criar uma faixa com o formato certo
Os apps precisam criar um AudioTrack, começar a reproduzi-lo e chamar
getRoutedDevice()
para determinar o dispositivo de áudio padrão em que o som será reproduzido.
Isso pode ser, por exemplo, uma faixa codificada em PCM de silêncio curto e seguro usada apenas para determinar o dispositivo roteado e os recursos de áudio dele.
Receber codificações com suporte
Use
getAudioProfiles()
(nível 31 da API e mais recentes) ou
getEncodings()
(nível 23 da API e mais recentes) para determinar os formatos de áudio disponíveis no
dispositivo de áudio padrão.
Verificar os formatos e perfis de áudio com suporte
Use AudioProfile
(nível 31 da API e mais recentes) ou
isDirectPlaybackSupported()
(nível 29 da API e mais recentes) para verificar as combinações com suporte de formato,
número de canais e taxa de amostragem.
Alguns dispositivos Android são capazes de oferecer suporte a codificações além daquelas com suporte do dispositivo de áudio de saída. Esses formatos adicionais precisam ser detectados usando isDirectPlaybackSupported(). Nesses casos, os dados de áudio são recodificados para um formato com suporte do 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 da API 33) introduziu rotas de áudio antecipadas. Você pode antecipar o suporte a atributos 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 têm suporte para reprodução de mídia direta no 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
AudioFormat instâncias. Ela é ordenada com o mais preferido primeiro na lista e o menos preferido por último.
getDirectProfilesForAttributes()
retorna uma lista de objetos com suporte para o
dispositivo de áudio roteado no momento com os
AudioAttributes.AudioProfile A lista de itens AudioFormat preferidos é iterada até que um AudioProfile compatível seja encontrado. Este AudioProfile é armazenado como bestAudioProfile.
As taxas de amostragem e as máscaras de canal ideais são determinadas em bestAudioProfile.
Por fim, uma instância AudioFormat
apropriada é criada.
Criar faixa de áudio
Os apps precisam usar essas informações para criar um AudioTrack para o AudioFormat de maior qualidade com suporte do 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 24 da API e mais recentes, adicione um
OnRoutingChangedListenerpara monitorar mudanças no dispositivo de áudio (HDMI, Bluetooth etc.). - Para o nível 23 da API, registre um
AudioDeviceCallbackpara receber mudanças na lista de dispositivos de áudio disponíveis. - Para os níveis 21 e 22 da API, monitore eventos de conexão HDMI e use os dados extras das transmissões.
- Registre também um
BroadcastReceiverpara monitorarBluetoothDevicemudanças de estado em dispositivos anteriores ao nível 23 da API, já queAudioDeviceCallbackainda não tem suporte.
Quando uma mudança no dispositivo de áudio for detectada para o AudioTrack, o app precisará 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 maior qualidade 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);