O Android TV oferece suporte a várias saídas de áudio simultâneas: alto-falantes da TV, home theater HDMI, Bluetooth e muito mais. Esses dispositivos são compatíveis com várias codificações (como Dolby Digital+, DTS, PCM), taxas de amostragem e canais. As smart TVs conectadas por HDMI são compatíveis com muitos formatos, mas o Bluetooth só é compatível com PCM.
Os dispositivos de áudio disponíveis e o roteamento podem mudar durante a reprodução: os usuários podem conectar o HDMI, o Bluetooth ou modificar as configurações. Os apps precisam se adaptar de forma adequada para garantir a reprodução contínua usando os recursos do novo dispositivo. Formatos incompatíveis podem causar erros ou silêncio.
Ofereça várias codificações para a melhor experiência com base nos recursos do dispositivo. Por exemplo, o Dolby Digital será usado se houver suporte. Caso contrário, o app vai usar o PCM. Consulte Formatos de mídia compatíveis para ver os decodificadores do Android que transformam streams em PCM.
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 certo
Os apps precisam criar um AudioTrack, iniciar a reprodução e chamar
getRoutedDevice() para determinar o dispositivo de áudio padrão em que o som
será reproduzido. Pode ser, por exemplo, uma faixa segura de silêncio curto codificada em PCM
usada apenas para determinar o dispositivo roteado e os recursos de áudio dele.
Receber codificações compatíveis
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 perfis e formatos 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
compatíveis de formato, contagem de canais e taxa de amostragem.
Alguns dispositivos Android são capazes de oferecer suporte a codificações além das
compatíveis com o dispositivo de áudio de saída. Esses formatos adicionais precisam ser
detectados por isDirectPlaybackSupported(). Nesses casos, os dados de áudio são
codificados novamente em um formato compatível com o dispositivo de áudio de saída. Use
isDirectPlaybackSupported() para verificar corretamente a compatibilidade com o formato escolhido,
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 as rotas de áudio antecipadas. É possível
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, é possível consultar quais perfis são compatíveis com a reprodução direta de mídia pelo dispositivo de áudio roteado no momento. Isso exclui todos os perfis que não são compatíveis ou, por exemplo, que seriam 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 AudioFormat.
Ela é ordenada com a mais preferida em primeiro lugar na lista e a menos preferida
em último. getDirectProfilesForAttributes() retorna uma lista de objetos
AudioProfile compatíveis com 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 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 compatível com o 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 maiores que 24, 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 da API 21 e 22, monitore os eventos de conexão HDMI e use os dados extras das transmissões.
- Além disso, registre um
BroadcastReceiverpara monitorar as mudanças de estado deBluetoothDeviceem dispositivos com API anterior à 23, já que oAudioDeviceCallbackainda não é compatível.
Quando uma mudança no dispositivo de áudio é detectada para o AudioTrack, o app
precisa 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
for compatível ou se a codificação usada anteriormente
não for mais compatível.
Código de amostra
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);