Kembali ke blog
13 Agustus 2026Sergei Solod9 mnt baca

Bagaimana Saya Membuat Video H.264 Beberapa Kali Lebih Kecil Tanpa Penurunan Kualitas yang Terlihat Jelas

Codec-nya tetap H.264. Yang saya ubah adalah seluruh kebijakan encoding: CRF 28, x264 veryslow, batas resolusi kelas 720p, dan hanya frame rate yang benar-benar berguna. File menjadi beberapa kali lebih kecil sementara playback normal tetap terlihat wajar. Pelajaran terbesarnya: biaya encoding dibayar sekali, sedangkan biaya bandwidth dibayar pada setiap view.

H.264FFmpegKompresi VideoPerforma Webx264

Hasilnya terlihat seperti peningkatan yang biasanya saya harapkan setelah pindah ke codec yang lebih baru: ukuran file video saya menjadi beberapa kali lebih kecil, sementara playback biasa tetap terlihat normal dan saya tidak melihat penurunan kualitas yang jelas pada ukuran tampilan normal.

Namun saya tidak pindah ke AV1, HEVC, atau VP9. Saya masih memakai H.264 di dalam MP4.

Yang berubah adalah semua hal di sekeliling codec. Saya membangun ulang kebijakan encoding untuk workload yang sangat spesifik: klip ilustrasi dan animasi pendek, audiens yang banyak menggunakan perangkat mobile, bandwidth sebagai biaya berulang utama, dan hampir tidak ada kekhawatiran tentang lamanya encode offline yang hanya dilakukan sekali.

Baseline yang akhirnya saya pilih sengaja konservatif untuk playback dan sengaja mahal untuk encoding: H.264 Main Profile @ Level 3.1, avc1, 8-bit yuv420p, CRF 28, x264 veryslow, batas resolusi kelas 720p, frame rate berguna yang biasanya tidak melebihi 30 fps, references dan B-frames yang dibatasi, serta faststart untuk progressive MP4 delivery.

Optimasi paling penting bukan sebuah flag FFmpeg

Perubahan terbesar adalah cara saya melihat biaya.

Encoding dilakukan sekali. Delivery terjadi setiap kali seseorang meminta file tersebut.

Untuk video real-time, menghabiskan jauh lebih banyak CPU demi sedikit penghematan bitrate bisa menjadi trade yang buruk. File saya di-encode secara offline lalu disajikan berkali-kali. Dalam model seperti itu, menghemat sepuluh menit ketika encoding bisa tidak berarti secara ekonomi jika encode yang lebih cepat membuat setiap request di masa depan lebih besar.

Itulah sebabnya -preset veryslow masuk akal bagi saya. Saya rela membayar CPU satu kali jika x264 bisa menggunakannya untuk mencari representasi yang lebih efisien. Browser tidak mengulang pencarian encoder; browser hanya men-decode bitstream yang sudah jadi.

Aturannya menjadi sederhana: habiskan komputasi pada langkah yang dilakukan sekali, dan hemat byte pada langkah yang terus berulang.

Mengapa saya tetap memakai H.264 daripada mengejar codec terbaru

Saya tidak mengklaim H.264 adalah codec dengan efisiensi kompresi terbaik. Bukan. Codec yang lebih baru bisa sangat menarik ketika sistem delivery mampu menyimpan beberapa rendition dan memilih yang terbaik untuk tiap client.

Batasan saya berbeda: satu URL, satu file, satu codec, dan sesedikit mungkin masalah playback untuk audiens yang banyak menggunakan mobile.

Untuk kebutuhan itu, H.264 di dalam MP4 masih merupakan baseline yang sangat aman. Apple saat ini menyarankan web developer menggunakan file MP4 yang di-encode dengan H.264 untuk video statis di Safari. Dokumentasi Android saat ini juga mencantumkan H.264 di dalam MP4 dan mewajibkan decoder Main Profile sejak Android 6.0; rekomendasi playback-nya juga memasukkan 1280×720 pada 30 fps sebagai konfigurasi H.264 HD. Lihat format media yang didukung Android.

Itu bukan berarti perangkat modern dibatasi pada Main Profile atau Level 3.1. Panduan HLS Apple, misalnya, umumnya lebih memilih High Profile daripada Main atau Baseline. Saya memilih Main@3.1 karena saya ingin decoder envelope yang sengaja sederhana untuk satu MP4 statis, bukan karena Apple mewajibkannya.

Saya berhenti meng-encode pixel yang tidak perlu ada

Resolusi adalah salah satu lever terbesar. Batas saya menjadi sekitar 1280×720 untuk landscape, 720×1280 untuk portrait, dan kurang lebih 960×960 untuk material square atau mixed-orientation.

Aturan yang lebih penting: jangan upscale hanya untuk mencapai batas maksimum.

Jika source berukuran 900×600, mengubahnya menjadi 1280×720 tidak mengembalikan detail. Itu hanya membuat lebih banyak sample yang perlu dijelaskan encoder. Source 1920×1080 bisa diturunkan ke kelas 720p, sedangkan source 900×600 bisa tetap sekitar 900×600. Ceiling adalah batas maksimum, bukan target.

Kedengarannya sederhana, tetapi menghapus pixel yang tidak perlu bisa lebih berpengaruh daripada banyak tweak encoder yang obscure.

Saya berhenti membayar frame yang sebenarnya tidak dimiliki source

Frame rate adalah multiplier lain. Jika sebuah animasi memiliki sekitar 16 visual state yang berguna per detik, menyimpannya sebagai 30 atau 60 fps tidak otomatis menciptakan gerakan yang lebih baik. Sering kali itu hanya menciptakan sample temporal yang berulang atau disintesis dan tetap harus direpresentasikan.

Kebijakan saya adalah mempertahankan cadence yang berguna dari source dan biasanya tetap di 30 fps atau di bawahnya. Untuk material seperti ini, 12, 15, 16, 18, 20, 24, 25, atau 30 fps semuanya bisa masuk akal jika memang menggambarkan source.

Saya juga lebih memilih CFR yang bersih pada output yang dihasilkan. VFR tidak secara inheren rusak; CFR hanya membuat timestamps, jumlah frame, pemeriksaan durasi, seeking, dan validation berikutnya lebih mudah di pipeline saya.

Prinsip umum lebih berguna daripada satu angka FPS: jangan membayar bandwidth untuk informasi temporal yang tidak ada pada source.

CRF 28 adalah pilihan workload, bukan angka ajaib

Saya tidak ingin setiap clip dipaksa menuju target bitrate yang sama. Ilustrasi yang hampir statis dan scene dengan gerakan kompleks tidak membutuhkan jumlah bit yang sama agar terlihat layak.

Karena itu saya memakai mode CRF x264 dan untuk workload ilustrasi yang bandwidth-first ini saya menetapkan sekitar -crf 28. FFmpeg mendokumentasikan CRF pada libx264 sebagai constant-quality rate control; lihat dokumentasi codec FFmpeg.

CRF 28 sengaja agresif. Saya tidak akan menyalinnya begitu saja untuk film grain, footage kamera yang noisy, teks layar sangat kecil, atau workload di mana fidelity lebih penting daripada bandwidth.

Saya juga tidak memiliki perceptual score universal yang membuktikan CRF 28 transparan. Yang bisa saya katakan dari workload saya lebih sempit: file menjadi jauh lebih kecil dan tetap terlihat normal bagi saya dalam playback biasa. Itu observasi praktis, bukan klaim bahwa CRF 28 visually lossless.

veryslow mahal untuk encoder, bukan otomatis untuk decoder

Preset saya adalah -preset veryslow. Preset yang lebih lambat memberi x264 lebih banyak kesempatan untuk mencari keputusan prediction dan coding yang efisien. Harganya adalah CPU dan waktu encoding.

Perbedaan penting: encoder effort dan decoder complexity bukan hal yang sama.

Saya bisa membiarkan x264 bekerja sangat keras sambil membatasi stream final secara terpisah. Output contract konservatif saya adalah:

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

FFmpeg mengekspos CRF, preset, tuning, profile restriction, reference frame, dan B-frame secara terpisah. Saya memikirkannya dengan cara yang sama: biarkan encoder mencari sekeras mungkin, tetapi tetap buat sisi playback biasa.

GOP dan VBV adalah guardrail, bukan kontrol kualitas utama

Untuk klip progressive pendek ini saya memakai maximum GOP sekitar lima detik: sekitar -g 150 pada 30 fps, -g 120 pada 24 fps, atau -g 80 pada 16 fps.

Ini keputusan workload, bukan aturan universal. Adaptive streaming memiliki constraint lain; panduan authoring HLS Apple, misalnya, merekomendasikan IDR setiap dua detik. Saya tidak menyalin aturan HLS itu secara buta ke file MP4 statis pendek yang progressive.

Saya juga memakai kira-kira:

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

Nilai ini merupakan ceiling untuk melindungi dari spike bitrate yang tidak biasa. Bukan berarti “encode semuanya pada 4 Mbps.” CRF tetap mengatur normal rate allocation, jadi klip yang mudah tetap bebas menjadi sangat kecil.

Saya juga membuat container MP4 sengaja membosankan

Saya menggunakan avc1 secara eksplisit. Dokumentasi HLS Apple saat ini lebih memilih sample format seperti avc1 daripada avc3. Itu bukan penyebab file saya mengecil, tetapi sejalan dengan tujuan menghasilkan H.264-in-MP4 yang konvensional.

Saya juga memakai -movflags +faststart. Dokumentasi format FFmpeg menyebut faststart memindahkan index moov MP4 ke awal file. Persyaratan HTTP streaming Android juga menyebut untuk MPEG-4, moov harus berada setelah ftyp dan sebelum mdat.

ftyp
moov
mdat

faststart tidak meningkatkan compression. Ia membuat progressive HTTP playback lebih nyaman.

Untuk output SDR biasa saya memakai 8-bit yuv420p dan memberi sinyal BT.709 dengan limited/video range. Jika clip tidak memiliki audio, saya tidak membuat audio track. Untuk illustrated content ini saya juga menggunakan -tune animation; saya menganggapnya sebagai pilihan khusus content, bukan bagian dari compatibility contract universal.

Profil FFmpeg inti

Untuk illustrated source 30 fps, bagian inti command kurang lebih seperti ini:

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

Tahap scaling dan frame-rate sengaja tidak di-hard-code di sini. Source 900×600 tidak perlu dibesarkan hanya karena ceiling 1280×720, dan animasi dengan frame rate alami yang rendah tidak perlu dipaksa menjadi 30 fps hanya karena contoh memakai -g 150.

Command adalah implementasi kebijakan, bukan kebijakan itu sendiri.

Mengapa file menjadi beberapa kali lebih kecil

Tidak ada satu magic flag.

Pengurangan berasal dari beberapa keputusan yang menyingkirkan bentuk pemborosan berbeda: pixel yang tidak perlu, frame yang tidak perlu, cara berpikir fixed-bitrate, encoder setting murah, keyframe yang terlalu sering, dan stream yang tidak saya butuhkan.

Itulah sebabnya mengatakan “file ini H.264” ternyata memberi sangat sedikit informasi tentang ukurannya. Dua encode H.264 dari source yang sama bisa berbeda drastis karena nama codec tidak menjelaskan resolution, frame rate, rate control, preset, GOP structure, profile, atau source preparation.

Dalam kasus saya, mengubah keputusan di sekitar codec lebih penting daripada mengganti codec.

Apa yang tidak dibuktikan hasil ini

Saya tidak mengisolasi setiap setting dalam controlled experiment, jadi saya tidak bisa secara jujur memberikan persentase tepat berapa banyak penghematan yang berasal dari veryslow, CRF 28, penurunan resolusi, atau penurunan frame rate secara individual.

Saya juga tidak bisa mengklaim setiap output CRF 28 perceptually transparent. “Tidak ada penurunan kualitas yang jelas” adalah observasi saya untuk workload ilustrasi ini pada ukuran tampilan normal, bukan jaminan ilmiah untuk video apa pun.

Dan saya tidak mengatakan bahwa satu file H.264 adalah arsitektur yang benar untuk setiap situs. Multiple renditions, adaptive streaming, HDR, 4K, dan codec negotiation mengubah trade-off.

Hasilnya lebih sempit dan lebih berguna: untuk library short illustrated dan animated clips yang bandwidth-first dan mobile-heavy, ketika encoding time murah dan predictable playback penting, profil ini membuat file saya beberapa kali lebih kecil dan tetap terlihat normal pada playback biasa.

Aturan yang saya gunakan sekarang

Dulu saya melihat video optimization terutama sebagai masalah encoder settings. Sekarang saya melihatnya sebagai masalah lifetime cost.

Encoder mungkin berjalan sekali. Byte dapat melewati network ribuan atau jutaan kali.

Itu mengubah arti “mahal”.

Saya senang menghabiskan CPU satu kali. Saya jauh lebih tidak rela mengirim pixel hasil upscale, frame yang tidak menambah useful motion, atau bitrate yang tidak dibutuhkan content pada setiap request di masa depan.

Codec tetap membosankan: H.264 di dalam MP4. Optimasi terjadi di sekelilingnya.

Untuk workload ini, pelajarannya lebih jelas daripada satu flag FFmpeg mana pun: optimalkan biaya yang Anda bayar berulang kali, bukan biaya yang hanya Anda bayar sekali.