O Encoder Ignorou o tune=still Desde o Primeiro Build

Por Brenner Cruvinel • 6 minutos de leitura

o sintoma foi um número que não batia. eu tinha uma bateria de encoders rodando em paralelo: dezenove configurações entre av1, avif, x264, x265, webp e jpeg xl com perda. até vvc entrou, mais por curiosidade do que por esperança. cada uma calibrada pra mesma qualidade visual, pra saber se o av1 do nest era o mais compacto ou só o mais próximo de casa. um dos agentes reproduziu o build do nest na mão, com os mesmos parâmetros, e o arquivo saiu com outro tamanho. mesma carta, mesmo crf, mesmo preset, bytes diferentes. eu li isso de manhã e minha primeira reação foi que o agente tinha errado o comando.

o probe falhava e ninguém avisava

não tinha errado. o forge pede pro encoder o tune=still, o modo de foto parada, que é o certo pra um stream onde cada frame é uma foto independente. antes de pedir, ele roda um probe pra ver se o encoder aceita esse modo. o probe rodava sem um parâmetro que o modo exige, falhava, e o código caía num modo genérico sem avisar ninguém. o manifest gravava a spec, que dizia "still", não o que o encoder tinha feito. desde o primeiro build eu vinha lendo um manifest que não batia com o binário, e o único lugar que registrava o modo era o lugar errado.

a diferença mediu onze pontos de qualidade visual por dez por cento a mais de bytes, no mesmo crf. o build antigo era o pior ponto da curva: menor, mas feio de um jeito que não compensa. e ninguém teria achado olhando o vídeo, porque a cara do frame no player não diz qual tune foi usado. só bytes contra bytes de um encode independente. e foi por acaso: a bateria de encoders existia pra responder outra pergunta, e o encode manual do baseline era só o ponto de referência dela. se eu tivesse calibrado a bateria contra o manifest em vez de contra um encode feito do zero, o bug teria continuado invisível, e todos os números da série teriam saído dez por cento otimistas em bytes e onze pontos pessimistas em qualidade. agora o manifest grava o modo que o encoder de fato usou, e o probe passa o parâmetro certo.

e mesmo depois do fix tinha uma segunda camada. o svt-av1 tem dois modos que dá pra chamar de "still": um otimizado pra uma métrica de similaridade estrutural, que foi o que o probe consertada passou a escolher, e outro chamado de qualidade de imagem, que só funciona em stream todo de keyframe. o segundo rende mais um ponto de qualidade por um por cento de bytes. pouco, mas é o modo certo pra foto parada, e o build recomendado do corpus inteiro saiu com ele. o manifest agora diz qual dos dois, em número, porque "still" sozinho já me enganou uma vez.

variante nas mesmas 2048 cartastamanhoqualidade visual
jpeg xl repack, sem perda1,12x menorigual à fonte
av1 do primeiro build3,3x menor52
av1 no tune=still3,0x menor63
av1 com inter2,5x menor62
avif5,5x menor43

a linha do avif parece a vencedora e não é. está num ponto muito mais baixo da curva de qualidade, não dá pra comparar pelo mesmo número. calibrado pra qualidade igual à do av1, ele fica uns 12% menor, e isso deixou uma pergunta aberta sobre qual backend usar pra stills. e o inter, que era minha aposta favorita, ficou maior que o tune=still. isso eu explico no próximo post, porque a configuração estava errada e o problema era outro.

o resto da bateria de dezenove eu não vou tabelar, mas dois achados ficaram comigo. o x264, codec de 2003, foi o mais rápido dentro da janela de qualidade por uma margem enorme, e o vvc, o codec mais novo da lista, custou cem vezes o tempo do x264 pra ficar no meio da tabela. e dois presets diferentes do svt-av1 cuspiram streams byte a byte idênticos, porque a versão que eu uso mapeia os dois pro mesmo modo interno quando o stream é todo de keyframe. mais um caso de parâmetro que eu passava e o encoder ignorava, dessa vez documentado. e o jpeg xl com perda, que eu esperava competir, só compete em qualidade alta, bem acima da janela que interessa pra tile de deck. é um codec bom pra um alvo que esse corpus não pede.

o capcut tava jogando carta fora

eu tinha escrito nas apostas que acelerar o vídeo cortava bytes, porque tinha feito isso no capcut e o arquivo saiu com metade do tamanho. reproduzi programaticamente: acelerar sessenta vezes, exportar a trinta frames por segundo em h.264, do jeito que o editor faz. o arquivo saiu com metade dos bytes. e com metade das cartas.

o export amostra a linha do tempo acelerada e descarta o que cai entre um frame e outro. das 2048 cartas, 993 sumiram. o que sobrou foi re-encodado em h.264 em cima de av1 que já tinha perda. o capcut não comprimiu nada, jogou metade do dataset fora e chamou de otimização, e eu olhei o tamanho do arquivo e acreditei. mudar só o fps, sem acelerar, não muda um byte. é anotação de tempo no container, como o ffprobe já tinha me dito no primeiro dia e eu não tinha registrado direito.

a aposta errada rendeu um bug certo. com fps diferente de um no manifest, o código que busca frame pelo índice pedia um instante que não existia, porque assumia que ordinal e timestamp eram a mesma coisa. corrigido, e testado com o fps que eu nunca deveria ter mudado. o ganho real que o capcut apontava, redundância entre frames vizinhos, existe. só que ele se mede com inter, sem perder carta, e é o assunto da madrugada seguinte.

o que mais o manifest está me contando errado

fechei o dia com duas coisas validadas por hash e uma dúvida maior do que quando comecei. o modo do encoder era uma constante que eu achava que estava ligada, e estava desligada desde o começo. o capcut era um número que eu achava que era compressão, e era perda de dado. os dois estavam num manifest ou numa exportação que eu tinha lido e aceitado sem checar contra nada.

se o encoder não tava fazendo o que o manifest dizia, a pergunta que ficou foi o que mais eu estava lendo em resumo em vez de validar no disco. eu não sabia ainda que a resposta ia chegar de madrugada, com reunião marcada.