El resultado parecía el tipo de mejora que normalmente exige cambiar a un códec más nuevo: mis archivos de vídeo pasaron a ocupar varias veces menos, mientras que la reproducción normal seguía viéndose bien y yo no apreciaba una pérdida de calidad evidente a tamaños de visualización habituales.
Pero no había cambiado a AV1, HEVC ni VP9. Seguía usando H.264 dentro de MP4.
Lo que cambió fue todo lo que rodeaba al códec. Rediseñé la política de codificación para una carga concreta: clips cortos ilustrados y animados, una audiencia muy orientada a móvil, el ancho de banda como coste recurrente principal y casi ninguna preocupación por cuánto tarda una codificación offline que solo se hace una vez.
El baseline terminó siendo deliberadamente conservador para la reproducción y deliberadamente caro para el encoder: H.264 Main Profile @ Level 3.1, avc1, yuv420p de 8 bits, CRF 28, x264 veryslow, un techo de resolución de clase 720p, solo frame rates útiles normalmente de hasta 30 fps, referencias y B-frames acotados y faststart para entrega progresiva de MP4.
La optimización más importante no era un flag de FFmpeg
El cambio decisivo fue cómo pensaba en el coste.
La codificación ocurre una vez. La transferencia del archivo ocurre en cada reproducción.
En vídeo en tiempo real, gastar mucha más CPU para ahorrar algo de bitrate puede ser un mal intercambio. Mis archivos se codifican offline y luego se sirven una y otra vez. En ese modelo, ahorrar diez minutos en el encode puede no tener casi valor si esa codificación más rápida hace que cada futura descarga sea mayor.
Por eso -preset veryslow tiene sentido para mí. Estoy dispuesto a gastar CPU una vez si x264 puede usar ese trabajo para encontrar una representación más eficiente. El navegador no repite la búsqueda del encoder; solo decodifica el bitstream final.
La regla pasó a ser sencilla: gastar cómputo en el paso que ocurre una vez y ser tacaño con los bytes en el paso que se repite.
Por qué mantuve H.264 en vez de perseguir el códec más nuevo
No afirmo que H.264 sea el códec con mejor compresión disponible. No lo es. Los códecs más nuevos son muy interesantes cuando la arquitectura puede mantener varias renditions y escoger la mejor para cada cliente.
Mi restricción era distinta: una URL, un archivo, un códec y el menor número posible de sorpresas de reproducción en una audiencia muy móvil.
Para ese trabajo, H.264 en MP4 sigue siendo un baseline muy seguro. Apple actualmente indica a los desarrolladores web que usen archivos MP4 codificados con H.264 para vídeo estático en Safari. La documentación actual de Android incluye H.264 en MP4 y exige un decoder Main Profile a partir de Android 6.0; sus recomendaciones de reproducción también incluyen 1280×720 a 30 fps como configuración H.264 HD. Véanse los formatos multimedia compatibles de Android.
Eso no significa que los dispositivos modernos estén limitados a Main Profile o Level 3.1. Apple, por ejemplo, suele preferir High Profile frente a Main o Baseline para HLS. Elegí Main@3.1 porque quería deliberadamente un envelope modesto para el decoder en un único MP4 estático, no porque Apple lo exija.
Dejé de codificar píxeles que no necesitaban existir
La resolución fue una de las palancas más grandes. Mi techo quedó aproximadamente en 1280×720 para horizontal, 720×1280 para vertical y alrededor de 960×960 para material cuadrado o de orientación mixta.
La regla más importante es: no hacer upscale solo para alcanzar ese techo.
Si una fuente es 900×600, llevarla a 1280×720 no recupera detalle. Solo crea más muestras que el encoder tiene que describir. Una fuente 1920×1080 puede reducirse a la clase 720p, mientras que una de 900×600 puede quedarse cerca de 900×600. El techo es un máximo, no un objetivo.
Suena trivial, pero eliminar píxeles innecesarios puede ahorrar más que muchos ajustes oscuros del encoder.
Dejé de pagar por fotogramas que la fuente realmente no tenía
El frame rate es otro multiplicador. Si una animación contiene aproximadamente 16 estados visuales útiles por segundo, almacenarla a 30 o 60 fps no crea automáticamente un movimiento mejor. A menudo solo añade muestras temporales repetidas o sintetizadas que igualmente deben codificarse.
Mi política es preservar la cadencia útil de la fuente y normalmente no superar 30 fps. Para este tipo de material, 12, 15, 16, 18, 20, 24, 25 o 30 fps pueden ser perfectamente razonables si realmente describen la fuente.
También prefiero un CFR limpio en el output generado. VFR no está roto por definición; CFR simplemente simplifica timestamps, conteo de frames, comprobaciones de duración, seeking y validación posterior en mi pipeline.
El principio general es más útil que un número concreto: no pagar ancho de banda por información temporal que la fuente no contiene.
CRF 28 es una decisión para mi workload, no un número mágico
No quería forzar todos los clips a la misma tasa de bits objetivo. Una ilustración casi estática y una escena con movimiento complejo no necesitan el mismo número de bits para verse aceptables.
Por eso uso el modo CRF de x264 y, para este workload ilustrado centrado en ancho de banda, me quedé alrededor de -crf 28. FFmpeg documenta CRF en libx264 como control de tasa de calidad constante; véase la documentación de códecs de FFmpeg.
CRF 28 es deliberadamente agresivo. No lo copiaría a ciegas para grano de película, vídeo de cámara ruidoso, texto diminuto en capturas de pantalla o una carga donde la fidelidad importe más que el ancho de banda.
Tampoco tengo una métrica perceptual universal que demuestre que CRF 28 es transparente. Lo que sí puedo afirmar para mi material es más limitado: los archivos se hicieron mucho más pequeños y siguieron viéndose normales durante una reproducción corriente. Es una observación práctica, no una afirmación de pérdida visual cero.
Veryslow es caro para el encoder, no automáticamente para el decoder
Mi preset es -preset veryslow. Un preset más lento da a x264 más oportunidades de buscar decisiones de predicción y codificación eficientes. El precio es CPU y tiempo de encoder.
La distinción clave es que el esfuerzo del encoder y la complejidad del decoder no son lo mismo.
Puedo dejar que x264 trabaje mucho y, aun así, limitar por separado el stream final. Mi contrato conservador de salida es:
H.264 Main Profile
Level 3.1
8-bit yuv420p
avc1
refs = 4
B-frames = 5
B-pyramid = normal
open GOP = disabled
FFmpeg expone CRF, presets, tuning, restricciones de perfil, reference frames y B-frames por separado. Así es exactamente como los trato: el encoder puede buscar de forma cara, pero el lado de reproducción debe seguir siendo ordinario.
GOP y VBV son límites, no el control principal de calidad
Para estos clips progresivos cortos uso un GOP máximo de aproximadamente cinco segundos: cerca de -g 150 a 30 fps, -g 120 a 24 fps o -g 80 a 16 fps.
Es una elección para mi workload, no una regla universal. El adaptive streaming tiene otras restricciones; Apple, por ejemplo, recomienda IDR cada dos segundos para HLS. No traslado automáticamente esa regla de HLS a MP4 estáticos y progresivos de pocos segundos.
Además uso aproximadamente:
-maxrate:v 4M
-bufsize:v 8M
Son techos contra picos de bitrate inusuales. No significan “codificar todo a 4 Mbps”. CRF sigue controlando la asignación normal de bits, así que los clips fáciles pueden seguir quedando muy pequeños.
También hice aburrido el contenedor MP4
Uso explícitamente avc1. La documentación actual de Apple para HLS recomienda sample formats como avc1 en lugar de avc3. Eso no es lo que redujo mis archivos, pero encaja con el objetivo de producir un H.264-in-MP4 convencional.
También uso -movflags +faststart. La documentación de formatos de FFmpeg explica que faststart mueve el índice moov del MP4 al principio del archivo. Los requisitos de streaming HTTP de Android también indican que en MPEG-4 moov debe preceder a mdat después de ftyp.
ftyp
moov
mdat
Faststart no mejora la compresión. Hace menos problemática la reproducción progresiva por HTTP.
Para SDR normal uso yuv420p de 8 bits y señalizo BT.709 con rango limited/video. Si el clip no necesita audio, no fabrico una pista de audio. Para material ilustrado uso además -tune animation; lo considero una elección específica del contenido, no parte del contrato universal de compatibilidad.
El perfil central de FFmpeg
Para una fuente ilustrada de 30 fps, la parte principal del comando se parece aproximadamente a esto:
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
El escalado y el frame rate no están hardcodeados a propósito. Una fuente 900×600 no debería ampliarse solo porque el techo sea 1280×720, y una animación naturalmente lenta no debería forzarse a 30 fps solo porque el ejemplo use -g 150.
El comando implementa la política; no es la política en sí.
Por qué los archivos pasaron a ocupar varias veces menos
No hubo un flag milagroso.
La reducción salió de acumular varias decisiones que eliminaban desperdicios distintos: píxeles innecesarios, frames innecesarios, pensamiento de bitrate fijo, ajustes de encoder baratos, keyframes demasiado frecuentes y streams que no necesitaba.
Por eso decir “este archivo es H.264” explica sorprendentemente poco sobre su tamaño. Dos encodes H.264 de la misma fuente pueden diferir muchísimo porque el nombre del códec no describe resolución, FPS, rate control, preset, estructura GOP, perfil ni preparación de la fuente.
En mi caso, cambiar esas decisiones alrededor del códec importó más que cambiar el propio códec.
Lo que este resultado no demuestra
No aislé cada ajuste en un experimento controlado, así que no puedo atribuir honestamente un porcentaje exacto del ahorro a veryslow, CRF 28, la reducción de resolución o la reducción de FPS por separado.
Tampoco puedo afirmar que todo output CRF 28 sea perceptualmente transparente. “Sin pérdida de calidad evidente” es mi observación para este workload ilustrado a tamaños normales de visualización, no una garantía científica para cualquier vídeo.
Y no sostengo que un único archivo H.264 sea la arquitectura correcta para todos los sitios. Multiple renditions, adaptive streaming, HDR, 4K y codec negotiation cambian las compensaciones.
El resultado es más acotado y más útil: para una biblioteca de clips ilustrados y animados corta, mobile-heavy y centrada en ancho de banda, donde el tiempo de encode es barato y la reproducción predecible importa, este perfil hizo mis archivos varias veces más pequeños sin dejar de verse normales en reproducción corriente.
La regla que uso ahora
Antes pensaba en optimización de vídeo sobre todo como un problema de parámetros del encoder. Ahora lo veo como un problema de coste de vida útil.
El encoder puede ejecutarse una vez. Los bytes pueden cruzar la red miles o millones de veces.
Eso cambia lo que significa “caro”.
Estoy encantado de gastar CPU una vez. Estoy mucho menos dispuesto a enviar en cada futura petición píxeles creados por upscale, frames que no añaden movimiento útil o bitrate que el contenido no necesita.
El códec siguió siendo aburrido: H.264 en MP4. La optimización ocurrió alrededor de él.
Para mi workload, la lección es más útil que cualquier flag concreto de FFmpeg: optimiza el coste que pagas repetidamente, no el que pagas una sola vez.