Esta página se concentra nos contribuidores para a latência de saída, mas uma discussão semelhante se aplica à latência de entrada.
Assumindo que os circuitos analógicos não contribuem significativamente, os principais contribuintes de nível de superfície para a latência de áudio são os seguintes:
- Inscrição
- Número total de buffers no pipeline
- Tamanho de cada buffer, em quadros
- Latência adicional após o processador do aplicativo, como de um DSP
Por mais precisa que a lista de colaboradores acima possa ser, ela também é enganosa. A razão é que 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 insuficiente ou sobrecarregado é ouvido como um "clique" ou "pop". Para compensar, o projetista do sistema aumenta os tamanhos dos buffers ou as contagens de buffers. 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 obter mais informações sobre 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, em seguida, corrigi-los. Isso elimina os artefatos audíveis e pode permitir buffers ainda menores ou menos e, assim, reduzir a latência.
Em nossa experiência, as causas mais comuns de faltas e excessos incluem:
- Linux CFS (Completely Fair Scheduler)
- threads de alta prioridade com agendamento SCHED_FIFO
- inversão de prioridade
- longa latência de agendamento
- manipuladores de interrupção de longa duração
- longo tempo de desativação de interrupção
- gerenciamento de energia
- kernels de segurança
CFS Linux e agendamento SCHED_FIFO
O Linux CFS 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 thread. O valor nice varia de -19 (menos agradável ou mais tempo de CPU alocado) a 20 (mais agradável ou menos tempo de CPU alocado). Em geral, todos os encadeamentos com um valor de nice determinado recebem tempo de CPU aproximadamente igual e encadeamentos com um valor de nice numericamente menor devem esperar receber mais tempo de CPU. No entanto, o CFS é "justo" 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, pode levar a CPU de um thread com gentileza numericamente baixa para um thread com gentileza numericamente alta. No caso de áudio, isso pode resultar em um volume insuficiente ou excessivo.
A solução óbvia é evitar o CFS para threads de áudio de alto desempenho. A partir do Android 4.1, esses encadeamentos agora usam a política de agendamento SCHED_FIFO
em vez da política de agendamento SCHED_NORMAL
(também chamada SCHED_OTHER
) implementada pelo CFS.
Prioridades SCHED_FIFO
Embora os threads de áudio de alto desempenho agora usem SCHED_FIFO
, eles ainda são suscetíveis a outros threads SCHED_FIFO
de prioridade mais alta. Normalmente, esses são threads de trabalho do kernel, mas também pode haver alguns threads de usuário sem áudio com a política SCHED_FIFO
. As prioridades SCHED_FIFO
disponíveis variam de 1 a 99. Os encadeamentos de áudio são executados na prioridade 2 ou 3. Isso deixa a prioridade 1 disponível para encadeamentos de prioridade mais baixa e prioridades 4 a 99 para encadeamentos de prioridade mais alta. Recomendamos que você use a prioridade 1 sempre que possível e reserve as prioridades de 4 a 99 para os threads que são garantidos para serem concluídos dentro de um determinado período de tempo, executados com um período menor que o período dos threads de áudio e que não interferem no agendamento de fios de áudio.
Agendamento de taxa monotônica
Para obter mais informações sobre a teoria de atribuição de prioridades fixas, consulte o artigo da Wikipédia Rate-monotonic schedule (RMS). Um ponto-chave é que as prioridades fixas devem ser alocadas estritamente com base no período, com prioridades mais altas atribuídas a threads de períodos mais curtos, não com base na "importância" percebida. Os encadeamentos não periódicos podem ser modelados como encadeamentos periódicos, usando a frequência máxima de execução e computação máxima por execução. Se um encadeamento não periódico não pode ser modelado como um encadeamento periódico (por exemplo, pode ser executado com frequência ilimitada ou computação ilimitada por execução), então não deve ser atribuída uma prioridade fixa, pois isso seria incompatível com o agendamento de encadeamentos periódicos verdadeiros .
Inversão de prioridade
A inversão de prioridade é um modo de falha clássico de sistemas de 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 (estado compartilhado protegido por) um mutex . Consulte o artigo " Evitando a inversão de prioridade " para obter técnicas para mitigá-la.
Agendamento de latência
A latência de agendamento é o tempo entre quando um thread fica pronto para ser executado e quando a troca de contexto resultante é concluída para que o thread realmente seja executado em uma CPU. Quanto menor a latência, melhor, e qualquer coisa acima de dois milissegundos causa problemas para o áudio. A latência de agendamento longa é mais provável de ocorrer durante as transições de modo, como ativar ou desligar uma CPU, alternar entre um kernel de segurança e o kernel normal, alternar do modo de energia total para o modo de baixo consumo ou ajustar a frequência e a tensão do clock da CPU .
Interrupções
Em muitos projetos, a CPU 0 atende a todas as interrupções externas. Portanto, um manipulador de interrupção de longa duração pode atrasar outras interrupções, em particular as interrupções de conclusão de acesso direto à memória de áudio (DMA). Projete manipuladores de interrupção para terminar rapidamente e adiar o trabalho demorado para um encadeamento (de preferência um encadeamento CFS ou encadeamento SCHED_FIFO
de prioridade 1).
De forma equivalente, desabilitar interrupções na CPU 0 por um longo período tem o mesmo resultado de atrasar o atendimento de interrupções de áudio. Tempos longos de desativação de interrupção normalmente acontecem enquanto se espera por um bloqueio de rotação do kernel. Revise esses bloqueios de rotação para garantir que eles sejam limitados.
Energia, desempenho e gerenciamento térmico
Gerenciamento de energia é um termo amplo que engloba esforços para monitorar e reduzir o consumo de energia enquanto otimiza 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 governador da CPU é 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:
- escala dinâmica de tensão
- escala de frequência dinâmica
- habilitação de núcleo dinâmico
- comutação de cluster
- power gating
- hotplug (hotswap)
- vários modos de suspensão (parar, parar, inativo, suspender, etc.)
- migração de processos
- afinidade do processador
Algumas operações de gerenciamento podem resultar em "paradas de trabalho" ou tempos durante os quais não há trabalho útil executado pelo processador do aplicativo. Essas interrupções de trabalho podem interferir no áudio, portanto, esse gerenciamento deve ser projetado para uma interrupção de trabalho aceitável no pior caso enquanto o áudio estiver ativo. Claro, quando a fuga 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) pode ser executado no(s) mesmo(s) núcleo(s) de processador de aplicativo usado para o kernel do sistema operacional principal e o código do aplicativo. Qualquer momento durante o qual uma operação do kernel de segurança está ativa em um núcleo é efetivamente uma interrupção do trabalho comum que normalmente seria executado nesse núcleo. Em particular, isso pode incluir trabalho de áudio. Por sua natureza, o comportamento interno de um kernel de segurança é inescrutável das camadas de nível superior e, portanto, quaisquer anomalias de desempenho causadas por um kernel de segurança são especialmente perniciosas. Por exemplo, as operações do kernel de segurança normalmente não aparecem em rastreamentos de alternância de contexto. Chamamos isso de "tempo escuro" - tempo que decorre mas não pode ser observado. Os kernels de segurança devem ser projetados para uma interrupção de trabalho aceitável no pior caso enquanto o áudio estiver ativo.