Bu işlem hattını kodek denemek için kurmadım. Animasyonlu görseller, pratikte kısa ve sessiz video gibi davranan içeriği sunmanın pahalı bir yolu hâline geldiği için kurdum.
İş yüküm çoğunlukla kısa animasyonlu WebP, GIF ve APNG dosyalarından oluşuyor: genellikle birkaç saniye, çoğu zaman yalnızca birkaç düzine görünür kare ve yüksek zamansal benzerlik. İzlemelerin büyük kısmı mobilden geliyor, aynı dosyalar tekrar tekrar istenebiliyor ve bir kez ödenen kodlama maliyeti, sonrasında her görüntülemede taşınan baytlardan çok daha az önemli.
Zor kısım FFmpeg’i çalıştırmak değil. Animasyonlu bir görsel, düzenli tek bir kare hızında tam boy resimlerden oluşan temiz bir dizi olmak zorunda değil. Kısmi dikdörtgenler, karıştırma ve temizleme kuralları, alfa, düzensiz gecikmeler, sıfır süreli kareler, farklı yönler ve genel amaçlı bir aracın yanıltıcı özetleyebileceği zaman bilgileri içerebilir.
Bu yüzden dönüşümü tek bir komut değil, bozulmaması gereken kurallar bütünü olarak ele alıyorum:
animasyonlu WebP / GIF / APNG
↓
tam görünür tuval durumlarını yeniden kur
↓
kaynak zamanlamasını geri kazan ve normalleştir
↓
nihai dizideki tüm kaynakları analiz et
↓
nihai MP4 için tek bir CFR seç
↓
büyütmeden en küçük ortak tuvali hesapla
↓
uyumlu H.264 segmentleri kodla
↓
akış sözleşmesini doğrula
↓
akışı kopyalayarak birleştir
↓
paket zaman çizelgesini normalleştir ve doğrula
↓
HTTP sunumunu doğrula
↓
atomik olarak yayımla
Kodek önemli, fakat animasyonun gerçekten gösterdiği şeyi korumak daha önemli.
Ölçülmüş bir üretim sonucu: 217 animasyonlu WebP tek bir 78,49 MB MP4 oldu
Girdi tek bir 1,49 GB video değildi. Toplam 10.633 görünür kare içeren 217 ayrı animasyonlu WebP dosyasıydı. Kaynak animasyonların toplam boyutu yaklaşık 1,49 GB’tı.
GİRDİ
217 animasyonlu WebP dosyası
toplam 1,49 GB
10.633 görünür kare
ÇIKTI
1 H.264 MP4
78,49 MB
0,98 Mbit/s
1264×720
30 fps CFR
Main@3.1 / CRF 28 / veryslow
Ortaya çıkan H.264 dosyası yaklaşık 0,98 Mbit/s ile 78,49 MB oldu. Kaynak animasyonların toplam boyutuna göre yaklaşık 19 kat daha küçük, yani yaklaşık %94,7 daha az veri.
Bu gerçek bir uçtan uca işlem hattı sonucu, “eski H.264’e karşı yeni H.264” şeklinde temiz bir A/B testi değil. Temsil biçimi yüzlerce animasyonlu görselden zamansal olarak sıkıştırılmış tek bir videoya değişti; bu yüzden 19 katlık farkın tamamını CRF 28’e, veryslow’a veya tek bir kodlayıcı seçeneğine bağlamıyorum.
Önce izleyicinin gerçekten gördüğü görüntüleri yeniden kuruyorum
En tehlikeli kestirme, saklanan her animasyon karesinin öncekinin tamamını değiştiren tam bir görüntü olduğunu varsaymak.
Animasyonlu bir WebP karesi, konumlandırılmış bir dikdörtgen ile karıştırma ve temizleme davranışını tanımlayabilir. APNG’de ofsetler, boyutlar, süre ve karıştırma/temizleme işlemleri vardır. GIF de önceki tuvali koruyabilir, bir alanı temizleyebilir veya daha eski bir durumu geri yükleyebilir.
Dolayısıyla saklanan kare, anlamı daha önce oluşturulmuş tuvale bağlı küçük bir yama olabilir. Bu yamaları tam görüntü gibi kodlamak doğru animasyonun küçük hâlini değil, yanlış animasyonu üretir.
Benim çıkarım sınırım tam görünür tuval durumu: doğru bir görüntüleyicinin önceki karenin temizleme kuralını ve mevcut karenin karıştırma kuralını uyguladıktan sonra göstereceği tamamen birleştirilmiş pikseller.
Bu, tüm işlem hattının ilk doğruluk garantisi. Yanlış yeniden kurulmuş bir parça H.264’e düzleştirildikten sonra hiçbir CRF, ön ayar veya kapsayıcı seçeneği onu düzeltemez.
Kare süresi kaynak verisidir; tahmin edilecek bir FPS değeri değildir
Animasyon biçimleri zamanı farklı saklar. Animasyonlu WebP her kare için 1 ms biriminde süre kullanır. GIF gecikmeyi saniyenin yüzde biri olarak saklar. APNG her kare gecikmesi için pay ve payda kullanır; payda sıfırsa PNG belirtimi bunu 100 kabul eder.
Bu gecikmeler gerçek zaman çizelgesini oluşturur. Genel amaçlı bir aracın verdiği FPS sadece özet değerdir ve yanıltıcı olabilir.
İşlem hattımdaki gerçek bir WebP 1264×720 boyutunda ve 49 görünür kareydi. Gecikmeler 62 ile 63 ms arasında değişiyor, toplam süre 3,063 saniye oluyordu. Bu pratikte 16 fps ritmidir; çünkü 16 fps’de bir kare 62,5 ms sürer.
Genel bir araç aynı kaynak için 25 fps bildirdi. Bu sayıya güvenmek kaynak zamanlamasını değiştirir veya gereksiz tekrar kareleri oluştururdu.
Geçersiz ya da belirsiz gecikmeler için de açık bir kurala ihtiyacım var. WebP sıfır ve çoğu zaman çok küçük sürelerin yorumunu uygulamaya bırakır. GIF sıfır gecikme içerebilir. APNG’de pay sıfır olabilir; bu durumda sonraki kare mümkün olduğunca hızlı gösterilmelidir, fakat görüntüleyici pratik bir alt sınır koyabilir.
Benim normalleştirmem milisaniye hassasiyetini korur, sıfır veya açıkça anlamsız derecede kısa sürelerde 10 ms’lik küçük bir alt sınır uygular ve ancak gerçekten kullanılabilir zaman bilgisi yoksa 100 ms yedek değer kullanır. Bunlar üretim kurallarıdır, evrensel standartlar değil.
Varsayılan 30 fps yerine tüm nihai MP4 için tek bir CFR seçiyorum
Görünür durumları ve sürelerini yeniden kurduktan sonra kaynak zaman çizelgesini video zaman çizelgesine taşıyorum. Her şeyi körlemesine 30 fps kodlamıyorum.
Aynı nihai MP4’e girecek tüm kaynaklar için şu küçük aday kümesini değerlendiriyorum:
10, 12, 15, 16, 18, 20, 24, 25, 30 fps
Seçici, tüm nihai diziyi yeterince iyi temsil eden en düşük CFR’yi seçiyor. Farklı nihai MP4’ler farklı hızlar kullanabilir, ancak aynı MP4 içindeki bağımsız kodlanmış tüm bölümler aynı seçilmiş CFR’yi kullanır.
3,063 saniyelik örnek kazancı net gösteriyor: 16 fps’de yaklaşık 49 çıkış karesi, 30 fps’de yaklaşık 92 gerekir. Aynı MP4 içindeki başka bir kaynak gerçekten 30 fps gerektiriyorsa tüm koleksiyon 30 kullanır. Tek nihai akış içinde farklı kare hızlarını karıştırmıyorum.
Video izi için 90.000 Hz zaman ölçeği kullanıyorum; çünkü izin verilen her CFR tam sayı kare süresi veriyor:
10 fps → 9000 tik
12 fps → 7500 tik
15 fps → 6000 tik
16 fps → 5625 tik
18 fps → 5000 tik
20 fps → 4500 tik
24 fps → 3750 tik
25 fps → 3600 tik
30 fps → 3000 tik
Bu kesin kare ızgarasını video izinin zaman ölçeği tanımlar. Tutarlılık için MP4’ün genel zaman ölçeğini de 90.000 yapıyorum, fakat bu kapsayıcıya ait ayrı bir saattir. Doğrulayıcı video paketlerini yuvarlanmış ondalık sürelere değil, tam sayı ızgarasına göre denetler.
Çözünürlük sınırları tavan değerlerdir, zorunlu tuval boyutları değil
Sunum zarfım yatayda yaklaşık 1280×720, dikeyde 720×1280 ve karma yönlerde hem genişlik hem yükseklikte en fazla 960 pikseldir.
Değişmez kural: asla büyütme. 900×600 bir kaynak 1280×720 yapılınca ayrıntı kazanmaz; yalnızca kodlayıcının tarif etmesi gereken enterpole pikseller oluşur.
İkinci kural daha az belirgin: 960×960 bir üst sınırdır, zorunlu kare tuval değildir.
Önce her kaynak için yalnızca küçültmeye izin vererek etkin boyutları hesaplıyorum. Ardından nihai dizi, bu küçültülmüş etkin dikdörtgenlerin tamamını sığdırabilen en küçük ortak çift boyutlu tuvali alıyor.
Örneğin dizi 960×540 yatay ve 500×900 dikey görüntü gerektiriyorsa ortak tuval 960×900 olabilir; 960×960 olmak zorunda değildir. Tüm bölümlerin kodlanmış boyutları yine aynıdır, dolayısıyla akış kopyasıyla birleştirme mümkün kalır ve gereksiz siyah alan kodlanmaz.
En-boy oranını koruyup boşluğu dolduruyorum; görüntüyü esnetmiyorum. Benim işlem hattımda arka plan siyah. Normal H.264/yuv420p kaynak alfa kanalını korumadığı için saydamlık bilinçli biçimde bu arka planla birleştiriliyor.
H.264 MP4 bu sunum problemine neden iyi uyuyor
GIF, APNG ve animasyonlu WebP ilkel biçimler değil. Onlar da değişmeyen alanları yeniden çizmekten kaçınabilir; dolayısıyla “video her zaman daha küçüktür” demek yanlış olur.
Ancak H.264 görüntüler arası zamansal tahmin için tasarlanmıştır. Sabit arka planlı ve küçük bölgelerin değiştiği kısa çizim döngüleri; referans görüntüler, kareler arası tahmin ve P/B kareleri için uygun bir iş yüküdür.
Apple’ın güncel Safari belgeleri statik video için H.264 kodlu MP4 öneriyor ve animasyonlu GIF’lerin modern bir video kodek’ine göre bant genişliği açısından on iki kata, enerji tüketiminde yaklaşık iki kata kadar daha pahalı olabileceğini belirtiyor. Bu 12× Apple’ın örneğidir, benim ölçümüm değildir.
Benim ölçülmüş 19× sonucum yararlı bir üretim gözlemidir; yine de zaten küçük ve iyi optimize edilmiş her animasyonlu WebP’nin MP4’e yenileceğini varsaymak yerine ölçüyorum.
Her animasyon tek bir akış sözleşmesi altında bölüm olarak kodlanıyor
Kodlayıcı çalıştığında işlem hattı görünür kareleri, ortak CFR’yi, nihai tuvali ve beklenen süreyi zaten biliyor.
Bölüm komutumun merkezi kısmı yaklaşık olarak şöyle:
ffmpeg -framerate "$COLLECTION_FPS" -i frame-%06d.png \
-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 "$GOP_FRAMES" \
-maxrate:v 4M \
-bufsize:v 8M \
-x264-params "stitchable=1:open-gop=0:b-pyramid=normal:nal-hrd=none" \
-color_range tv \
-color_primaries bt709 \
-color_trc bt709 \
-colorspace bt709 \
-fps_mode passthrough \
-map_metadata -1 \
-map_chapters -1 \
-an -sn -dn \
-video_track_timescale 90000 \
-movie_timescale 90000 \
-t "$EXPECTED_DURATION" \
segment.mp4
Azami GOP yaklaşık beş saniyedir ve seçilen CFR’den türetilir: 16 fps’de 80 kare, 24 fps’de 120, 30 fps’de 150.
-t "$EXPECTED_DURATION" koruması süs değildir. Kare listemde son görüntüyü, önceki gerçek karenin süresinin uygulanması için bir son işareti olarak tekrar ediyorum. Açık süre sınırı olmazsa bu tekrar fazladan bir bitiş karesine dönüşebilir.
Bunu 49 karelik, 3,063 saniyelik örnekte yeniden ürettim. -t olmadan 16 fps’de 50, 30 fps’de 94 kare çıktı. -t 3.063 ile beklenen 49 ve 92 kare elde edildi.
Akış kopyasıyla birleştirme ancak sıkı uyumluluk kontrolünden sonra güvenlidir
FFmpeg’in concat demultiplekseri aynı akışları, kodeği ve zaman tabanını bekler; ayrıca her dosyanın süresini bir sonrakini konumlandırmak için kullanır. Yanlış süre bilgisi zaman çizelgesi hataları oluşturabilir.
Uyumsuz dosyaları birleştirme aşamasında uyumlu hâle getirmeye çalışmıyorum. Bölüm kabul edilmeden önce şu sözleşmeyi karşılamalı:
koleksiyon CFR’si = aynı
akış zaman tabanı = aynı
MP4 video izi zaman ölçeği = aynı
tuval boyutları / SAR = aynı
profil / seviye / piksel biçimi = aynı
renk sinyali = aynı
avcC / AVC ek verisi = bayt bayt aynı
Bölümler bağımsız kodlandığı için x264’te stitchable=1 kullanıyorum; fakat bunu AVC yapılandırmasının eşit olduğuna kanıt saymıyorum. Birleştirmeden önce gerçek yapılandırma baytlarını yine karşılaştırıyorum.
Sözleşme sağlandığında nihai birleştirme ikinci bir sıkıştırma gerektirmez:
ffmpeg -f concat -safe 0 -i segments.ffconcat \
-c:v copy \
-an -sn -dn \
-movflags +faststart \
final.mp4
-c:v copy, hazır H.264 bölümlerinin yeniden çözülüp tekrar sıkıştırılmasını engeller.
Doğrulama hem MP4 dosyasını hem de sunulma biçimini kapsıyor
FFmpeg 0 koduyla çıktı diye dosyayı yayımlamıyorum.
Doğrulayıcı gerçek deterministik zamanlama hataları yakaladı: 16 fps’de sözleşmenin istediği 5625 yerine 5580 tik gördüm; daha sonra 24 fps’lik bir çıktı tam 3750 yerine 3751 içerdi. Tek tiklik farkın derin incelemesi ayrı bir konu; burada alınacak ders, aynı işlemi yeniden çalıştırmanın deterministik zaman hatasını düzeltmemesidir.
Medya nesnesinde beklenen akış sayısını, H.264 profil/seviyesini, piksel biçimini, planlanan kesin boyutları, SAR’ı, renk sinyalini, video izi için 90 kHz zaman ölçeğini, paket sürelerini, kare ve paket sayılarını, toplam süreyi, PTS/DTS ilişkilerini, bölümler arası aynı AVC yapılandırmasını, moov atomunun mdat’tan önce gelmesini ve sıkı hata davranışıyla tam çözmeyi doğruluyorum.
ffprobe -v error \
-select_streams v:0 \
-show_streams \
-show_packets \
-of json \
final.mp4
ffmpeg -v error -xerror -err_detect explode \
-i final.mp4 -f null -
Fakat yerelde doğru bir MP4 ağ üzerinden yanlış sunulabilir. Bu yüzden HTTP yolunu da kontrol ediyorum: beklenen Content-Type, doğru Content-Length, bayt aralığı desteği, geçerli 206 Partial Content yanıtı ve doğru Content-Range.
Kodlayıcı sözleşmesini değiştirdiğimde, ffprobe donanım uyumluluğunu kanıtlıyor diye varsaymak yerine gerçek cihaz ve tarayıcılarda küçük bir deneme yapıyorum. Güncel iPhone/Safari, mütevazı bir Android cihaz ve yaygın masaüstü tarayıcılarında başlatma, arama, döngü, arka plana geçip dönme ve aralık oynatmayı sınarım.
Dosya ve sunum yolu sözleşmeyi geçtikten sonra üretim varlığını atomik olarak değiştiriyorum.
Bu işlem hattının bilinçli olarak bilgi kaybettiği yerler
Bu, dağıtım için dönüşümdür; arşiv ana kopyası değildir. Alfa düzleştirilir. Düzensiz kaynak zamanlaması nihai MP4 için tek CFR’ye nicemlenir. Büyük kaynaklar küçültülebilir. Zaten kayıplı animasyonlu WebP bir kez daha kayıplı kodlamadan geçer. Ses bilinçli olarak yoktur.
Saydamlığın her arka planda korunması gerektiğinde, düzensiz kare zamanlamasının tam biçimi içerik açısından anlamlı olduğunda, arşiv kaynağı hazırlarken veya uygulamada dağıtımı başka şekilde çözen uyarlamalı çok kodek’li video altyapısı zaten varken bu hattı aynen kullanmam.
Çok küçük ve zaten iyi optimize edilmiş animasyonlu WebP dosyalarında da MP4’ün kesin daha küçük olacağını varsaymak yerine ölçüm yaparım.
Bugün kullandığım üretim sırası
- Animasyon biçimini algıla ve gerçek kare kontrol üstverisini oku.
- Biçimin karıştırma ve temizleme kurallarına göre tam görünür tuval durumlarını yeniden kur.
- Her kare süresini geri kazan ve normalleştir.
- Milisaniye hassasiyetinde yetkili kaynak zaman çizelgesini oluştur.
- Aynı nihai MP4’e girecek tüm kaynakları analiz et.
- 10/12/15/16/18/20/24/25/30 arasından tek ortak CFR seç.
- Görünür durumları bu CFR zaman çizelgesine eşle.
- Yalnızca küçülterek etkin boyutları hesapla; asla büyütme.
- Gerekli en küçük ortak çift boyutlu tuvali oluştur.
- Görüntüyü esnetmeden doldur ve alfayı bilinçli biçimde birleştir.
- Her kaynağı aynı akış sözleşmesiyle H.264 Main@3.1 / yuv420p / avc1 olarak kodla.
- Her bölümi beklenen süresiyle sınırla.
- Gerçek AVC yapılandırması veya zamanlaması sözleşmeyi bozan bölümi reddet.
- Kabul edilen bölümleri
-c:v copyile birleştir. - Nihai paket zaman çizelgesini normalleştir ve doğrula.
- Sonucu tamamen çöz.
- HTTP başlıklarını, bayt aralıklarını ve kısmi yanıtları doğrula.
- Kodlayıcı profili değiştikten sonra cihaz ve tarayıcı denemesi yap.
- Tüm kontroller başarılı olduktan sonra atomik olarak yayımla.
Gerçeğin kaynağı dosya uzantısı değil, zaman çizelgesidir
Animasyonlu WebP, GIF veya APNG belirli bir uzantıya sahip görüntü klasörü değil, zamanlanmış görünür tuval durumları dizisidir.
H.264 zamansal benzerliği çok iyi kullanabilir, fakat yanlış bileştirmeyi, uydurulmuş zamanlamayı veya uyumsuz bölüm üstverisini düzeltemez. Bu hattı güvenilir yapan mühendisliğin büyük kısmı x264’ten önce ve sonra gerçekleşir.
Bende kalan kural şudur: temsil biçimini ancak neyin kesinlikle değişmemesi gerektiğini açıkça tanımladıktan sonra değiştir.
Birincil belgeler
- Google WebP kapsayıcı belirtimi — kare dikdörtgenleri, süre, karıştırma ve temizleme.
- W3C PNG belirtimi, üçüncü baskı — APNG zamanlaması, ofsetler, karıştırma ve temizleme.
- GIF89a belirtimi — gecikmeler ve temizleme davranışı.
- FFmpeg biçim belgeleri — concat gereksinimleri ve MP4 davranışı.
- FFmpeg bit akışı filtreleri belgeleri —
setts. - ffprobe belgeleri — akış ve paket inceleme.
- Apple: Safari için video içeriği sunma — statik video için H.264 MP4 ve animasyonlu GIF yerine video kullanımı.
- Android desteklenen medya biçimleri — H.264 desteği ve HTTP akış gereksinimleri.