Gravação de vídeo: evidências
Relatório de implementação, testes executados e homologação física pendente.
Esta mudança acrescenta captura opcional de vídeo e geração de MP4 após a chamada. O WAV e o contrato existente de chamadas permanecem independentes. Os ensaios iniciais usaram somente material sintético. A ativação autorizada na VPS 1 está registrada ao final deste documento. Android/iPhone, chamadas prolongadas e capacidade de produção continuam pendentes de homologação.
Comportamento implementado
Com ATENDROZAP_CALLS_VIDEO_RECORDING_ENABLED=true, vídeo e gravação de áudio habilitados e FFmpeg/ffprobe disponíveis, a chamada captura as unidades H.264 recebidas e as efetivamente enviadas ao relay. A imagem Docker padrão api e a configuração padrão continuam com a gravação de vídeo desligada.
O observador pertence à chamada, preservando a captura durante ausência, substituição e liberação do attachment do navegador. Frames incompletos ou escritas parciais no relay não viram frames enviados na gravação. Timestamps RTP, orientação e eventos de câmera acompanham o H.264 em uma fila limitada, sem escrita em disco nos goroutines de mídia.
Depois do encerramento, o arquivo final é composto em MP4 H.264 1280×360 a 25 fps, com atendente à esquerda, contato à direita e áudio AAC estéreo derivado do WAV. Câmera desligada deixa a área correspondente preta. O WAV continua disponível separadamente. A saída só pode ser anunciada como ready após inspeção e decode integral; a composição tem orçamento de fila, disco, decodificação e tempo. Reordenação de apresentação H.264 não suportada gera falha explícita.
call_video_recordings mantém o estado independente: recording, processing, ready, failed ou expired. O primeiro resultado terminal e seu webhook são persistidos juntos. O WAV necessário a uma captura ou composição pendente é preservado fisicamente até esse trabalho terminar; a expiração continua impedindo seu download quando o prazo já venceu. Reinício marca captura/composição pendente como failed/worker_restarted, sem retomada automática do trabalho.
O histórico e GET /call/{id} acrescentam videoRecording somente quando existe captura. O endpoint GET /call/{id}/recording/video exige header token, mantém isolamento de instância/worker, aceita Range e usa cache privado desabilitado. O console faz proxy por sessão autenticada e apresenta player, download e estados de processamento, falha e expiração.
Os subeventos video_recording_ready/video_recording_failed usam o envelope existente EventType=call. Atualizam apenas o vídeo: não são ended, não mudam motivo de encerramento, não liberam vaga e não substituem o objeto recording do áudio. Guia e prompt de integração: Gravação de videochamadas.
Evidências RED/GREEN
Os exemplos abaixo usam dados sintéticos e um PostgreSQL descartável. Nenhum dado privado ou endereço de banco de produção foi utilizado. “RED” descreve execução que falhou antes do respectivo ajuste; “GREEN” é o resultado executado depois. Testes de recursos inteiramente novos também são listados como cobertura, sem inventar uma execução anterior.
| Camada e teste | Falha observada antes do ajuste | Resultado após o ajuste |
|---|---|---|
HTTP — TestVideoRecordingRouteRequiresHeaderCredential | Rota de MP4 devolvia 404, quando o teste exigia 401 para credencial apenas na query | Passou: autenticação exclusivamente pelo header |
Serviço — TestVideoRecordingSurvivesAbsentReplacedAndReleasedBridges | Nenhum dos quatro frames esperados era observado (0, esperado 4) | Passou: captura mantém o mesmo ciclo durante ausência, substituição e liberação da ponte |
Motor — TestVideoRecordingOnlyObservesCompletelySentAccessUnits | Nenhuma das duas access units esperadas era observada (0, esperado 2) | Passou: só captura unidades aceitas e completamente enviadas |
Motor — TestVideoRecordingReceivesAuthenticatedFramesWithoutBrowser | Timeout de um segundo aguardando captura sem navegador | Passou: recebe H.264 após autenticação e remontagem SRTP mesmo sem sink do navegador |
Motor — TestVideoRecordingExcludesPartiallyWrittenFrames | Em escrita curta, continuava até quatro escritas, quando deveria parar na segunda | Passou: interrompe envio e não captura unidade incompleta |
Retenção — TestRecordingCleanupPinsWAVUntilItsVideoJobEnds | Quatro subcasos recording/processing → ready/failed removiam WAV ainda necessário | Passaram: mantém o WAV pendente e libera limpeza após resultado final do vídeo |
Gravador — TestNewSourceAfterCameraOffDoesNotReuseOldParameterSets | Nova câmera reutilizava SPS/PPS de outro SSRC | Passou: nova fonte precisa de seus próprios parâmetros antes de decodificar |
Validação MP4 — TestVideoValidationRejectsTruncatedMP4WithIntactMetadata | Arquivo truncado com metadados intactos era aceito como válido | Passou: decode integral recusa a saída corrompida |
H.264 adversarial — TestEveryParameterSetIsValidatedBeforeCapture | Nove subcasos aceitavam SPS fora dos limites escondido antes de outro SPS, conjuntos distintos na mesma AU ou extensões de NAL não suportadas | Passaram: valida todos os SPS e rejeita conjuntos ambíguos e extensões não suportadas; repetição idêntica continua permitida |
Os testes de observação foram escritos após adicionar as interfaces e executados antes de ligá-las ao serviço/motor; portanto, suas falhas acima foram comportamentais, não apenas erros de compilação.
Os dois testes do gravador foram reproduzidos em RED por go test -overlay, removendo somente as respectivas correções sem alterar o workspace. Sem o overlay, os mesmos testes passaram. A evidência mostra o efeito desses ajustes específicos, sem afirmar que toda a gravação existia no commit anterior.
Cobertura executada por camada
- Motor e serviço: recepção SRTP fragmentada, SPS/PPS, wrap do timestamp
0xfffffff0, orientação CVO3, câmera desligada, filtro até keyframe, callback removido, registro tardio após encerramento e engine sem suporte.go test -race ./internal/callspassou. No módulo do fork,go test -race .ego vet .passaram. Os novos testesTestVideoRecordingforam repetidos vinte vezes com detector de races. - Persistência:
TestVideoRecordingLifecycleAndFirstTerminalEvent,TestVideoRecordingCleanupRecoveryAndRetentionAreIndependenteTestVideoRecordingResultAndOutboxRollbackTogetherpassaram. Exercitam atraso de eventos, rollback de resultado/outbox, preservação do WAV, isolamento de worker, expiração e ausência de republicação após poda do evento.go test -race ./internal/storagepassou no banco descartável, incluindo a proteção do WAV durante processamento eTestRecordingCleanupReleasesVideoPinsWhenRecoveringStoppedWorker. - Captura e composição:
ATENDROZAP_TEST_VIDEO_TOOLS_REQUIRED=1 go test -race ./internal/callrecordingpassou usando FFmpeg/ffprobe reais e conteúdo sintético. As verificações incluem pixels nas rotações 0/90/180/270°, preto com câmera desligada, início remoto atrasado, mudança de resolução no mesmo SSRC, áudio AAC com energia nos dois canais e PTS0,0.2,1.5segundos preservados com wrap RTP.TestWaitDeadlineCancelsAndReapsRunningTranscoderverifica cancelamento e espera de um processo filho real, sem shell. A revisão adversarial final acrescentou nove subcasos RED/GREEN e foi seguida de nova execução completa do pacote com-race, ferramentas obrigatórias evet, todas aprovadas. - HTTP: testes focados de histórico, WAV, MP4, autenticação, Range, estados indisponíveis e limites de instância/worker passaram com
-race.TestOpenAPIpassou depois de acrescentar a operação à enumeração manual do teste de roteamento. Essa enumeração faltante foi corrigida; a rota já respondia nos testes HTTP específicos. - Console:
TestConsoleCallHistoryAndRecordingpassou com-race. Verifica player, download, processamento, proxy Range, exigência de sessão e ausência de tokens no HTML e nos headers de resposta ao navegador. - Documentação:
python3 scripts/sync_api_docs.py, a verificação--checke os três testes detest_build_docs.pypassaram; o catálogo agora contém 69 operações.
Verificação global e imagem opcional
Verificações globais concluídas nesta implementação:
go test -race ./...passou com PostgreSQL descartável eATENDROZAP_TEST_VIDEO_TOOLS_REQUIRED=1, incluindo os testes reais de FFmpeg/ffprobe com material sintético. A execução global foi repetida e passou após a última revisão adversarial de todos os SPS/PPS.go vet ./...ego build ./cmd/atendrozappassaram, também repetidos depois dessa revisão final.- Testes com detector de races de
github.com/purpshell/meowcaller/...e ovetdo fork passaram. - Revisão automatizada do inventário: quatro testes Python e verificação de 60 dependências passaram. Os dezoito testes Node também passaram.
python3 scripts/check_video_tools.pypassou: revisões e checksums das fontes nativas, avisos de licença e configuração revisada do FFmpeg foram conferidos. Essa verificação não substitui a execução da imagem pronta.
A primeira execução global expôs uma espera incompleta no teste de sessão TestServerErrorReceiptFailsSentMessage: o status persistido já podia ser failed antes de o evento assíncrono aparecer no outbox, causando unexpected events: [] na fixture em memória. O teste passou a aguardar as duas condições, sem mudar o código de produção. A execução focada repetida cem vezes passou, seguida de nova execução global verde.
A imagem padrão api foi construída com sucesso e scripts/test_engine_offline.sh passou. O target video-test, derivado de api-video e usando exatamente seus executáveis de FFmpeg 8.1.2/libx264 fixados, também foi construído e executado com sucesso:
docker build --target video-test -t atendrozap-video-test:local .
docker run --rm --network none --read-only \
--tmpfs /tmp:rw,nosuid,nodev,size=512m,mode=1777 \
--cpus 2 --memory 1g atendrozap-video-test:localPassaram vinte testes principais, além dos subcasos, incluindo composição MP4, decode integral, pixels, orientação, mudança de fonte e energia dos dois canais de áudio. A execução foi local em linux/arm64, com rede desligada e sistema de arquivos somente leitura, exceto /tmp limitado a 512 MiB. Os limites de duas CPUs e 1 GiB de memória descrevem o ensaio; não medem a capacidade de chamadas de produção.
Na primeira execução desse container, a extração de áudio no helper de teste solicitava o muxer bruto s16le, ausente do runtime mínimo. A composição MP4/AAC e a validação do arquivo já funcionavam. O helper passou a extrair WAV e ler seu chunk PCM, preservando as verificações de amostras e energia dos dois canais. Nenhum codec de produção precisou ser acrescentado. Após esse ajuste exclusivamente de teste, o container passou e go test -race ./... com banco descartável/ferramentas obrigatórias, go vet ./... e go build ./cmd/atendrozap foram repetidos e passaram.
O ensaio de CI em linux/amd64 ocorrerá após o push; não é declarado verde neste relatório. Revisão e inventário das ferramentas nativas estão em docs/LICENSE_REVIEW.md e third_party/video-tools/manifest.json.
Limites e validação física pendente
Padrões: um render ativo e oito esperando por worker, fila de captura de 16 MiB por chamada, limite de 1 GiB para captura e, separadamente, para MP4, timeout de 30 minutos incluindo espera. Segmentos temporários, WAV e MP4 coexistem: esses limites não representam uma cota total de disco. O guia documenta limites de segmentos, geometria, referências e NALs.
A composição usa CPU adicional e a fila reside no processo da API. Ainda não há dado para prometer quantidade de chamadas ou renders simultâneos numa VPS específica. A retenção deve considerar mídia, temporários e espaço livre; o banco guarda metadados, não os arquivos.
A rotação segue a interpretação CVO atual. Os testes sintéticos verificam a aplicação do valor, não comprovam que Android/iPhone emitam a orientação esperada. Permanecem pendentes: gravação de voz/vídeo prolongada com Android/iPhone, comparação visual e A/V nos dois sentidos, câmera desligada/religada, troca de aba, Wi-Fi interrompido por dez segundos e medição de CPU/memória/disco sob carga representativa. A estabilidade anterior de uma chamada real não constitui homologação desta gravação.
Preparar uma imagem pelo fluxo do repositório não conclui essas etapas e não implica ativação da opção. A publicação autorizada abaixo não substitui a homologação física pendente.
Ativação autorizada na VPS 1 — 19/09/2026
Às 22:34:50 BRT (2026-09-20T01:34:50Z), API e console passaram da revisão 0df3b65 para 4f7e63f, com a API construída pelo target api-video. FFmpeg e ffprobe 8.1.2 foram conferidos dentro do container em execução.
Configuração global do worker:
ATENDROZAP_CALLS_VIDEO_RECORDING_ENABLED=true
ATENDROZAP_CALLS_VIDEO_RECORDING_RETENTION_DAYS=7
ATENDROZAP_CALLS_RECORDING_RETENTION_DAYS=30
ATENDROZAP_CALLS_VIDEO_RECORDING_MAX_CONCURRENT=1Aplica-se às próximas videochamadas das instâncias calls existentes e futuras. O MP4 expira sete dias depois do encerramento, sem reduzir o prazo do WAV ou excluir o histórico. A API bloqueia o download na expiração; a limpeza horária remove os arquivos vencidos. Não há geração retroativa.
Antes do reinício foi confirmada a ausência de chamadas em andamento. Banco e configuração anteriores ficaram em backup privado na própria VPS: /opt/atendrozap/pre-deploy/video-recording-4f7e63f-20260920T013431Z/. Somente API e console foram recriados. A migração 18 foi aplicada no startup.
Vinte e dois testes principais do gravador, mais subcasos, passaram usando os codecs exatos da imagem publicada em linux/amd64, rede desativada, filesystem somente leitura, /tmp de 512 MiB, uma CPU e 1 GiB de memória. O binário de testes foi compilado com Go 1.26.4 para essa arquitetura. O material foi sintético; nenhum número foi pareado e nenhuma chamada real foi iniciada para a validação.
Após a ativação: readiness 200; capabilities.calls_video_recording=true; retenções anunciadas 7/30; OpenAPI público com o prazo independente de vídeo. As duas instâncias de chamadas e a instância de mensagens anteriormente conectadas voltaram a conectadas; a outra instância permaneceu desconectada. O Atendro já usa videoRecording.expiresAt para mostrar a disponibilidade.
O canal SSH original de build ficou sem retornar, apesar de a etapa remota ter terminado: as três imagens, binários ops, revisão e remoção do bundle foram conferidos por outra conexão, seguidos do teste isolado acima. O ship.sh agora usa keepalive a cada 15 segundos para que builds longos e silenciosos mantenham a conexão e falhas de transporte sejam detectadas.
Correção publicada: referências H.264 do navegador — 20/09/2026
Publicação autorizada da revisão 10346148924f058edeb5ea445621ea0ce6d704ed (1034614) na VPS 1, às 2026-09-20T02:47Z. API e console substituíram 4f7e63f; a API foi construída com o target api-video, mantendo FFmpeg/ffprobe 8.1.2. O limite de referências do SPS passou de quatro para 16, preservando os limites de geometria e orçamento do decoder. O encoder nativo do Chrome produzia SPS válidos com seis/sete referências, recusados antes do MP4. Detalhes e regressões em docs/DECISIONS.md e internal/callrecording/testdata/browser-h264/README.md.
Os 23 testes principais do pacote de gravação, mais subcasos, passaram na imagem Linux/amd64 preparada na VPS, com rede desativada, filesystem somente leitura, /tmp de 512 MiB, uma CPU e 1 GiB de memória. As fixtures do navegador produziram MP4 nos dois lados e passaram pela decodificação estrita do FFmpeg. Nenhuma chamada real foi iniciada.
Antes da ativação havia zero chamadas abertas, zero capturas de áudio e zero gravações de vídeo em captura/processamento. Backup privado do banco, configuração e informações de retorno: /opt/atendrozap/pre-deploy/h264-references-1034614-20260920T024713Z/. Banco, proxy e serviço de backups não foram reiniciados.
Após a publicação, o status operacional confirmou a revisão completa, readiness 200 e banco disponível. As duas sessões de chamadas e a sessão de mensagens anteriores voltaram a conectadas; a quarta instância continuou desconectada. Capabilities manteve captura de vídeo habilitada, vídeo por sete dias e áudio por 30 dias. A documentação pública passou a anunciar o limite 16. O status agregado degraded já existia antes da publicação; as pendências de webhooks não foram alteradas por esta correção.
A correção aplica-se às próximas capturas. Registros de vídeo já terminados com falha não são reprocessados, pois seus temporários foram removidos. A origem exata da falha histórica continua sem comprovação pelo log genérico; a incompatibilidade com o SPS do navegador foi reproduzida e corrigida.
Compatibilidade de orientação do cliente publicada — 20/09/2026
Às 2026-09-20T15:20:59Z (12:20:59 BRT), a revisão 92437cd substituiu 1034614 na API e no console da VPS 1. A imagem usa o target api-video. Foi habilitada ATENDROZAP_CALLS_VIDEO_RECORDING_INVERT_REMOTE_ORIENTATION=true para alinhar os novos MP4 ao padrão invert=true do controller do Atendro. A opção troca 90°/270° apenas no lado remoto durante a composição offline; o contrato RTP/API e a orientação do atendente permanecem independentes.
O relato visual mostrava o cliente de cabeça para baixo. A diferença entre as duas fórmulas foi reproduzida com um retrato sintético de topo vermelho e base azul; a regressão confere as quatro posições no MP4, alterações de orientação/resolução na mesma chamada e quadros sem novo metadado. Ela também confere que o lado local mantém a própria rotação. O print não registra os valores CVO originais, e preferências manuais do navegador continuam fora do contrato de gravação.
Os 24 testes principais do pacote, além dos subcasos, passaram com os codecs da imagem Linux/amd64 pronta, uma CPU, 1 GiB, rede desativada e filesystem somente leitura fora do /tmp sintético. A validação local completa passou com go test -race ./..., PostgreSQL 17 descartável, FFmpeg/ffprobe obrigatórios, go vet ./... e go build ./cmd/atendrozap. Nenhuma chamada real foi iniciada pelo agente.
Havia zero chamadas, capturas de áudio ou vídeos em processamento antes do reinício. Backup privado do banco e da configuração: /opt/atendrozap/pre-deploy/video-orientation-92437cd-20260920T152040Z/. Após a atualização: readiness 200, flag de compatibilidade confirmada no container, gravação habilitada e retenções 7/30. As duas sessões de chamadas e a sessão de mensagens voltaram a conectadas; as duas outras instâncias continuaram desconectadas. Nenhum erro JSON de startup foi observado.
Os dois vídeos já disponíveis não foram regravados. Os temporários e a linha do tempo de orientação original são descartados após compor o MP4, portanto não se aplica uma rotação fixa em lote que possa inverter trechos corretos. A confirmação visual no aparelho real depende de nova chamada.