Recursos de áudio

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 com Bluetooth etc. Esses dispositivos de saída de áudio oferecem suporte a diferentes recursos de áudio, como codificações (Dolby Digital+, DTS e PCM), taxa de amostragem e canais. Por exemplo, as TVs conectadas HDMI oferecem suporte a diversas codificações enquanto fones de ouvido Bluetooth conectados costumam oferecer suporte apenas a PCM.

A lista de dispositivos de áudio disponíveis e o dispositivo de áudio roteado também pode mudar ao fazer a conexão a quente de dispositivos HDMI, conectar ou desconectar fones de ouvido Bluetooth, ou o usuário mudando as configurações de áudio. Como os recursos de saída de áudio podem mudam mesmo quando estão reproduzindo mídia, os aplicativos precisam se adaptar adequadamente a esses será alterado e continuar a reprodução no novo dispositivo de áudio roteado e recursos. A saída do formato de áudio errado pode resultar em erros ou nenhum som está tocando.

Os apps podem gerar o mesmo conteúdo em várias codificações para oferecer ao usuário a melhor experiência de áudio, dependendo do dispositivo de áudio recursos. Por exemplo, um stream de áudio codificado em Dolby Digital é reproduzido. caso a TV seja compatível, enquanto um stream de áudio PCM com suporte mais amplo é quando não há compatibilidade com Dolby Digital. A lista de dispositivos decodificadores usados para transformar um stream de áudio em PCM podem ser encontrados em Formatos de mídia compatíveis.

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

Crie 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 a partir do qual reproduzir som. Isso pode ser, por exemplo, uma faixa segura e curta de silêncio codificada em PCM usada apenas para determinar o dispositivo roteado e os recursos de áudio dele.

Receber codificações compatíveis

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

Verificar os perfis e formatos de áudio compatíveis

Usar AudioProfile (nível 31 da API e mais recentes) ou isDirectPlaybackSupported() (nível 29 da API e mais recentes) para verificar as combinações de formatos aceitas, contagem de canais e taxa de amostragem.

Alguns dispositivos Android são compatíveis com codificações além das suportado pelo dispositivo de áudio de saída. Esses formatos adicionais devem ser detectado pelo isDirectPlaybackSupported(). Nesses casos, os dados de áudio é recodificado para um formato compatível com o dispositivo de áudio de saída. Usar isDirectPlaybackSupported() para verificar corretamente o suporte ao formato desejado. mesmo que 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 suporte ao atributo de áudio do dispositivo e preparar faixas para os dispositivo de áudio. Você pode usar getDirectPlaybackSupport() para verificar se a reprodução direta é compatível com o áudio roteado dispositivo 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 mídia direta a reprodução no dispositivo de áudio roteado no momento. Isso exclui qualquer perfil incompatíveis ou que seriam, por exemplo, transcodificados pelo sistema estrutural:

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. Eles são ordenados com o mais preferido primeiro na lista e o menos preferido em último. getDirectProfilesForAttributes() retorna uma lista de objetos Objetos AudioProfile para o dispositivo de áudio roteado atualmente com o AudioAttributes. A lista de itens AudioFormat preferidos são iterados até que uma correspondência seja compatível AudioProfile foi encontrado. Este AudioProfile está armazenado como bestAudioProfile. As taxas de amostragem e as máscaras de canal ideais são determinadas a partir de bestAudioProfile. Por fim, um AudioFormat apropriado é 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 de API iguais ou maiores que 24, adicione um OnRoutingChangedListener para monitorar alterações 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 do plugue HDMI e usar os dados extras das transmissões.
  • Também registre um BroadcastReceiver para monitorar. Mudanças de estado do BluetoothDevice para dispositivos anteriores à API 23, já que AudioDeviceCallback não é mas compatíveis.

Quando uma mudança no dispositivo de áudio for detectada para o AudioTrack, o app verificar os recursos de áudio atualizados e, se necessário, recriar o AudioTrack por um AudioFormat diferente. Faça isso se um protótipo de alta qualidade a codificação é compatível ou a codificação usada anteriormente é não têm 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);