Latência de áudio

Latência é o tempo necessário para um sinal percorrer um sistema. Veja alguns tipos comuns de latência relacionados a apps de áudio:

  • Latência na saída de áudio é o tempo entre a geração de uma amostra de áudio por um app e a reprodução da amostra na entrada para fone de ouvido ou alto-falante integrado.
  • Latência na entrada de áudio é o tempo entre o recebimento de um sinal de áudio pela entrada de áudio de um dispositivo, como o microfone, e a disponibilização dos mesmos dados de áudio em um app.
  • Latência de ida e volta é a soma da latência de entrada, do tempo de processamento do app e da latência de saída.

  • Latência de toque é o tempo entre o toque de um usuário na tela e o recebimento desse evento de toque por um app.
  • Latência de aquecimento é o tempo necessário para iniciar o canal do áudio na primeira vez em que os dados são colocados na fila de um buffer.

Esta página descreve como desenvolver um app de áudio com baixa latência na entrada e na saída e como evitar a latência de aquecimento.

Medir a latência

É difícil medir a latência na entrada e na saída de áudio de forma isolada, já que é preciso saber exatamente quando a primeira amostra foi enviada para o caminho de áudio (embora isso possa ser feito usando um circuito de testes de tensão e um osciloscópio). Se você conhece a latência de ida e volta do áudio, é possível usar a seguinte regra geral: a latência na entrada (e na saída) de áudio é metade da latência de ida e volta em caminhos sem processamento de sinal.

A latência de ida e volta do áudio varia significativamente de acordo com o modelo do dispositivo e a versão do Android. É possível ter uma ideia do nível da latência de ida e volta de dispositivos Nexus lendo as medições publicadas.

Você pode medir a latência de ida e volta do áudio criando um app que gere um sinal de áudio, acompanhe esse sinal e meça o tempo entre o envio e o recebimento dele. Como alternativa, você pode instalar este aplicativo de teste de latência. Ele testa a latência de ida e volta usando o teste de Larsen. Além disso, é possível ver o código-fonte do aplicativo de teste de latência.

A latência mais baixa é alcançada por meio de caminhos de áudio com processamento mínimo de sinal. Por isso, também é possível usar um dongle de loopback de áudio, que permite que o teste seja feito no conector de fone de ouvido.

Práticas recomendadas para minimizar a latência

Validar o desempenho do áudio

O Documento de definição de compatibilidade do Android (CDD, na sigla em inglês) enumera os requisitos de hardware e software de um dispositivo Android compatível. Consulte Compatibilidade do Android para saber mais sobre o programa de compatibilidade geral e o CDD para ver o documento correspondente.

No CDD, a latência de ida e volta é especificada como 20 ms ou menos (embora os músicos geralmente precisem de 10 ms). Isso porque há casos de uso importantes que se tornam possíveis com 20 ms.

Atualmente, não há API que determine a latência de áudio em um caminho de um dispositivo Android em tempo de execução. No entanto, é possível usar as sinalizações de recurso de hardware a seguir para saber se o dispositivo tem alguma proteção contra latência:

Os critérios para relatar essas sinalizações são definidos no CDD, nas seções 5.6 Latência de áudio e 5.10 Áudio profissional.

Veja como verificar esses recursos em Java:

Kotlin

val hasLowLatencyFeature: Boolean =
        packageManager.hasSystemFeature(PackageManager.FEATURE_AUDIO_LOW_LATENCY)

val hasProFeature: Boolean =
        packageManager.hasSystemFeature(PackageManager.FEATURE_AUDIO_PRO)

Java

boolean hasLowLatencyFeature =
    getPackageManager().hasSystemFeature(PackageManager.FEATURE_AUDIO_LOW_LATENCY);

boolean hasProFeature =
    getPackageManager().hasSystemFeature(PackageManager.FEATURE_AUDIO_PRO);

Acerca da relação entre recursos de áudio, o android.hardware.audio.low_latency é um pré-requisito de android.hardware.audio.pro. Um dispositivo pode implementar android.hardware.audio.low_latency e não android.hardware.audio.pro, mas não o contrário.

Não fazer suposições com relação ao desempenho do áudio

Para evitar problemas de latência, tenha cuidado com as seguintes suposições:

  • Não suponha que os alto-falantes e microfones usados em dispositivos móveis tenham boa acústica. Como eles são pequenos, a acústica costuma ser ruim. Por isso, o processamento de sinal é adicionado para melhorar a qualidade do som. Esse processamento gera latência.
  • Não suponha que os callbacks de entrada e saída sejam sincronizados. Para entrada e saída simultâneas, são usados gerenciadores de conclusão de fila de buffer separados para cada lado. Não há garantia de ordem relativa desses callbacks nem da sincronização dos clocks de áudio, mesmo quando ambos os lados usam a mesma taxa de amostragem. Seu app deve armazenar os dados em buffer com uma sincronização de buffer adequada.
  • Não suponha que a taxa de amostragem real corresponda exatamente à taxa de amostragem nominal. Por exemplo: se a taxa de amostragem nominal é 48.000 Hz, é normal que o relógio de áudio avance a uma taxa um pouco diferente do que CLOCK_MONOTONIC do sistema operacional. Isso acontece porque os relógios de áudio e do sistema podem derivar de cristais diferentes.
  • Não suponha que a taxa de amostragem real de reprodução corresponda exatamente à taxa de amostragem de captura real, principalmente se os pontos de extremidade estiverem em caminhos separados. Por exemplo, se você estiver capturando pelo microfone do dispositivo, à taxa de amostragem nominal de 48.000 Hz, e reproduzindo áudio USB à mesma taxa, as taxas de amostragem reais provavelmente serão ligeiramente diferentes entre si.

Uma consequência dos clocks de áudio possivelmente independentes é a necessidade de conversão assíncrona da taxa de amostragem. Uma técnica simples (embora não ideal em termos de qualidade do áudio) para a conversão assíncrona da taxa de amostragem é duplicar ou remover amostras conforme a necessidade, até quase um ponto de interseção zero. É possível fazer conversões mais sofisticadas.

Minimizar a latência de entrada

Esta seção traz sugestões para ajudar a reduzir a latência na entrada do áudio ao gravar usando um microfone integrado ou um microfone de fone de ouvido externo.

  • Se o app estiver monitorando a entrada, sugira que o usuário use fones de ouvido (por exemplo, inclua uma tela que indique Melhor com fones de ouvido no primeiro acesso). O simples uso de fones de ouvido não garante a menor latência possível. Talvez seja necessário concluir outras etapas para remover o processamento de sinal indesejado do caminho do áudio, por exemplo, usar a predefinição VOICE_RECOGNITION ao fazer gravações.
  • Prepare-se para processar taxas de amostragem nominais de 44.100 e 48.000 Hz, conforme informado por getProperty(String) para PROPERTY_OUTPUT_SAMPLE_RATE. Outras taxas de amostragem são possíveis, mas raras.
  • Prepare-se para processar os tamanhos de buffer informados por getProperty(String) para PROPERTY_OUTPUT_FRAMES_PER_BUFFER. Os seguintes tamanhos de buffer são comuns: 96, 128, 160, 192, 240, 256 ou 512 frames. No entanto, é possível utilizar outros valores.

Minimizar a latência de saída

Use a taxa de amostragem ideal ao criar um player de áudio

Para ter a menor latência possível, é necessário fornecer dados de áudio que correspondam à taxa de amostragem ideal e ao tamanho do buffer do dispositivo. Para mais informações, consulte Design para redução de latência.

Em Java, é possível verificar a taxa de amostragem ideal pelo AudioManager, conforme exibido no seguinte exemplo de código:

Kotlin

val am = getSystemService(Context.AUDIO_SERVICE) as AudioManager
val sampleRateStr: String? = am.getProperty(AudioManager.PROPERTY_OUTPUT_SAMPLE_RATE)
var sampleRate: Int = sampleRateStr?.let { str ->
    Integer.parseInt(str).takeUnless { it == 0 }
} ?: 44100 // Use a default value if property not found

Java

AudioManager am = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
String sampleRateStr = am.getProperty(AudioManager.PROPERTY_OUTPUT_SAMPLE_RATE);
int sampleRate = Integer.parseInt(sampleRateStr);
if (sampleRate == 0) sampleRate = 44100; // Use a default value if property not found

Assim que souber a taxa de amostragem ideal, você poderá fornecê-la ao criar seu player. Este exemplo usa o OpenSL ES:

// create buffer queue audio player
void Java_com_example_audio_generatetone_MainActivity_createBufferQueueAudioPlayer
        (JNIEnv* env, jclass clazz, jint sampleRate, jint framesPerBuffer)
{
   ...
   // specify the audio source format
   SLDataFormat_PCM format_pcm;
   format_pcm.numChannels = 2;
   format_pcm.samplesPerSec = (SLuint32) sampleRate * 1000;
   ...
}

Observação: samplesPerSec refere-se à taxa de amostragem por canal em milihertz (1 Hz = 1.000 mHz).

Use o tamanho de buffer ideal ao enfileirar dados de áudio

É possível verificar o tamanho ideal do buffer de forma parecida com a verificação da taxa de amostragem ideal, usando a API do AudioManager:

Kotlin

val am = getSystemService(Context.AUDIO_SERVICE) as AudioManager
val framesPerBuffer: String? = am.getProperty(AudioManager.PROPERTY_OUTPUT_FRAMES_PER_BUFFER)
var framesPerBufferInt: Int = framesPerBuffer?.let { str ->
    Integer.parseInt(str).takeUnless { it == 0 }
} ?: 256 // Use default

Java

AudioManager am = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
String framesPerBuffer = am.getProperty(AudioManager.PROPERTY_OUTPUT_FRAMES_PER_BUFFER);
int framesPerBufferInt = Integer.parseInt(framesPerBuffer);
if (framesPerBufferInt == 0) framesPerBufferInt = 256; // Use default

A propriedade PROPERTY_OUTPUT_FRAMES_PER_BUFFER indica o número de frames de áudio que o buffer Camada de abstração de hardware (HAL, na sigla em inglês) pode conter. É necessário compilar buffers de áudio de forma que eles contenham um múltiplo exato desse número. Ao usar o número correto de frames de áudio, os callbacks ocorrerão em intervalos regulares, o que reduzirá a instabilidade.

É importante usar a API para determinar o tamanho do buffer em vez de usar um valor fixo no código, uma vez que os tamanhos de buffer HAL são diferentes dependendo do dispositivo e da versão do Android.

Evite adicionar interfaces de saída que envolvam processamento de sinal

Somente estas interfaces são compatíveis com o mixer rápido:

  • SL_IID_ANDROIDSIMPLEBUFFERQUEUE
  • SL_IID_VOLUME
  • SL_IID_MUTESOLO

Estas interfaces não são permitidas porque envolvem processamento de sinal, o que fará sua solicitação de uma faixa rápida ser recusada:

  • SL_IID_BASSBOOST
  • SL_IID_EFFECTSEND
  • SL_IID_ENVIRONMENTALREVERB
  • SL_IID_EQUALIZER
  • SL_IID_PLAYBACKRATE
  • SL_IID_PRESETREVERB
  • SL_IID_VIRTUALIZER
  • SL_IID_ANDROIDEFFECT
  • SL_IID_ANDROIDEFFECTSEND

Ao criar seu player, adicione apenas interfaces rápidas, conforme exibido no seguinte exemplo:

const SLInterfaceID interface_ids[2] = { SL_IID_ANDROIDSIMPLEBUFFERQUEUE, SL_IID_VOLUME };

Verifique se está usando uma faixa de baixa latência

Siga estas etapas para verificar se você conseguiu uma faixa de baixa latência:

  1. Abra o app e execute o seguinte comando:
  2. adb shell ps | grep your_app_name
    
  3. Anote o código do processo do app.
  4. Agora, abra um áudio no app. Você tem aproximadamente três segundos para executar o seguinte comando a partir do terminal:
  5. adb shell dumpsys media.audio_flinger
    
  6. Procure o código do processo. Um F na coluna Name indica uma faixa de baixa latência (F significa faixa rápida).

Minimizar a latência de aquecimento

Ao enfileirar dados de áudio pela primeira vez, o circuito de áudio do dispositivo levará um tempo curto, porém significativo, para aquecer. Para evitar essa latência de aquecimento, é possível enfileirar buffers de dados de áudio que contêm “silence”, conforme exibido no exemplo de código a seguir:

#define CHANNELS 1
static short* silenceBuffer;
int numSamples = frames * CHANNELS;
silenceBuffer = malloc(sizeof(*silenceBuffer) * numSamples);
    for (i = 0; i<numSamples; i++) {
        silenceBuffer[i] = 0;
    }

No momento da produção do áudio, é possível mudar para o enfileiramento de buffers que contêm dados de áudio reais.

Observação: a saída constante de áudio gera um alto consumo de energia. Interrompa a saída no método onPause(). Além disso, é recomendável pausar em caso de saída silenciosa após períodos de inatividade do usuário.

Outras amostras de código

Para fazer o download de um app de amostra que demonstra a latência de áudio, consulte as Amostras do NDK.

Mais informações

  1. Latência de áudio para desenvolvedores de apps
  2. Colaboradores da latência de áudio
  3. Medir a latência de áudio
  4. Aquecimento de áudio
  5. Latência (áudio)
  6. Tempo de atraso de ida e volta