Bu optimizasyonu benim için gerçekten somutlaştıran sayı basitti: gerçek bir üretim videosu yeni H.264 politikasından sonra yaklaşık 280 MB’den 50 MB’ye düştü. Bu yaklaşık 5,6× daha küçük demek; kabaca 230 MB veya orijinal boyutun %82’si tasarruf edildi.
Bunu AV1, HEVC ya da VP9’a geçerek yapmadım. üretim çıktı MP4 içinde H.264 olarak kaldı. Değişen şey kodeğin etrafındaki politikaydı: gereksiz pikselleri azaltmak, gereksiz zamansal örnekleri azaltmak, çok daha az muhafazakâr bir kalite hedefi belirlemek, tek seferlik kodlama aşamasında daha fazla CPU harcamak ve kod çözücü tarafını bilinçli biçimde sınırlamak.
Kullanım durumu çok belirgindi: kısa illüstrasyon ve animasyon klipleri, trafiğin yaklaşık %80’i mobil cihazlardan geliyor, bant genişliği tekrarlanan maliyet ve önceden kodlamada CPU zamanı aynı büyük dosyayı tekrar tekrar sunmaktan çok daha ucuz.
Eski ve yeni politika kabaca şöyleydi:
eski yapılandırma
H.264 Main @ Level 4.0
CRF 19
önayar slow
1920x1080 / 1080x1920'ye kadar
30 fps
refs = 3
B kareleri = 3
GOP ≈ 2 saniye
VBV ≈ 10M / 20M
yeni yapılandırma
H.264 Main @ Level 3.1
CRF 28
önayar veryslow
720p sınıfı üst sınır, büyütme yok
anlamlı CFR, genellikle <= 30 fps
refs = 4
B kareleri = 5
GOP ≈ 5 saniye
VBV ≈ 4M / 8M
yuv420p / avc1 / faststart
Ölçülen sonuç: ~280 MB'tan ~50 MB'a
Birden fazla gerçek üretim ölçümüm var, ancak bunların hepsi aynı deney değil. Bu kategorileri ayrı tutmak, en büyük görünen yüzdeyi seçmekten daha önemli.
| Ölçüm | Önce | Sonra | Azalma |
|---|---|---|---|
| Aynı somut video | ~280 MB | ~50 MB | ~5,6× daha küçük / ~%82 az |
| Aynı dosyanın önceki aşaması | ~350 MB | ~238 MB | ~1,47× daha küçük / %32 az |
İlk satır başlık için en temiz kanıt: aynı gerçek video, yeni politikadan önce yaklaşık 280 MB ve sonra yaklaşık 50 MB. Bu tam çift için geri kazanılan eski loglarda süre/bit hızı satırı korunmamış, bu yüzden sonradan bir tane uydurmuyorum. Boyut değişimi tek başına yaklaşık 5,6× farkı gösteriyor.
~350→238 MB vakası, o aşamadaki aynı örneğin daha erken optimizasyonuydu. Çıktı yaklaşık 1264×720, 30 fps, ~500 saniye, sessiz ve yaklaşık 3,8 Mbps idi. Matematik de uyuyor: 3,8 Mbps × ~500 saniye yaklaşık 238 MB. ~350 MB’ye göre zaten %32 tasarruftu, ama bant genişliği hedefim için yine de çok büyüktü.
Eski kütüphane de büyük H.264 dosyalarının tek bir garip aykırı örnek olmadığını gösteriyordu. Bir denetimde toplam 6,37 GB olan 238 üretim videosu vardı: 117 H.264 ve 121 AV1. 101 dosya en az 20 MB, 34 dosya en az 50 MB idi. Büyük H.264 örneklerinden bazıları:
| Boyut | Süre | Ortalama bit hızı |
|---|---|---|
| 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 |
Bunlar farklı içerikler olduğu için tablo bir A/B testi değil, bağlam sağlıyor. Yine de eski 3,8 Mbps örneğinin tekil bir sapma olmadığını gösteriyor: eski kitaplıktaki birkaç büyük H.264 dosyası gerçekten yaklaşık 2,6–3,8 Mbps aralığındaydı.
En önemli optimizasyon bir FFmpeg parametresi değildi
En büyük değişiklik, maliyete nasıl baktığımdı.
Kodlama bir kez yapılır. Dosyanın aktarımı, biri dosyayı her istediğinde yeniden gerçekleşir.
Gerçek zamanlı videoda biraz bit hızı kazanmak için çok daha fazla CPU harcamak kötü bir takas olabilir. Benim dosyalarım çevrimdışı kodlanıyor ve sonra tekrar tekrar sunuluyor. Bu modelde daha hızlı kodlama gelecekteki her isteği büyütüyorsa, kodlama sırasında on dakika kazanmanın ekonomik değeri neredeyse yoktur.
Bu yüzden -preset veryslow benim için mantıklı. x264 daha verimli bir temsil bulmak için ek CPU kullanabiliyorsa bu maliyeti bir kez ödemeye razıyım. Tarayıcı kodlayıcının yaptığı aramayı tekrarlamaz; yalnızca bitmiş bit akışını çözer.
Kural basitleşti: bir kez yapılan adımda hesaplamadan kaçınma, tekrar eden adımda ise bayt konusunda cimri ol.
En yeni kodeğin peşinden gitmek yerine neden H.264'te kaldım
H.264'ün mevcut en verimli sıkıştırma kodeği olduğunu iddia etmiyorum. Değil. Dağıtım sistemi birden fazla sürüm tutup her istemci için en uygun olanı seçebiliyorsa daha yeni kodekler çok cazip olabilir.
Benim kısıtım farklıydı: tek URL, tek dosya, tek kodek ve mobil ağırlıklı kullanıcı kitlesinde pratik olarak mümkün olan en az oynatma sorunu.
Bu iş için MP4 içindeki H.264 hâlâ çok güvenli bir temel. Apple bugün internet sitesi geliştiricilerine Safari'de statik video için H.264 ile kodlanmış MP4 dosyaları kullanmalarını öneriyor. Android'ın güncel belgeleri MP4 içinde H.264'ü listeliyor ve Android 6.0'dan itibaren Main profili kod çözücüsünü gerekli kılıyor; H.264 oynatma önerilerinde HD için 1280×720 @ 30 fps de yer alıyor ve HD'nin her cihazda bulunmadığı ayrıca belirtiliyor. Bkz. Android tarafından desteklenen medya biçimleri.
Bu, modern cihazların Main profili veya Seviye 3.1 ile sınırlı olduğu anlamına gelmiyor. Örneğin Apple'ın HLS kılavuzu genel olarak Main veya Baseline profili yerine High profilini tercih ediyor. Main@3.1'i, Apple zorunlu tuttuğu için değil, tek bir statik MP4 için bilinçli olarak mütevazı kod çözücü gereksinimleri istediğim için seçtim.
Gereksiz pikselleri kodlamayı bıraktım
Çözünürlük en büyük kaldıraçlardan biriydi. Tavanım yatay içerik için yaklaşık 1280×720, dikey için 720×1280, kare veya karışık yönlü içerik için yaklaşık 960×960 oldu.
Daha önemli kural şu: sırf tavana ulaşmak için asla büyütme.
Kaynak 900×600 ise onu 1280×720 yapmak kayıp ayrıntıyı geri getirmez. Yalnızca kodlayıcının tanımlaması gereken daha fazla örnek oluşturur. 1920×1080 bir kaynak 720p sınıfına küçültülebilir; 900×600 bir kaynak ise yaklaşık 900×600 kalabilir. Tavan bir üst sınırdır, hedef değildir.
Basit görünüyor ama gereksiz pikselleri kaldırmak, birçok egzotik kodlayıcı ayarından daha fazla fark yaratabilir.
Bu üst sınır sırf yuvarlak bir sayı olduğu için seçilmedi. 1920×1080 bir kare 2.073.600 piksel, 1280×720 ise 921.600 piksel içerir. Yani 1080p'den 720p'ye geçmek, kodlayıcı sıkıştırma kararlarına başlamadan önce uzamsal örneklerin yaklaşık %55,6'sını kaldırır.
540p'yi de genel varsayılan olarak düşündüm. Ancak 960×540 yalnızca 518.400 pikseldir; 1280×720'den %43,75 daha azdır ve 720p örneklerinin yalnızca %56,25'i kalır. Çizimli içerikte bu örnekler ince çizgileri, gözleri, saçları, parmakları, yüzleri ve keskin sınırları taşır. Hâlâ daha az bayt gerekiyorsa, uzamsal bilginin bir %43,75'ini daha körlemesine atmak yerine önce biraz daha yüksek CRF denerim. Nicemleme sonraki kodlamada değiştirilebilir; küçültmeyle silinen ayrıntı ise geri gelmez.
Bu yüzden 720p sınıfı benim temkinli genel üst sınırım; 540p'nin kötü olduğu iddiası değil. Belirli bir dosyada ölçülmüş 540p sürümü daha iyi olabilir. Yalnızca bu geri döndürülemez uzamsal kesintiyi veri olmadan tüm kitaplığa uygulamıyorum.
Kaynakta gerçekten olmayan kareler için bant genişliği harcamayı bıraktım
Kare hızı da bir çarpandır. Bir animasyonda saniyede yaklaşık 16 gerçekten yararlı görsel durum varsa onu 30 veya 60 fps olarak saklamak otomatik biçimde daha iyi hareket üretmez. Çoğunlukla yalnızca yine kodlanması gereken tekrarlanmış veya türetilmiş zamansal örnekler ekler.
Politikam kaynağın gerçekten yararlı ritmini korumak ve normalde 30 fps veya altında kalmak. Bu tür içerikte 12, 15, 16, 18, 20, 24, 25 veya 30 fps, kaynağı gerçekten yansıtıyorsa gayet makul olabilir.
Üretilen çıktıda temiz bir sabit kare hızını da tercih ediyorum. VFR kendi başına sorunlu değil; CFR yalnızca zaman damgalarını, kare sayılarını, süre kontrollerini, gezinmeyi ve sonraki doğrulamayı benim işlem hattımda daha kolay hâle getiriyor.
Genel ilke tek bir FPS değerinden daha yararlı: kaynakta olmayan zamansal bilgi için bant genişliği ödeme.
CRF 28 sihirli bir sayı değil, iş yüküme göre bir seçim
Her klibi aynı hedef bit hızına 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 bite ihtiyacı yok.
Bu nedenle x264'ün CRF modunu kullanıyorum ve bant genişliğini önceleyen bu illüstrasyon ağırlıklı kullanım için yaklaşık -crf 28'de karar kıldım. FFmpeg, libx264 içindeki CRF'ı sabit kaliteye yönelik hız kontrolü olarak belgeler; bkz. FFmpeg kodek belgeleri.
CRF 28 bilinçli olarak agresif. Film greni, gürültülü kamera görüntüsü, ekrandaki çok küçük yazılar veya görüntü sadakatinin bant genişliğinden daha önemli olduğu bir kullanım için bunu körü körüne kopyalamam.
Ayrıca CRF 28'in her durumda algısal olarak şeffaf olduğunu kanıtlayan evrensel bir puanım yok. Kendi içeriğim için söyleyebileceğim daha dar: dosyalar ciddi ölçüde küçüldü ve normal oynatmada bana hâlâ doğal göründü. Bu pratik bir gözlem; CRF 28'in görsel olarak kayıpsız olduğu iddiası değil.
veryslow kodlayıcı için pahalıdır; kod çözücü için otomatik olarak pahalı değildir
Ön ayarım -preset veryslow. Daha yavaş ön ayar, x264'e daha verimli tahmin ve kodlama kararları aramak için daha fazla fırsat verir. Bedeli kodlama CPU'su ve zamandır.
Kritik ayrım şu: kodlayıcının harcadığı emek ile kod çözücünün karmaşıklığı aynı şey değildir.
x264'ün çok çalışmasına izin verirken bitmiş akışı ayrıca sınırlandırabilirim. Muhafazakâr çıktı sözleşmem şöyle:
H.264, Main profili
Level 3.1
8 bit yuv420p
avc1
refs = 4
B kareleri = 5
B-pyramid = normal
açık GOP = kapalı
FFmpeg; CRF, ön ayar, ince ayar, profil kısıtları, referans kareleri ve B-karelerini ayrı ayrı ayarlamaya izin veriyor. Ben de bunları bu şekilde düşünüyorum: kodlayıcı kapsamlı arama yapsın, oynatma tarafı ise sıradan ve öngörülebilir kalsın.
GOP ve VBV ana kalite kontrolü değil, güvenlik sınırlarıdır
Bu kısa aşamalı videolarda 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, kullanımım için bir seçim; evrensel bir kural değil. Uyarlanabilir akışın farklı gereksinimleri var. Örneğin Apple'ın HLS yazım kılavuzu iki saniyede bir IDR öneriyor. Bu HLS kuralını kısa statik ve aşamalı 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ışı bit hızı sıçramalarına karşı bir tavan. “Her şeyi 4 Mbps ile kodla” anlamına gelmiyor. Normal bit dağılımını CRF belirlemeye devam ediyor; basit videolar bu yüzden çok küçük olabilir.
MP4 kapsayıcısını da olabildiğince sıradan tuttum
Açıkça avc1 kullanıyorum. Apple'ın güncel HLS belgeleri avc3 yerine avc1 gibi örnek biçimlerini öneriyor. Bu dosyalarımı küçülten şey değil ama geleneksel MP4 içinde H.264 çıktısı üretme hedefime uyuyor.
Ayrıca -movflags +faststart kullanıyorum. FFmpeg'in biçim belgeleri, faststart'ın MP4 içindeki moov dizinini dosyanın başına taşıdığını söylüyor. Android'ın HTTP üzerinden akış gereksinimleri de MPEG-4 için moov'un ftyp'den sonra ve mdat'dan önce gelmesini istiyor.
ftyp
moov
mdat
Faststart sıkıştırmayı iyileştirmez. Aşamalı HTTP oynatmayı daha sorunsuz hâle getirir.
Normal SDR çıktı için 8-bit yuv420p kullanıyor ve BT.709'u sınırlı video aralığı ile işaretliyorum. Videoda ses yoksa yapay bir ses parçası üretmiyorum. Bu illüstrasyon içeriği için -tune animation da kullanıyorum; bunu evrensel uyumluluk sözleşmesinin parçası değil, içeriğe özgü bir seçim olarak görüyorum.
Temel FFmpeg profili
30 fps illüstrasyon kaynağı için komutun 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
Ölçekleme ve kare hızı aşamalarını özellikle sabitlemedim. 900×600 kaynak, tavan 1280×720 diye büyütülmemeli; doğal olarak düşük kare hızlı bir animasyon da örnekte -g 150 var diye 30 fps'e zorlanmamalı.
Komut politikanın bir uygulamasıdır; politikanın kendisi değildir.
Dosyalar neden birkaç kat küçüldü
Tek bir mucize parametre yoktu.
Küçülme, farklı türden israfları ortadan kaldıran birkaç kararın birleşiminden geldi: gereksiz pikseller, gereksiz kareler, aşırı temkinli kalite hedefi, verimlilik yerine hızı öne çıkaran kodlayıcı ayarları, gereğinden sık anahtar kareler ve ihtiyaç duymadığım akışlar.
Bu yüzden “bu dosya H.264” demek boyutu hakkında şaşırtıcı derecede az şey söyler. Aynı kaynağın iki H.264 kodlaması çok farklı boyutlarda olabilir; çünkü kodek adı çözünürlüğü, kare hızını, hız kontrolünü, ön ayarı, GOP yapısını, profili veya kaynağın nasıl hazırlandığını anlatmaz.
Benim durumumda kodeğin çevresindeki bu kararları değiştirmek, kodeği değiştirmekten daha önemliydi.
Bu sonuç neyi kanıtlamıyor
Her ayarı kontrollü bir deneyde tek tek izole etmedim. Bu yüzden tasarrufun tam yüzde kaçının veryslow, CRF 28, çözünürlük azaltma veya kare hızı azaltmadan ayrı ayrı geldiğini dürüstçe söyleyemem.
Her CRF 28 çıktısının algısal olarak şeffaf olduğunu da iddia edemem. “Belirgin kalite kaybı yoktu” ifadesi, normal görüntüleme boyutlarında bu illüstrasyon ağırlıklı içerikteki gözlemimdir; herhangi bir video için bilimsel garanti değildir.
Tek bir H.264 dosyasının her internet sitesi için doğru mimari olduğunu da savunmuyorum. Birden fazla sürüm, uyarlanabilir akış, HDR, 4K ve kodek seçimi dengeleri değiştirir.
Gerçekte söyleyebileceğim daha dar: bant genişliğini önceleyen, mobil ağırlıklı kısa illüstrasyon ve animasyon videolarından oluşan; kodlama süresinin ucuz, öngörülebilir oynatmanın önemli olduğu kütüphanemde bu profil dosyaları birkaç kat küçülttü ve normal oynatmada görüntü doğal kalmaya devam etti.
Bu sonuç optimizasyon yaklaşımımı nasıl değiştirdi
Video optimizasyonunu eskiden ağırlıklı olarak kodlayıcı ayarları problemi gibi düşünüyordum. Şimdi onu dosyanın tüm ömrü boyunca oluşan maliyet problemi gibi görüyorum.
Kodlayıcı bir kez çalışabilir. Baytlar ağdan binlerce veya milyonlarca kez geçebilir.
Bu, “pahalı” kelimesinin anlamını değiştiriyor.
CPU'yu bir kez harcamaktan memnunum. Büyütmeyle oluşturulan pikselleri, işe yarar hareket eklemeyen kareleri veya içeriğin ihtiyaç duymadığı bit hızını her gelecek istekte göndermeye çok daha az razıyım.
Kodek sıradan kaldı: MP4 içinde H.264. Optimizasyon onun çevresinde gerçekleşti.
Bu kullanım için ders herhangi bir FFmpeg parametresinden daha net: bir kez ödediğin maliyeti değil, tekrar tekrar ödediğin maliyeti optimize et.