O número que finalmente tornou essa otimização concreta foi simples: um vídeo real de produção caiu de cerca de 280 MB para 50 MB depois da nova política H.264. Isso significa cerca de 5,6× menor, economizando aproximadamente 230 MB ou 82% do tamanho original.
Eu não consegui isso migrando a entrega para AV1, HEVC ou VP9. A saída continuou sendo H.264 em MP4. O que mudou foi a política ao redor do codec: menos pixels desnecessários, menos amostras temporais inúteis, uma meta de qualidade muito menos conservadora, mais trabalho do codificador uma única vez e um contrato de decodificador deliberadamente limitado.
Era uma carga muito específica: vídeos curtos ilustrados e animados, cerca de 80% do tráfego em dispositivos móveis, largura de banda como custo recorrente e codificação prévia, em que gastar CPU uma vez é barato comparado a servir arquivos grandes repetidamente.
De forma resumida, as políticas antiga e nova eram:
configuração anterior
H.264 Main @ Level 4.0
CRF 19
predefinição slow
até 1920x1080 / 1080x1920
30 fps
refs = 3
quadros B = 3
GOP ≈ 2 segundos
VBV ≈ 10M / 20M
configuração nova
H.264 Main @ Level 3.1
CRF 28
predefinição veryslow
limite da classe 720p, sem ampliação
CFR útil, normalmente <= 30 fps
refs = 4
quadros B = 5
GOP ≈ 5 segundos
VBV ≈ 4M / 8M
yuv420p / avc1 / faststart
O resultado medido: de ~280 MB para ~50 MB
Tenho várias medições reais de produção, mas elas não são todas o mesmo experimento. Manter essas categorias separadas importa mais do que escolher a porcentagem mais chamativa.
| Medição | Antes | Depois | Redução |
|---|---|---|---|
| Mesmo vídeo concreto | ~280 MB | ~50 MB | ~5,6× menor / ~82% menos |
| Etapa anterior do mesmo arquivo | ~350 MB | ~238 MB | ~1,47× menor / 32% menos |
A primeira linha é a evidência mais limpa para o título: o mesmo vídeo concreto antes e depois da política nova, aproximadamente 280 MB contra 50 MB. Para esse par exato, os registros antigos recuperados não preservaram uma linha de duração/taxa de bits, então não invento uma. A mudança de tamanho sozinha já mostra cerca de 5,6×.
O caso ~350→238 MB foi uma etapa anterior de otimização do mesmo exemplo naquela fase da cadeia de processamento. O resultado tinha aproximadamente 1264×720, 30 fps, ~500 segundos, sem áudio e cerca de 3,8 Mbps. A conta fecha: 3,8 Mbps por aproximadamente 500 segundos dão cerca de 238 MB. Já era 32% menor que ~350 MB, mas ainda muito grande para meu objetivo de largura de banda.
A biblioteca antiga também mostrava que arquivos H.264 grandes não eram uma exceção isolada. Uma auditoria tinha 238 vídeos de produção totalizando 6,37 GB: 117 H.264 e 121 AV1. 101 arquivos tinham pelo menos 20 MB e 34 pelo menos 50 MB. Alguns H.264 grandes eram:
| Tamanho | Duração | Taxa de bits média |
|---|---|---|
| 121,0 MB | 4:25 | 3,83 Mbps |
| 101,2 MB | 5:19 | 2,659 Mbps |
| 92,78 MB | 4:44 | 2,735 Mbps |
| 90,10 MB | 3:55 | 3,206 Mbps |
| 89,74 MB | 4:41 | 2,676 Mbps |
São conteúdos diferentes, portanto a tabela serve como contexto, não como teste A/B. Ainda assim, ela mostra que o exemplo antigo de 3,8 Mbps não era um caso isolado: vários H.264 grandes da biblioteca anterior realmente ficavam perto de 2,6–3,8 Mbps.
A otimização mais importante não era uma opção do FFmpeg
A maior mudança foi a forma como passei a pensar em custo.
A codificação acontece uma vez. A transferência do arquivo acontece toda vez que alguém o solicita.
Em vídeo em tempo real, gastar muito mais CPU para economizar um pouco de taxa de bits pode ser uma troca ruim. Meus arquivos são codificados previamente e depois servidos repetidamente. Nesse modelo, economizar dez minutos na codificação pode não ter quase nenhum valor econômico se a codificação mais rápida aumentar cada transferência futura.
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 feita pelo codificador; ele apenas decodifica o fluxo de bits final.
A regra ficou simples: gastar computação na etapa que acontece uma vez e ser econômico com 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 podem ser atraentes quando o sistema de distribuição consegue manter várias versões e escolher a melhor para cada dispositivo.
Minha restrição era outra: uma URL, um arquivo, um codec e o mínimo possível de problemas de reprodução em uma audiência majoritariamente móvel.
Para esse trabalho, H.264 em MP4 continua sendo uma base muito segura. A Apple orienta atualmente desenvolvedores de sites 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 um decodificador de perfil Main a partir do Android 6.0; suas recomendações de reprodução H.264 também listam 1280×720 a 30 fps para HD, observando que HD não está disponível em todos os dispositivos. Veja os formatos de mídia compatíveis com Android.
Isso não significa que dispositivos modernos estejam limitados a perfil Main ou Nível 3.1. Por exemplo, as recomendações da Apple para HLS geralmente preferem perfil High a Main ou perfil Baseline. Escolhi Main@3.1 porque queria requisitos de decodificação deliberadamente modestos para um único MP4 estático, não porque a Apple exija esse perfil.
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 material quadrado ou de orientação mista.
A regra mais importante é: nunca ampliar apenas para chegar ao teto.
Se a fonte é 900×600, transformá-la em 1280×720 não recupera detalhe. Só cria mais amostras para o codificador descrever. Uma fonte 1920×1080 pode ser reduzida para a classe de 720p, enquanto uma fonte 900×600 pode continuar perto de 900×600. O teto é um máximo, não um alvo.
Parece simples, mas remover pixels desnecessários pode ter mais impacto do que muitos ajustes obscuros do codificador.
Esse limite não foi escolhido por ser um número redondo. Um quadro de 1920×1080 contém 2.073.600 pixels; 1280×720 contém 921.600. Portanto, passar de 1080p para 720p remove cerca de 55,6% das amostras espaciais antes mesmo de o codificador começar a decidir como comprimir.
Também considerei 540p como padrão geral. Porém, 960×540 tem apenas 518.400 pixels: 43,75% menos que 1280×720, deixando 56,25% das amostras de 720p. Em material ilustrado, essas amostras descrevem linhas finas, olhos, cabelo, dedos, rostos e contornos nítidos. Se eu ainda precisar de menos bytes, prefiro testar primeiro um CRF ligeiramente mais alto em vez de descartar às cegas outros 43,75% da informação espacial. A quantização pode ser alterada em uma nova codificação; o detalhe removido pela redução de resolução já foi perdido.
Por isso, a classe 720p é meu limite geral prudente, não uma afirmação de que 540p seja ruim. Para um arquivo específico, uma versão 540p medida pode vencer. Eu só não transformo esse corte espacial irreversível em regra global sem dados.
Parei de pagar por quadros que a fonte não tinha de verdade
A taxa de quadros é outro multiplicador. Se uma animação contém cerca de 16 estados visuais realmente úteis por segundo, armazená-la a 30 ou 60 fps não cria automaticamente um movimento melhor. Muitas vezes isso só acrescenta amostras temporais repetidas ou sintetizadas que ainda precisam ser representadas.
Minha política é preservar a cadência útil da fonte e normalmente ficar em 30 fps ou menos. Para esse tipo de material, 12, 15, 16, 18, 20, 24, 25 ou 30 fps podem ser perfeitamente razoáveis quando realmente descrevem a fonte.
Também prefiro uma taxa de quadros constante e limpa na saída gerada. VFR não é inerentemente problemático; CFR apenas simplifica marcas de tempo, contagem de quadros, verificações de duração, busca no vídeo e validações posteriores no meu fluxo de processamento.
O princípio geral é mais útil do que qualquer valor específico de FPS: não pagar largura de banda por informação temporal que a fonte não contém.
CRF 28 é uma escolha para este caso de uso, não um número mágico
Eu não queria forçar todos os vídeos para a mesma taxa de bits 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 material ilustrado com prioridade em largura de banda, fiquei em torno de -crf 28. O FFmpeg documenta CRF no 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 sem testar para grão de filme, vídeo de câmera com muito ruído, texto minúsculo na tela ou um caso em que fidelidade importe mais do que largura de banda.
Também não tenho uma métrica perceptiva universal que prove que CRF 28 seja transparente. O que posso dizer sobre meu material é mais limitado: os arquivos ficaram muito menores e continuaram parecendo normais para mim durante a reprodução comum. É uma observação prática, não uma afirmação de que CRF 28 seja visualmente sem perdas.
Veryslow é caro para o codificador, não automaticamente para o decodificador
Minha predefinição é -preset veryslow. Uma predefinição mais lenta dá ao x264 mais oportunidades de procurar decisões eficientes de predição e codificação. O custo aparece em CPU e tempo de codificação.
A distinção importante é que o esforço do codificador e a complexidade do decodificador não são a mesma coisa.
Posso deixar o x264 trabalhar muito e, ao mesmo tempo, limitar separadamente o fluxo final. Meus requisitos conservadores de saída são:
H.264, perfil Main
Level 3.1
yuv420p de 8 bits
avc1
refs = 4
quadros B = 5
B-pyramid = normal
GOP aberto = desativado
O FFmpeg expõe CRF, predefinições, ajuste específico, restrições de perfil, quadros de referência e quadros B separadamente. É assim que penso neles: deixo o codificador procurar bastante, mas mantenho o lado da reprodução convencional.
GOP e VBV são limites de segurança, não o principal controle de qualidade
Para esses vídeos 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.
Essa é uma escolha para meu caso de uso, não uma regra universal. Transmissão adaptativa tem outras restrições; por exemplo, as recomendações da Apple para criação de HLS indicam IDRs a cada dois segundos. Não aplico automaticamente essa regra de HLS a arquivos MP4 estáticos, curtos e progressivos.
Também uso aproximadamente:
-maxrate:v 4M
-bufsize:v 8M
Esses valores são tetos contra picos incomuns de taxa de bits. Eles não significam “codificar tudo a 4 Mbps”. O CRF continua responsável pela alocação normal de bits, então vídeos simples ainda podem ficar muito pequenos.
Também deixei o contêiner MP4 bem convencional
Uso explicitamente avc1. A documentação atual de HLS da Apple recomenda formatos de amostra como avc1 em vez de avc3. Isso não foi o que reduziu meus arquivos, mas combina com o objetivo de produzir H.264 em MP4 de forma 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 transmissão por 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 apenas torna a reprodução progressiva por HTTP menos problemática.
Para SDR comum uso yuv420p de 8 bits e sinalizo BT.709 com faixa de vídeo limitada. Se o vídeo não tem áudio, não crio artificialmente uma faixa de áudio. Para esse conteúdo ilustrado também uso -tune animation; trato isso como uma escolha específica do tipo de conteúdo, não como parte dos requisitos universais 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
As etapas de escala e taxa de quadros não estão fixadas aqui de propósito. Uma fonte 900×600 não deve ser ampliada só porque o teto é 1280×720, e uma animação naturalmente com baixa taxa de quadros 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 opção milagrosa.
A redução veio da combinação de várias decisões que eliminavam desperdícios diferentes: pixels desnecessários, quadros desnecessários, um alvo de qualidade conservador demais, configurações do codificador que favoreciam velocidade em vez de eficiência, quadros-chave frequentes demais e fluxos que eu não precisava.
Por isso dizer “este arquivo é H.264” conta surpreendentemente pouco sobre o tamanho. Duas codificações H.264 da mesma fonte podem ser muito diferentes porque o nome do codec não informa resolução, taxa de quadros, método de controle de taxa, predefinição, estrutura GOP, perfil nem preparação da fonte.
No meu caso, mudar essas decisões ao redor do codec teve mais impacto 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 da taxa de quadros.
Também não posso afirmar que toda saída com CRF 28 seja perceptualmente transparente. “Sem perda de qualidade evidente” é minha observação para esse material 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 sítio. Várias versões, transmissão adaptativa, HDR, 4K e negociação de codec mudam os compromissos.
O que realmente posso afirmar é mais limitado: em uma biblioteca de vídeos curtos ilustrados e animados em que largura de banda é prioridade, a audiência é majoritariamente móvel, o tempo de codificação custa pouco e a reprodução previsível importa, esse perfil deixou meus arquivos várias vezes menores e eles continuaram parecendo normais na reprodução comum.
Como esse resultado mudou minha estratégia de otimização
Antes eu pensava em otimização de vídeo principalmente como um problema de configurações do codificador. Hoje penso nela como um problema de custo ao longo da vida do arquivo.
O codificador 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 solicitação futura, pixels criados por ampliação, quadros que não acrescentam movimento útil ou taxa de bits 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 caso de uso, a lição é mais clara do que qualquer opção isolada do FFmpeg: otimize o custo que você paga repetidamente, não o custo que paga uma única vez.