O resultado parecia o tipo de melhoria que normalmente exigiria um codec mais novo: meus arquivos de vídeo ficaram várias vezes menores, enquanto a reprodução normal continuava parecendo boa e eu não via uma perda de qualidade evidente em tamanhos comuns de visualização.
Mas eu não tinha mudado para AV1, HEVC ou VP9. Continuava usando H.264 dentro de MP4.
O que mudou foi tudo ao redor do codec. Reconstruí a política de codificação para um workload específico: clipes curtos ilustrados e animados, audiência muito concentrada em mobile, largura de banda como principal custo recorrente e quase nenhuma preocupação com quanto tempo leva um encode offline feito uma única vez.
O baseline final ficou deliberadamente conservador para playback e deliberadamente caro para encoding: H.264 Main Profile @ Level 3.1, avc1, yuv420p 8-bit, CRF 28, x264 veryslow, limite de resolução na classe 720p, apenas frame rates úteis normalmente até 30 fps, references e B-frames limitados e faststart para entrega progressiva de MP4.
A otimização mais importante não era uma flag do FFmpeg
A mudança decisiva foi a forma como eu pensava em custo.
A codificação acontece uma vez. A transferência do arquivo acontece em cada reprodução.
Para vídeo em tempo real, gastar muito mais CPU para economizar um pouco de bitrate pode ser uma troca ruim. Meus arquivos são codificados offline e depois servidos repetidamente. Nesse modelo, economizar dez minutos no encode pode ter pouco valor se o encode mais rápido aumenta todas as futuras requisições.
Por isso -preset veryslow faz sentido para mim. Estou disposto a gastar CPU uma vez se o x264 puder usar esse trabalho para encontrar uma representação mais eficiente. O navegador não repete a busca do encoder; ele apenas decodifica o bitstream final.
A regra virou: gastar computação na etapa que acontece uma vez e economizar agressivamente bytes na etapa que se repete.
Por que continuei com H.264 em vez de perseguir o codec mais novo
Não estou dizendo que H.264 seja o codec com a melhor eficiência de compressão disponível. Não é. Codecs mais novos são atraentes quando a arquitetura de entrega consegue manter várias renditions e escolher a melhor para cada cliente.
Minha restrição era diferente: uma URL, um arquivo, um codec e o mínimo possível de surpresas de reprodução para uma audiência mobile-heavy.
Para isso, H.264 em MP4 continua sendo um baseline muito seguro. A Apple atualmente orienta desenvolvedores web a usar arquivos MP4 codificados em H.264 para vídeo estático no Safari. A documentação atual do Android inclui H.264 em MP4 e exige decoder Main Profile a partir do Android 6.0; suas recomendações também incluem 1280×720 a 30 fps como uma configuração H.264 HD. Veja os formatos de mídia suportados pelo Android.
Isso não quer dizer que dispositivos modernos estejam limitados a Main Profile ou Level 3.1. A Apple, por exemplo, em HLS geralmente prefere High Profile a Main ou Baseline. Escolhi Main@3.1 porque queria deliberadamente um envelope modesto de decodificação para um único MP4 estático, não porque a Apple exija isso.
Parei de codificar pixels que não precisavam existir
A resolução foi uma das maiores alavancas. Meu teto ficou aproximadamente em 1280×720 para paisagem, 720×1280 para retrato e cerca de 960×960 para conteúdo quadrado ou de orientação mista.
A regra mais importante é: nunca fazer upscale apenas para atingir esse teto.
Se a fonte é 900×600, transformá-la em 1280×720 não recupera detalhe. Apenas cria mais amostras para o encoder descrever. Uma fonte 1920×1080 pode ser reduzida para a classe 720p, enquanto uma fonte 900×600 pode continuar próxima de 900×600. O teto é um máximo, não um alvo.
Parece simples, mas remover pixels desnecessários pode economizar mais do que muitos tweaks obscuros do encoder.
Parei de pagar por frames que a fonte não tinha de verdade
Frame rate é outro multiplicador. Se uma animação possui aproximadamente 16 estados visuais úteis por segundo, armazená-la a 30 ou 60 fps não cria automaticamente um movimento melhor. Muitas vezes só adiciona amostras temporais repetidas ou sintetizadas que ainda precisam ser codificadas.
Minha política é preservar a cadência útil da fonte e normalmente ficar em 30 fps ou menos. Para esse tipo de conteúdo, 12, 15, 16, 18, 20, 24, 25 ou 30 fps podem ser todos razoáveis quando realmente descrevem a fonte.
Também prefiro um CFR limpo no output gerado. VFR não é inerentemente quebrado; CFR simplesmente facilita timestamps, contagem de frames, validação de duração, seeking e verificação posterior no meu pipeline.
O princípio geral é mais importante do que um único número: não pagar largura de banda por informação temporal que a fonte não contém.
CRF 28 é uma decisão do workload, não um número mágico
Eu não queria forçar cada clipe para o mesmo bitrate alvo. Uma ilustração quase estática e uma cena com movimento complexo não precisam da mesma quantidade de bits para parecer aceitáveis.
Por isso uso o modo CRF do x264 e, para esse workload ilustrado focado em bandwidth, fiquei em torno de -crf 28. O FFmpeg documenta CRF em libx264 como controle de taxa com qualidade constante; veja a documentação de codecs do FFmpeg.
CRF 28 é deliberadamente agressivo. Eu não copiaria esse valor cegamente para grão de filme, vídeo de câmera com ruído, texto minúsculo em screen recording ou um workload em que fidelidade importa mais do que largura de banda.
Também não tenho uma métrica perceptual universal provando que CRF 28 é transparente. O que posso dizer do meu próprio conteúdo é mais restrito: os arquivos ficaram muito menores e continuaram parecendo normais durante a reprodução comum. É uma observação prática, não uma afirmação de lossless visual.
Veryslow é caro para o encoder, não automaticamente para o decoder
Meu preset é -preset veryslow. Um preset mais lento dá ao x264 mais oportunidades de buscar decisões eficientes de prediction e coding. O custo é CPU e tempo do encoder.
A distinção essencial é que esforço do encoder e complexidade do decoder não são a mesma coisa.
Posso deixar o x264 trabalhar muito e ainda limitar separadamente o stream final. Meu contrato conservador de saída é:
H.264 Main Profile
Level 3.1
8-bit yuv420p
avc1
refs = 4
B-frames = 5
B-pyramid = normal
open GOP = disabled
O FFmpeg expõe CRF, presets, tuning, restrições de profile, reference frames e B-frames separadamente. É exatamente assim que penso neles: deixar o encoder procurar com muito esforço e manter o lado de playback comum.
GOP e VBV são guardrails, não o principal controle de qualidade
Para esses clipes progressivos curtos, uso um GOP máximo de aproximadamente cinco segundos: cerca de -g 150 a 30 fps, -g 120 a 24 fps ou -g 80 a 16 fps.
É uma escolha para meu workload, não uma regra universal. Adaptive streaming tem outras restrições; por exemplo, a Apple recomenda IDR a cada dois segundos para HLS. Não transfiro automaticamente essa regra de HLS para MP4 estáticos progressivos curtos.
Também uso aproximadamente:
-maxrate:v 4M
-bufsize:v 8M
Esses valores são tetos contra picos incomuns de bitrate. Eles não significam “codificar tudo a 4 Mbps”. O CRF continua controlando a alocação normal de bits, então clipes fáceis ainda podem ficar muito pequenos.
Também deixei o contêiner MP4 sem graça
Uso explicitamente avc1. A documentação atual de HLS da Apple recomenda sample formats como avc1 em vez de avc3. Isso não foi o que diminuiu meus arquivos, mas combina com o objetivo de gerar um H.264-in-MP4 convencional.
Também uso -movflags +faststart. A documentação de formatos do FFmpeg diz que faststart move o índice MP4 moov para o início do arquivo. Os requisitos do Android para streaming HTTP também dizem que, em MPEG-4, moov precisa vir antes de mdat, depois de ftyp.
ftyp
moov
mdat
Faststart não melhora a compressão. Ele torna a reprodução progressiva por HTTP menos problemática.
Para SDR comum uso yuv420p 8-bit e sinalizo BT.709 com limited/video range. Se o clipe não precisa de áudio, não crio uma faixa de áudio. Para conteúdo ilustrado uso também -tune animation; trato isso como uma decisão específica do conteúdo, não parte do contrato universal de compatibilidade.
O perfil central do FFmpeg
Para uma fonte ilustrada a 30 fps, a parte central do comando fica aproximadamente assim:
ffmpeg -i input \
-c:v libx264 \
-preset veryslow \
-tune animation \
-crf 28 \
-profile:v main \
-level:v 3.1 \
-pix_fmt yuv420p \
-tag:v avc1 \
-refs 4 \
-bf 5 \
-g 150 \
-maxrate:v 4M \
-bufsize:v 8M \
-x264-params "open-gop=0:b-pyramid=normal:nal-hrd=none" \
-color_range tv \
-color_primaries bt709 \
-color_trc bt709 \
-colorspace bt709 \
-an \
-movflags +faststart \
output.mp4
Scaling e frame rate não estão hardcoded de propósito. Uma fonte 900×600 não deve ser ampliada apenas porque o teto é 1280×720, e uma animação naturalmente de baixo frame rate não deve ser forçada a 30 fps só porque o exemplo usa -g 150.
O comando implementa a política; ele não é a política em si.
Por que os arquivos ficaram várias vezes menores
Não houve uma flag milagrosa.
A redução veio da soma de várias decisões que removiam desperdícios diferentes: pixels desnecessários, frames desnecessários, pensamento de bitrate fixo, configurações baratas de encoder, keyframes frequentes demais e streams de que eu não precisava.
Por isso dizer “este arquivo é H.264” conta surpreendentemente pouco sobre o tamanho. Dois encodes H.264 da mesma fonte podem ser muito diferentes porque o nome do codec não descreve resolução, FPS, rate control, preset, estrutura GOP, profile ou preparação da fonte.
No meu caso, mudar essas decisões ao redor do codec importou mais do que mudar o próprio codec.
O que esse resultado não prova
Eu não isolei cada configuração em um experimento controlado, então não posso atribuir honestamente uma porcentagem exata da economia separadamente a veryslow, CRF 28, redução de resolução ou redução de FPS.
Também não posso afirmar que todo output CRF 28 seja perceptualmente transparente. “Sem perda de qualidade evidente” é minha observação para esse workload ilustrado em tamanhos normais de visualização, não uma garantia científica para qualquer vídeo.
E não estou dizendo que um único arquivo H.264 seja a arquitetura certa para todo site. Multiple renditions, adaptive streaming, HDR, 4K e codec negotiation mudam os trade-offs.
O resultado é mais estreito e mais útil: para uma biblioteca bandwidth-first e mobile-heavy de clipes curtos ilustrados e animados, em que tempo de encoding é barato e playback previsível importa, esse perfil deixou meus arquivos várias vezes menores mantendo uma aparência normal na reprodução comum.
A regra que uso agora
Antes eu pensava em otimização de vídeo principalmente como um problema de configuração do encoder. Hoje penso como um problema de custo ao longo da vida do arquivo.
O encoder pode rodar uma vez. Os bytes podem atravessar a rede milhares ou milhões de vezes.
Isso muda o significado de “caro”.
Estou disposto a gastar CPU uma vez. Estou muito menos disposto a enviar, em cada futura requisição, pixels criados por upscale, frames que não acrescentam movimento útil ou bitrate de que o conteúdo não precisa.
O codec continuou sem graça: H.264 em MP4. A otimização aconteceu ao redor dele.
Para meu workload, a lição é mais útil do que qualquer flag isolada do FFmpeg: otimize o custo que você paga repetidamente, não o custo que paga uma única vez.