Esta página se concentra nos fatores que contribuem para a latência de saída, mas uma discussão semelhante se aplica à latência de entrada.
Supondo que o circuito analógico não contribua significativamente, os principais contribuintes de nível superficial para a latência do áudio são os seguintes:
- Aplicativo
- Número total de buffers no pipeline
- Tamanho de cada buffer, em frames
- Latência adicional após o processador do app, como de um DSP
Por mais precisa que seja a lista de colaboradores acima, ela também é enganosa. Isso ocorre porque a contagem e o tamanho do buffer são mais um efeito do que uma causa. O que geralmente acontece é que um determinado esquema de buffer é implementado e testado, mas durante o teste, um áudio é ouvido como um "clique" ou "pop". Para compensar, o designer do sistema aumenta os tamanhos ou as contagens de buffer. Isso tem o resultado desejado de eliminar os underruns ou overruns, mas também tem o efeito colateral indesejado de aumentar a latência. Para mais informações sobre os tamanhos de buffer, consulte o vídeo Latência de áudio: tamanhos de buffer.
Uma abordagem melhor é entender as causas dos underruns e overruns e corrigi-los. Isso elimina os artefatos audíveis e pode permitir buffers menores ou menores e, portanto, reduzir a latência.
Na nossa experiência, as causas mais comuns de subexecução e superexecução incluem:
- Linux CFS (Completely Fair Scheduler)
- linhas de execução de alta prioridade com programação SCHED_FIFO
- inversão de prioridade
- latência de programação longa
- manipuladores de interrupção de longa duração
- tempo de desativação de interrupção longo
- gerenciamento de energia
- núcleos de segurança
Programação CFS e SCHED_FIFO do Linux
O CFS do Linux foi projetado para ser justo com cargas de trabalho concorrentes que compartilham um recurso de CPU comum. Essa imparcialidade é representada por um parâmetro nice por linha de execução. O valor bom varia de -19 (menos bom ou mais tempo de CPU alocado) a 20 (mais bom ou menos tempo de CPU alocado). Em geral, todas as linhas de execução com um determinado valor nice recebem um tempo de CPU aproximadamente igual, e as linhas de execução com um valor nice numericamente menor devem receber mais tempo de CPU. No entanto, a CFS é "justa" apenas em períodos de observação relativamente longos. Em janelas de observação de curto prazo, o CFS pode alocar o recurso de CPU de maneiras inesperadas. Por exemplo, ele pode tirar a CPU de uma linha de execução com uma boa numéricamente baixa para uma linha de execução com uma boa numéricamente alta. No caso do áudio, isso pode resultar em um underrun ou overrun.
A solução óbvia é evitar a CFS para linhas de execução de áudio de alto
desempenho. A partir do Android 4.1, essas linhas de execução agora usam a
política de programação SCHED_FIFO
em vez da SCHED_NORMAL
(também chamada
de SCHED_OTHER
) implementada pelo CFS.
Prioridades SCHED_FIFO
Embora as linhas de execução de áudio de alto desempenho agora usem SCHED_FIFO
, elas
ainda são suscetíveis a outras linhas de execução SCHED_FIFO
de prioridade mais alta.
Elas geralmente são linhas de execução de trabalho do kernel, mas também podem haver algumas
linhas de execução de usuários que não são de áudio com a política SCHED_FIFO
. As prioridades SCHED_FIFO
disponíveis variam de 1 a 99. As linhas de execução de áudio são executadas com prioridade
2 ou 3. Isso deixa a prioridade 1 disponível para linhas de prioridade mais baixa
e as prioridades 4 a 99 para linhas de prioridade mais alta. Recomendamos
que você use a prioridade 1 sempre que possível e reserve as prioridades 4 a 99 para
linhas de execução que têm garantia de conclusão em um período
limitado, são executadas com um período mais curto do que o período de linhas de execução de áudio
e não interferem na programação de linhas de execução de áudio.
Programação de taxa-monotônica
Para mais informações sobre a teoria da atribuição de prioridades fixas, consulte o artigo da Wikipédia Rate-monotonic scheduling (RMS). Um ponto importante é que as prioridades fixas precisam ser alocadas estritamente com base no período, com prioridades mais altas atribuídas a linhas de execução de períodos mais curtos, não com base na "importância" percebida. As linhas de execução não periódicas podem ser modeladas como linhas de execução periódicas, usando a frequência máxima de execução e a computação máxima por execução. Se uma linha de execução não periódica não puder ser modelada como uma linha de execução periódica (por exemplo, ela pode ser executada com frequência ilimitada ou com computação ilimitada por execução), não será possível atribuir uma prioridade fixa, porque isso seria incompatível com a programação de linhas de execução periódicas verdadeiras.
Inversão de prioridade
A inversão de prioridade é um modo de falha clássico de sistemas em tempo real, em que uma tarefa de prioridade mais alta é bloqueada por um tempo ilimitado aguardando que uma tarefa de prioridade mais baixa libere um recurso, como um mutex (estado compartilhado protegido). Consulte o artigo Como evitar a inversão de prioridade para conferir técnicas de mitigação.
Latência de programação
A latência de programação é o tempo entre o momento em que uma linha de execução fica pronta para execução e o momento em que a mudança de contexto resultante é concluída para que a linha de execução seja executada em uma CPU. Quanto menor a latência, melhor. Qualquer valor acima de dois milissegundos causa problemas no áudio. A latência de programação longa provavelmente ocorre durante as transições de modo, como ligar ou desligar uma CPU, alternar entre um kernel de segurança e o kernel normal, alternar do modo de energia total para o de energia baixa, ou ajustar a frequência e a tensão do clock da CPU.
Interrupções
Em muitos designs, a CPU 0 atende a todas as interrupções externas. Portanto, um
gerenciador de interrupção de longa duração pode atrasar outras interrupções, em particular
interrupções de conclusão de acesso direto à memória (DMA) de áudio. Projete manipuladores de interrupção
para terminar rapidamente e adiar o trabalho demorado para uma linha de execução (de preferência
uma linha de execução CFS ou SCHED_FIFO
de prioridade 1).
Da mesma forma, desativar interrupções na CPU 0 por um longo período tem o mesmo resultado de atrasar o atendimento de interrupções de áudio. Tempos de desativação de interrupção longos geralmente acontecem enquanto se aguarda um bloqueio de giro do kernel. Verifique se esses bloqueios de giro estão limitados.
Gerenciamento de energia, desempenho e térmico
Gerenciamento de energia é um termo amplo que abrange esforços para monitorar e reduzir o consumo de energia, além de otimizar o desempenho. O gerenciamento térmico e o resfriamento do computador são semelhantes, mas buscam medir e controlar o calor para evitar danos devido ao excesso de calor. No kernel do Linux, o gerenciador é responsável pela política de baixo nível, enquanto o modo de usuário configura a política de alto nível. As técnicas usadas incluem:
- escalonamento de tensão dinâmico
- escalonamento de frequência dinâmica
- ativação do core dinâmico
- troca de cluster
- poder de controle
- Hotplug (hotswap)
- vários modos de suspensão (parada, pausa, inatividade, suspensão etc.)
- Migração de processo
- afinidade com o processador
Algumas operações de gerenciamento podem resultar em "paradas de trabalho" ou períodos em que não há trabalho útil realizado pelo processador do aplicativo. Essas interrupções podem interferir no áudio. Portanto, esse gerenciamento precisa ser projetado para uma interrupção aceitável no caso mais extremo enquanto o áudio estiver ativo. É claro que, quando a falha térmica é iminente, evitar danos permanentes é mais importante do que o áudio.
Kernels de segurança
Um kernel de segurança para gerenciamento de direitos digitais (DRM, na sigla em inglês) pode ser executado nos mesmos núcleos de processador de aplicativos usados para o kernel do sistema operacional principal e o código do aplicativo. Qualquer momento em que uma operação do kernel de segurança estiver ativa em um núcleo é uma interrupção do trabalho comum que normalmente seria executado nesse núcleo. Isso pode incluir trabalhos de áudio. Por natureza, o comportamento interno de um kernel de segurança é inescrutável em camadas de nível superior. Portanto, qualquer anomalia de desempenho causada por um kernel de segurança é especialmente nociva. Por exemplo, operações de kernel de segurança geralmente não aparecem em rastros de troca de contexto. Chamamos isso de "tempo escuro", o tempo que passa e não pode ser observado. Os kernels de segurança precisam ser projetados para uma interrupção de trabalho aceitável no caso mais extremo enquanto o áudio estiver ativo.