4.15. Framebuffers

Uma vez que o sensor da câmera é inicializado, ele emite quadros continuamente em sua taxa de quadros – um novo quadro a cada período de quadro, esteja a aplicação pronta para ele ou não. Cada quadro precisa de algum lugar na RAM para chegar, ou ele é perdido. O pool de framebuffers é onde esses quadros ficam entre o momento em que saem do DMA e o momento em que são processados pelo código do usuário, e a quantidade de framebuffers que a câmera mantém nesse pool controla como o DMA e a aplicação os compartilham. A escolha é exposta através de framebuffers(), e quatro modos estão disponíveis, selecionados pela contagem de buffers.

4.15.1. Buffer único (count = 1)

Um framebuffer na RAM. O DMA o preenche; a aplicação lê dele; a próxima chamada a snapshot() não pode iniciar até que a aplicação tenha liberado o buffer, porque o mesmo buffer é necessário para ambos.

A câmera e a aplicação funcionam em sincronia rígida (lock-step). O DMA precisa esperar a aplicação terminar, e a aplicação precisa esperar o DMA terminar, o que significa que a taxa de quadros alcançável é, na melhor das hipóteses, metade da taxa de quadros do sensor – a cada dois quadros, o que o sensor emite chega enquanto o buffer está ocupado e é perdido.

Este modo é o menor em RAM e o mais lento em throughput. Use-o apenas quando a RAM estiver muito limitada para alocar um segundo buffer.

4.15.2. Buffer duplo (count = 2)

Dois framebuffers na RAM: um buffer traseiro (back) que o DMA preenche, e um buffer dianteiro (front) do qual a aplicação lê. Quando a aplicação termina o buffer dianteiro, os dois papéis são trocados, e o DMA começa a preencher o buffer recém-liberado enquanto a aplicação lê do que acabou de ser preenchido.

Desde que a aplicação processe cada quadro em menos de um período de quadro da câmera, a aplicação vê a taxa de quadros completa do sensor – o próximo quadro do DMA já está esperando no buffer traseiro quando a aplicação chama snapshot() novamente. No momento em que o tempo de processamento excede um período de quadro, no entanto, a taxa cai pela metade: a câmera produzirá dois quadros no tempo que a aplicação leva para processar um, e apenas o segundo desses dois será entregue.

A partir desse ponto, a taxa degrada suavemente com o tempo de processamento. Toda vez que o DMA termina um novo quadro no buffer traseiro enquanto a aplicação ainda está trabalhando no buffer dianteiro, o novo quadro sobrescreve a captura anterior no lugar, em vez de ser descartado. A aplicação sempre obtém o quadro mais recente que a câmera produziu em sua próxima chamada a snapshot(), e a taxa alcançável da aplicação torna-se o inverso de seu tempo de processamento.

4.15.3. Buffer triplo (count = 3)

Três framebuffers na RAM: dois buffers traseiros (back) pelos quais o DMA alterna e um buffer dianteiro (front) no qual a aplicação está trabalhando no momento. Este é o modo padrão que a OpenMV Cam escolhe quando há RAM suficiente disponível, com retorno automático para buffer duplo ou único quando não há.

O terceiro buffer desacopla completamente a taxa de quadros da câmera da taxa de quadros da aplicação. O DMA sempre tem um buffer para escrever; a aplicação sempre tem um buffer para ler; a cada snapshot(), o buffer traseiro pronto mais recente torna-se o novo buffer dianteiro e o buffer dianteiro anterior é liberado para o DMA. A taxa de quadros da aplicação corresponde ao tempo que ela realmente leva para processar cada quadro – sem o passo de 1/2 em que o buffer duplo cai quando o tempo de processamento ultrapassa apenas um pouco um período de quadro.

4.15.4. FIFO de vídeo (count = 4 ou mais)

Quatro ou mais framebuffers na RAM, organizados como um anel de quadros capturados em sequência. Cada quadro que a câmera entrega é enfileirado no FIFO, e snapshot() retorna o quadro enfileirado mais antigo em vez do mais recente. A aplicação percorre os quadros capturados na ordem de captura, no tempo que ela realmente tem para gastar em cada um.

Este modo é a escolha certa quando cada quadro importa e breves paralisações de processamento são esperadas: gravar vídeo em um cartão SD cuja pilha de armazenamento pode bloquear por dezenas de milissegundos durante um apagamento, transmitir via USB para um host que para de ler brevemente, ou armazenar em buffer uma curta rajada de um evento rápido para inspeção no código.

Duas políticas tratam o caso em que o FIFO se enche antes que a aplicação o tenha esvaziado.

  • Descartar quadros antigos (padrão). Quando o FIFO enche, todos os quadros enfileirados, exceto o ativo, são descartados, de modo que a próxima snapshot() retorne um quadro recente em vez de um obsoleto. O DMA continua capturando o tempo todo, então a aplicação sempre vê dados novos após uma paralisação. Esta é a política certa quando o objetivo é manter o fluxo capturado atualizado – gravação de vídeo, transmissão ao vivo.

  • Parar a captura em caso de overflow. Passe fflush=False ao construtor CSI e o DMA para de preencher o FIFO quando ele estiver cheio, deixando os quadros enfileirados intactos. snapshot() continua a retornar quadros na ordem de captura até que a aplicação os tenha esvaziado, após o que o DMA é retomado. Esta é a política certa quando o objetivo é preservar cada quadro de uma curta rajada – capturar movimento rápido para inspecionar quadro a quadro no código posteriormente.

Consulte csi.CSI.framebuffers() para a API completa.

4.15.5. Modo acionado (triggered)

Uma alternativa aos modos sempre em execução acima é a captura acionada (triggered), onde o sensor emite um quadro apenas quando snapshot() solicita um. A câmera fica ociosa entre snapshots e inicia uma nova exposição cada vez que a aplicação chama.

O custo é o throughput: uma captura acionada não pode se sobrepor à anterior, então a taxa de quadros máxima alcançável é metade da taxa normal do sensor. O benefício é o tempo da exposição. O snapshot controla exatamente quando a exposição começa, que é o que uma aplicação deseja quando a exposição precisa se alinhar a um evento externo – um flash estroboscópico, um sensor de posição de esteira, um pulso em uma linha GPIO – em vez de chegar onde quer que o quadro de varredura do sensor em execução livre esteja quando a aplicação estiver pronta para lê-lo.

O modo acionado é específico do sensor. Em sensores compatíveis, ele é habilitado chamando csi0.ioctl(csi.IOCTL_SET_TRIGGERED_MODE, True) e desabilitado passando False.