Bloga dön
13 Ağustos 2026Sergei Solod8 dk okuma

H.264 Videolarımı Belirgin Bir Kalite Kaybı Olmadan Nasıl Birkaç Kat Küçülttüm

Codec değişmedi: yine H.264 kullandım. Asıl farkı CRF 28, x264 veryslow, 720p sınıfında çözünürlük tavanı ve yalnızca gerçekten gerekli kare hızını kullanan yeni encoding politikası yarattı. Dosyalar birkaç kat küçülürken normal oynatmada görüntü doğal kaldı; asıl ders ise encoding maliyetini bir kez, bandwidth maliyetini her görüntülemede ödediğimi fark etmekti.

H.264FFmpegVideo SıkıştırmaWeb Performansıx264

Ortaya çıkan sonuç, normalde daha yeni bir codec'e geçince bekleyeceğim türden bir iyileşmeydi: video dosyalarım birkaç kat küçüldü, buna rağmen normal oynatma doğal görünmeye devam etti ve sıradan görüntüleme boyutlarında belirgin bir kalite kaybı göremedim.

Fakat AV1, HEVC veya VP9'a geçmemiştim. Hâlâ MP4 içinde H.264 kullanıyordum.

Değişen şey codec'in çevresindeki her şeydi. Encoding politikasını belirli bir workload için baştan kurdum: kısa illüstrasyon ve animasyon klipleri, ağırlıklı olarak mobil kullanıcılar, tekrar tekrar ödediğim ana maliyet olarak bandwidth ve yalnızca bir kez yapılan offline encode'un ne kadar sürdüğünün neredeyse önemsiz olması.

Sonunda seçtiğim baseline, playback tarafında bilinçli olarak muhafazakâr, encoding tarafında ise bilinçli olarak pahalıydı: H.264 Main Profile @ Level 3.1, avc1, 8-bit yuv420p, CRF 28, x264 veryslow, 720p sınıfında çözünürlük tavanı, genellikle 30 fps'i aşmayan faydalı kare hızı, sınırlandırılmış reference/B-frame yapısı ve progressive MP4 delivery için faststart.

En önemli optimizasyon bir FFmpeg flag'i değildi

En büyük değişiklik maliyete nasıl baktığımdı.

Encoding bir kez yapılır. Delivery ise biri dosyayı her istediğinde tekrar gerçekleşir.

Real-time videoda biraz bitrate kazanmak için çok daha fazla CPU harcamak kötü bir takas olabilir. Benim dosyalarım offline encode ediliyor ve daha sonra tekrar tekrar sunuluyor. Bu modelde encoding sırasında on dakika kazanmanın ekonomik değeri, daha hızlı encode her gelecek isteği büyütüyorsa neredeyse sıfır olabilir.

Bu yüzden -preset veryslow benim için mantıklı. x264 daha verimli bir temsil bulabiliyorsa CPU'yu bir kez harcamaya razıyım. Browser encoder'ın arama sürecini tekrar etmez; yalnızca bitmiş bitstream'i decode eder.

Kuralım basitleşti: bir kez yapılan adımda hesaplama harca, tekrar eden adımda byte konusunda cimri ol.

Neden en yeni codec'in peşinden gitmek yerine H.264'te kaldım

H.264'ün mevcut en verimli codec olduğunu iddia etmiyorum. Değil. Delivery sistemi birden fazla rendition tutabiliyor ve her client için en uygun olanı seçebiliyorsa yeni codec'ler çok cazip olabilir.

Benim kısıtım farklıydı: tek URL, tek dosya, tek codec ve mobil ağırlıklı kullanıcı kitlesinde mümkün olduğunca az playback sorunu.

Bu iş için MP4 içindeki H.264 hâlâ çok güvenli bir baseline. Apple bugün web geliştiricilerine Safari'de static video için H.264 ile encode edilmiş MP4 dosyaları kullanmalarını söylüyor. Android'ın güncel belgeleri de MP4 içinde H.264 desteğini listeliyor ve Android 6.0'dan itibaren Main Profile decoder'ını zorunlu kılıyor; playback önerileri arasında HD H.264 örneği olarak 1280×720 @ 30 fps de bulunuyor. Ayrıntılar için Android desteklenen medya formatları sayfasına bakılabilir.

Bu, modern cihazların Main Profile veya Level 3.1 ile sınırlı olduğu anlamına gelmiyor. Örneğin Apple'ın HLS rehberi genellikle Main veya Baseline yerine High Profile'ı tercih ediyor. Main@3.1'i Apple bunu zorunlu tuttuğu için değil, tek bir static MP4 için bilinçli şekilde mütevazı bir decoder envelope istediğim için seçtim.

Var olması gerekmeyen pixel'ları encode etmeyi bıraktım

Resolution en büyük kaldıraçlardan biriydi. Tavanım landscape için yaklaşık 1280×720, portrait için 720×1280, kare veya mixed-orientation içerik için yaklaşık 960×960 oldu.

Daha önemli kural şu: sırf tavana ulaşmak için upscale etme.

Kaynak 900×600 ise onu 1280×720 yapmak yeni detay yaratmaz. Sadece encoder'ın tanımlaması gereken daha fazla sample üretir. 1920×1080 kaynak 720p sınıfına küçültülebilir; 900×600 kaynak ise yaklaşık 900×600 kalabilir. Tavan bir maksimumdur, hedef değildir.

Basit geliyor ama gereksiz pixel'ları kaldırmak, birçok gizemli encoder tweak'inden daha önemli olabilir.

Kaynağın gerçekte sahip olmadığı frame'ler için ödeme yapmayı bıraktım

Frame rate başka bir çarpan. Bir animasyonda saniyede yaklaşık 16 faydalı görsel durum varsa onu 30 veya 60 fps saklamak otomatik olarak daha iyi hareket yaratmaz. Çoğu zaman yalnızca temsil edilmesi gereken tekrarlanmış ya da sentezlenmiş temporal sample'lar üretir.

Politikam kaynağın faydalı cadence'ini korumak ve normalde 30 fps veya altında kalmak. Bu tür içerikte 12, 15, 16, 18, 20, 24, 25 veya 30 fps, gerçekten kaynağı tarif ediyorsa makul olabilir.

Üretilen çıktıda temiz CFR'ı da tercih ediyorum. VFR doğası gereği sorunlu değil; CFR benim pipeline'ımda timestamps, frame count, duration kontrolleri, seeking ve sonraki validation işlerini daha kolay hale getiriyor.

Genel ilke tek bir FPS sayısından daha yararlı: kaynakta olmayan temporal bilgi için bandwidth ödeme.

CRF 28 sihirli sayı değil, workload kararı

Her klibi aynı target bitrate'e zorlamak istemedim. Neredeyse sabit bir illüstrasyon ile karmaşık hareket içeren bir sahnenin kabul edilebilir görünmek için aynı sayıda bit'e ihtiyacı yok.

Bu nedenle x264'ün CRF modunu kullanıyorum ve bandwidth-first illüstrasyon workload'ım için yaklaşık -crf 28'de karar kıldım. FFmpeg, libx264 içindeki CRF'ı constant-quality rate control olarak belgeliyor; bkz. FFmpeg codec belgeleri.

CRF 28 bilinçli şekilde agresif. Film grain, gürültülü kamera görüntüsü, küçücük ekran yazıları veya fidelity'nin bandwidth'ten daha önemli olduğu bir workload'a bunu körü körüne kopyalamam.

Ayrıca CRF 28'in her durumda transparent olduğunu kanıtlayan evrensel bir perceptual score'um yok. Kendi workload'ım için söyleyebileceğim daha dar: dosyalar dramatik biçimde küçüldü ve normal playback sırasında bana hâlâ doğal göründü. Bu pratik bir gözlem; CRF 28'in visually lossless olduğu iddiası değil.

veryslow encoder için pahalıdır, decoder için otomatik olarak pahalı değildir

Preset'im -preset veryslow. Daha yavaş preset, x264'e daha verimli prediction ve coding kararları aramak için daha fazla fırsat verir. Bedeli encoding CPU'su ve zamanıdır.

Kritik ayrım şu: encoder effort ile decoder complexity aynı şey değildir.

x264'ün çok çalışmasına izin verirken bitmiş stream'i ayrıca kısıtlayabilirim. Muhafazakâr output contract'ım şöyle:

H.264 Main Profile
Level 3.1
8-bit yuv420p
avc1
refs = 4
B-frames = 5
B-pyramid = normal
open GOP = disabled

FFmpeg CRF, preset, tune, profile restrictions, reference frames ve B-frames ayarlarını ayrı ayrı sunuyor. Ben de bunları tam olarak böyle düşünüyorum: encoder derin arama yapsın, playback tarafı sıradan kalsın.

GOP ve VBV ana kalite kontrolü değil, korkuluklar

Bu kısa progressive kliplerde maksimum GOP'u yaklaşık beş saniye tutuyorum: 30 fps'te yaklaşık -g 150, 24 fps'te -g 120, 16 fps'te -g 80.

Bu bir workload kararı, evrensel kural değil. Adaptive streaming'in farklı gereksinimleri var; örneğin Apple HLS authoring rehberi iki saniyede bir IDR öneriyor. Ben bu HLS kuralını kısa static progressive MP4 dosyalarına körü körüne taşımıyorum.

Ayrıca yaklaşık olarak şunları kullanıyorum:

-maxrate:v 4M
-bufsize:v 8M

Bu değerler olağan dışı bitrate spike'larına karşı tavan. “Her şeyi 4 Mbps encode et” anlamına gelmiyor. Normal rate allocation'ı CRF belirlemeye devam ediyor; kolay klipler çok küçük olabiliyor.

MP4 container'ını da bilerek sıkıcı tuttum

Açıkça avc1 kullanıyorum. Apple'ın güncel HLS belgeleri avc3 yerine avc1 gibi sample format'ları öneriyor. Bu dosyalarımı küçültmedi ama sıradan H.264-in-MP4 çıktısı üretme hedefime uyuyor.

Ayrıca -movflags +faststart kullanıyorum. FFmpeg'in format belgeleri, faststart'ın MP4 içindeki moov index'ini dosyanın başına taşıdığını söylüyor. Android'ın HTTP streaming gereksinimleri de MPEG-4 için moov'un ftyp'den sonra ve mdat'dan önce gelmesini istiyor.

ftyp
moov
mdat

faststart compression'ı iyileştirmiyor. Progressive HTTP playback'i daha az sorunlu hale getiriyor.

Sıradan SDR output için 8-bit yuv420p kullanıyor ve BT.709 limited/video range sinyalliyorum. Klipte ses yoksa gereksiz audio track üretmiyorum. Bu illüstrasyon içeriği için -tune animation da kullanıyorum; bunu evrensel compatibility contract'ın değil, content class'ın seçimi olarak görüyorum.

Temel FFmpeg profili

30 fps illüstrasyon kaynağı için command'in merkez kısmı yaklaşık şöyle:

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 ve frame-rate aşamalarını özellikle hard-code etmedim. 900×600 source, tavan 1280×720 diye büyütülmemeli; doğal olarak düşük frame-rate'li animation, örnekte -g 150 var diye 30 fps'e zorlanmamalı.

Command politikanın uygulamasıdır; politikanın kendisi değildir.

Dosyalar neden birkaç kat küçüldü

Tek bir mucize flag yoktu.

Küçülme, farklı türden israfları ortadan kaldıran birden fazla kararın birleşiminden geldi: gereksiz pixel'lar, gereksiz frame'ler, fixed-bitrate düşüncesi, ucuz encoder ayarları, gerekenden sık keyframe'ler ve ihtiyaç duymadığım stream'ler.

Bu yüzden “bu dosya H.264” demek boyutu hakkında şaşırtıcı derecede az şey söyler. Aynı source'un iki H.264 encode'u dramatik biçimde farklı olabilir; çünkü codec adı resolution, frame rate, rate control, preset, GOP structure, profile veya source preparation'ı tarif etmez.

Benim durumumda codec'in çevresindeki bu kararları değiştirmek, codec'i değiştirmekten daha önemliydi.

Bu sonuç neyi kanıtlamıyor

Her ayarı kontrollü bir deneyde tek tek izole etmedim. Dolayısıyla savings'in tam yüzde kaçının veryslow, CRF 28, çözünürlük azaltma veya frame-rate azaltmadan geldiğini dürüstçe söyleyemem.

Her CRF 28 çıktısının perceptually transparent olduğunu da söyleyemem. “Belirgin kalite kaybı yoktu” ifadesi, normal görüntüleme boyutlarında bu illüstrasyon workload'ındaki gözlemimdir; herhangi bir video için bilimsel garanti değildir.

Tek bir H.264 dosyasının her site için doğru mimari olduğunu da savunmuyorum. Multiple renditions, adaptive streaming, HDR, 4K ve codec negotiation trade-off'ları değiştirir.

Sonuç daha dar ve daha kullanışlı: bandwidth-first, mobile-heavy, kısa illüstrasyon ve animasyon kliplerinden oluşan; encoding time'ın ucuz, predictable playback'in önemli olduğu kütüphanemde bu profil dosyaları birkaç kat küçülttü ve normal playback'te görüntü doğal kalmaya devam etti.

Şimdi kullandığım kural

Video optimization'ı eskiden ağırlıklı olarak encoder settings problemi gibi düşünüyordum. Şimdi lifetime cost problemi gibi düşünüyorum.

Encoder bir kez çalışabilir. Byte'lar ağdan binlerce veya milyonlarca kez geçebilir.

Bu, “pahalı” kelimesinin anlamını değiştiriyor.

CPU'yu bir kez harcamaktan memnunum. Upscale'in ürettiği pixel'ları, işe yarar hareket eklemeyen frame'leri veya content'in ihtiyaç duymadığı bitrate'i her gelecek request'te göndermeye çok daha az razıyım.

Codec sıkıcı kaldı: MP4 içinde H.264. Optimizasyon onun çevresinde gerçekleşti.

Bu workload için ders herhangi bir FFmpeg flag'inden daha net: bir kez ödediğin maliyeti değil, tekrar tekrar ödediğin maliyeti optimize et.