Contribuidores para a latência de áudio

Esta página concentra-se nos contribuintes 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 de áudio são os seguintes:

  • Aplicativo
  • 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 seja a lista de contribuidores acima, 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, uma saturação ou saturação de áudio é ouvida como um “clique” ou “pop”. Para compensar, o projetista do sistema aumenta o tamanho ou a contagem dos buffers. Isto tem o resultado desejado de eliminar os atrasos ou excessos, 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 é compreender as causas dos atrasos e dos excessos e, em seguida, corrigi-las. Isto elimina os artefatos audíveis e pode permitir buffers ainda menores ou em menor número e, assim, reduzir a latência.

Na nossa experiência, as causas mais comuns de subexecuções e excedentes incluem:

  • Linux CFS (Agendador Completamente Justo)
  • 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
  • tempo de desativação de interrupção longa
  • gerenciamento de energia
  • núcleos de segurança

Agendamento Linux CFS e SCHED_FIFO

O Linux CFS foi projetado para ser justo com cargas de trabalho concorrentes que compartilham um recurso de CPU comum. Essa justiça é representada por um parâmetro Nice por thread. O valor agradável varia de -19 (menos bom ou maior tempo de CPU alocado) a 20 (melhor ou menor tempo de CPU alocado). Em geral, todos os threads com um determinado valor de Nice recebem aproximadamente o mesmo tempo de CPU e os threads com um valor de Nice numericamente inferior devem esperar receber mais tempo de CPU. Contudo, a SFC é “justa” apenas durante períodos de observação relativamente longos. Em janelas de observação de curto prazo, o CFS pode alocar recursos da 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 do áudio, isso pode resultar em saturação ou saturação.

A solução óbvia é evitar o CFS para threads de áudio de alto desempenho. A partir do Android 4.1, esses threads agora usam a política de agendamento SCHED_FIFO em vez da política de agendamento SCHED_NORMAL (também chamada de 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 que não sejam de áudio com a política SCHED_FIFO . As prioridades SCHED_FIFO disponíveis variam de 1 a 99. Os threads de áudio são executados na prioridade 2 ou 3. Isso deixa a prioridade 1 disponível para threads de prioridade mais baixa e as prioridades 4 a 99 para threads 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 têm garantia de conclusão dentro de um período de tempo limitado, são executados com um período menor que o período dos threads de áudio e são conhecidos por não interferir no agendamento. de threads de áudio.

Agendamento monotônico de taxa

Para obter mais informações sobre a teoria da atribuição de prioridades fixas, consulte o artigo da Wikipedia Agendamento monotônico de taxa (RMS). Um ponto-chave é que as prioridades fixas devem ser atribuídas estritamente com base no período, com prioridades mais elevadas atribuídas a segmentos de períodos mais curtos, e não com base na “importância” percebida. Threads não periódicos podem ser modelados como threads periódicos, usando a frequência máxima de execução e o cálculo máximo por execução. Se um thread não periódico não puder ser modelado como um thread periódico (por exemplo, ele poderia ser executado com frequência ilimitada ou computação ilimitada por execução), então não deveria ser atribuída a ele uma prioridade fixa, pois isso seria incompatível com o escalonamento de verdadeiros threads periódicos .

Inversão de prioridade

A inversão de prioridade é um modo de falha clássico de sistemas em tempo real, onde 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 técnicas para mitigá-la.

Latência de agendamento

A latência de agendamento é o tempo entre o momento em que um thread fica pronto para ser executado e o momento em que a alternância de contexto resultante é concluída para que o thread realmente seja executado em uma CPU. Quanto menor a latência, melhor, e qualquer valor acima de dois milissegundos causa problemas de áudio. É mais provável que a latência de agendamento longa ocorra durante 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 potência total para o modo de baixo consumo de energia ou ajustar a frequência e a tensão do clock da CPU. .

Interrupções

Em muitos projetos, a CPU 0 atende todas as interrupções externas. Portanto, um manipulador 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 trabalhos demorados para um thread (de preferência um thread CFS ou thread 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 das 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 estejam limitados.

Energia, desempenho e gerenciamento térmico

Gerenciamento de energia é um termo amplo que abrange esforços para monitorar e reduzir o consumo de energia e, ao mesmo tempo, 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 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 utilizadas incluem:

  • escala de tensão dinâmica
  • escala de frequência dinâmica
  • habilitação de núcleo dinâmico
  • comutação de cluster
  • controle de energia
  • hotplug (troca a quente)
  • 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 períodos durante os quais não há trabalho útil executado pelo processador do aplicativo. Essas paralisações de trabalho podem interferir no áudio, portanto, esse gerenciamento deve ser projetado para uma paralisação de trabalho aceitável no pior caso enquanto o áudio estiver ativo. É claro que, quando a fuga térmica é iminente, evitar danos permanentes é mais importante que o áudio!

Núcleos 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(s) para o kernel do sistema operacional principal e o código do aplicativo. Qualquer momento durante o qual uma operação de kernel de segurança está ativa em um núcleo é efetivamente uma interrupção do trabalho normal que normalmente seria executado nesse núcleo. Em particular, isto pode incluir trabalho de áudio. Por sua natureza, o comportamento interno de um kernel de segurança é inescrutável nas 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 sombrio” – tempo que decorre e ainda 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.