Con số cuối cùng khiến việc tối ưu này trở nên thật sự cụ thể rất đơn giản: một video vận hành thực tế mà tôi theo dõi đã giảm từ khoảng 280 MB xuống 50 MB sau khi áp dụng chính sách H.264 mới. Tức là nhỏ hơn khoảng 5,6 lần, tiết kiệm xấp xỉ 230 MB hay khoảng 82% dung lượng ban đầu.
Tôi không đạt kết quả đó bằng cách chuyển sang AV1, HEVC hay VP9. Đầu ra vận hành thực tế vẫn là H.264 trong MP4. Thứ thay đổi là chính sách xung quanh bộ mã: ít điểm ảnh thừa hơn, ít mẫu thời gian vô ích hơn, mục tiêu chất lượng bớt bảo thủ hơn nhiều, chấp nhận tốn CPU hơn ở bước mã hóa một lần, đồng thời giữ giới hạn giải mã trong phạm vi có chủ đích.
Đây là một trường hợp sử dụng rất cụ thể: đoạn phim minh họa và hoạt ảnh ngắn, khoảng 80% lưu lượng từ di động, băng thông là chi phí lặp lại, còn thời gian mã hóa ngoại tuyến thì rẻ so với việc liên tục phục vụ những tệp quá lớn.
Chính sách cũ và mới đại khái như sau:
cấu hình cũ
H.264 Main @ Level 4.0
CRF 19
thiết lập sẵn slow
tối đa 1920x1080 / 1080x1920
30 fps
refs = 3
khung B = 3
GOP ≈ 2 giây
VBV ≈ 10M / 20M
cấu hình mới
H.264 Main @ Level 3.1
CRF 28
thiết lập sẵn veryslow
giới hạn lớp 720p, không phóng lớn
CFR hữu ích, thường <= 30 fps
refs = 4
khung B = 5
GOP ≈ 5 giây
VBV ≈ 4M / 8M
yuv420p / avc1 / faststart
Kết quả đo được: từ khoảng 280 MB xuống khoảng 50 MB
Tôi có vài phép đo vận hành thực tế, nhưng không phải tất cả đều là cùng một thử nghiệm. Phân biệt chúng rõ ràng quan trọng hơn việc chọn con số phần trăm gây ấn tượng nhất.
| Phép đo | Trước | Sau | Mức giảm |
|---|---|---|---|
| Cùng một video cụ thể | ~280 MB | ~50 MB | nhỏ hơn ~5,6× / giảm ~82% |
| Giai đoạn trước của cùng tệp | ~350 MB | ~238 MB | nhỏ hơn ~1,47× / giảm 32% |
Hàng đầu tiên là bằng chứng sạch nhất cho tiêu đề. Đó là cùng một video cụ thể trước và sau chính sách mới: khoảng 280 MB thành khoảng 50 MB. Tôi không khôi phục được dòng tốc độ bit/thời lượng của đúng cặp đó trong nhật ký cũ, nên không bịa thêm. Chỉ riêng thay đổi dung lượng đã đủ: tệp cụ thể này nhỏ đi khoảng 5,6 lần.
Trường hợp ~350→238 MB là một giai đoạn tối ưu sớm hơn của cùng tệp. Đầu ra lúc đó khoảng 1264×720, 30 fps, dài xấp xỉ 500 giây, không âm thanh và khoảng 3,8 Mbps. Phép tính khớp với kích thước đã quan sát: 3,8 Mbps trong khoảng 500 giây tương đương gần 238 MB. Đó đã là mức tiết kiệm 32% so với ~350 MB, nhưng vẫn quá lớn cho mục tiêu băng thông của tôi.
Kiểm tra thư viện cũ cũng cho thấy H.264 quá lớn không phải chỉ là một ngoại lệ kỳ lạ. Trong một lần kiểm tra, tôi có 238 video vận hành thực tế với tổng dung lượng 6,37 GB: 117 H.264 và 121 AV1. Có 101 tệp ít nhất 20 MB và 34 tệp ít nhất 50 MB. Một số H.264 lớn trông như sau:
| Kích thước | Thời lượng | Tốc độ bit trung bình |
|---|---|---|
| 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 |
Đây là các nội dung khác nhau nên bảng chỉ cung cấp bối cảnh, không phải thử nghiệm A/B. Tuy vậy, nó cho thấy ví dụ cũ ở mức 3,8 Mbps không phải trường hợp cá biệt: nhiều tệp H.264 lớn trong thư viện cũ thực sự nằm khoảng 2,6–3,8 Mbps.
Tối ưu quan trọng nhất không phải là một tham số FFmpeg
Thay đổi lớn nhất là cách tôi nhìn vào chi phí.
Mã hóa diễn ra một lần. Việc truyền tệp diễn ra mỗi lần có người yêu cầu nó.
Với video thời gian thực, dùng nhiều CPU hơn rất nhiều chỉ để tiết kiệm một chút tốc độ bit có thể là một sự đánh đổi tệ. Tệp của tôi được mã hóa ngoại tuyến rồi phục vụ lặp đi lặp lại. Trong mô hình đó, tiết kiệm mười phút mã hóa gần như vô nghĩa về mặt kinh tế nếu cách mã hóa nhanh hơn làm mọi lần tải về sau đều lớn hơn.
Đó là lý do -preset veryslow phù hợp với tôi. Tôi sẵn sàng trả chi phí CPU một lần nếu x264 có thể dùng nó để tìm cách biểu diễn hiệu quả hơn. Trình duyệt không lặp lại quá trình tìm kiếm của bộ mã hóa; nó chỉ giải mã luồng bit đã hoàn tất.
Quy tắc trở nên đơn giản: hãy chi tài nguyên tính toán ở bước chỉ làm một lần, và tiết kiệm từng byte ở bước phải lặp lại.
Vì sao tôi vẫn dùng H.264 thay vì chạy theo bộ mã mới nhất
Tôi không nói H.264 là bộ mã có hiệu quả nén cao nhất hiện nay. Không phải. Bộ mã mới hơn có thể rất hấp dẫn khi hệ thống phân phối giữ được nhiều phiên bản và chọn phiên bản phù hợp cho từng thiết bị.
Ràng buộc của tôi khác: một URL, một tệp, một bộ mã, và càng ít rắc rối phát video càng tốt với lượng người dùng chủ yếu trên thiết bị di động.
Với bài toán đó, H.264 trong MP4 vẫn là một lựa chọn cơ sở rất an toàn. Apple hiện khuyên nhà phát triển trang mạng dùng tệp MP4 mã hóa H.264 cho video tĩnh trong Safari. Tài liệu Android hiện tại cũng liệt kê H.264 trong MP4 và yêu cầu bộ giải mã hồ sơ Main từ Android 6.0 trở lên; khuyến nghị phát H.264 còn liệt kê 1280×720 ở 30 fps cho HD, đồng thời lưu ý rằng HD không có trên mọi thiết bị. Xem các định dạng đa phương tiện Android hỗ trợ.
Điều đó không có nghĩa thiết bị hiện đại bị giới hạn ở hồ sơ Main hay mức 3.1. Ví dụ, hướng dẫn HLS của Apple nhìn chung ưu tiên hồ sơ High hơn Main hoặc hồ sơ Baseline. Tôi chọn Main@3.1 vì muốn giữ yêu cầu giải mã tương đối khiêm tốn cho một tệp MP4 tĩnh duy nhất, chứ không phải vì Apple bắt buộc.
Tôi ngừng mã hóa những điểm ảnh không cần tồn tại
Độ phân giải là một trong những đòn bẩy lớn nhất. Trần của tôi vào khoảng 1280×720 cho video ngang, 720×1280 cho video dọc và khoảng 960×960 cho nội dung vuông hoặc có hướng hỗn hợp.
Quy tắc quan trọng hơn là: đừng phóng to chỉ để chạm tới trần.
Nếu nguồn là 900×600, biến nó thành 1280×720 không khôi phục thêm chi tiết. Nó chỉ tạo thêm mẫu mà bộ mã hóa phải mô tả. Nguồn 1920×1080 có thể giảm xuống lớp 720p, còn nguồn 900×600 có thể giữ gần 900×600. Trần là giới hạn tối đa, không phải mục tiêu.
Nghe đơn giản, nhưng loại bỏ điểm ảnh thừa có thể quan trọng hơn nhiều tinh chỉnh khó hiểu của bộ mã hóa.
Giới hạn này không phải một con số tròn được chọn tùy ý. Một khung hình 1920×1080 có 2.073.600 điểm ảnh, còn 1280×720 có 921.600. Vì vậy, giảm từ 1080p xuống 720p loại bỏ khoảng 55,6% mẫu không gian trước khi bộ mã hóa bắt đầu đưa ra quyết định nén.
Tôi cũng cân nhắc dùng 540p làm lựa chọn chung. Nhưng 960×540 chỉ có 518.400 điểm ảnh, ít hơn 43,75% so với 1280×720, tức chỉ còn 56,25% số mẫu của 720p. Với nội dung minh họa, các mẫu này mô tả những đường nét mảnh, mắt, tóc, ngón tay, khuôn mặt và cạnh sắc. Nếu vẫn cần ít byte hơn, tôi sẽ thử CRF cao hơn một chút trước khi mù quáng bỏ thêm 43,75% thông tin không gian. Có thể thay đổi mức lượng tử hóa ở lần mã hóa sau; chi tiết đã mất do hạ độ phân giải thì không thể lấy lại.
Vì vậy, lớp 720p là giới hạn chung thận trọng của tôi, không phải tuyên bố rằng 540p là xấu. Với một tệp cụ thể, phiên bản 540p được đo đạc có thể tốt hơn. Tôi chỉ không biến sự cắt giảm không gian không thể đảo ngược đó thành quy tắc cho toàn thư viện khi chưa có dữ liệu.
Tôi ngừng trả băng thông cho những khung hình mà nguồn thực sự không có
Tốc độ khung hình là một hệ số nhân khác. Nếu một hoạt ảnh chỉ có khoảng 16 trạng thái hình ảnh hữu ích mỗi giây, lưu nó ở 30 hoặc 60 fps không tự động tạo ra chuyển động tốt hơn. Thường nó chỉ tạo thêm các mẫu thời gian lặp hoặc tổng hợp mà vẫn phải được mã hóa.
Chính sách của tôi là giữ nhịp hữu ích của nguồn và thường ở mức 30 fps hoặc thấp hơn. Với loại nội dung này, 12, 15, 16, 18, 20, 24, 25 hoặc 30 fps đều có thể hợp lý nếu thực sự phản ánh nguồn.
Tôi cũng thích đầu ra có tốc độ khung hình cố định, sạch sẽ. VFR không có vấn đề về bản chất; CFR chỉ giúp việc xử lý dấu thời gian, đếm khung hình, kiểm tra thời lượng, tua và xác minh sau đó dễ hơn trong quy trình xử lý của tôi.
Nguyên tắc chung hữu ích hơn bất kỳ con số FPS cụ thể nào: đừng trả băng thông cho thông tin theo thời gian mà nguồn không chứa.
CRF 28 là lựa chọn cho trường hợp của tôi, không phải con số thần kỳ
Tôi không muốn ép mọi đoạn phim về cùng một tốc độ bit mục tiêu. Một hình minh họa gần như tĩnh và một cảnh có chuyển động phức tạp không cần cùng số bit để trông chấp nhận được.
Vì vậy tôi dùng chế độ CRF của x264 và với loại nội dung minh họa ưu tiên băng thông này, tôi chốt quanh -crf 28. FFmpeg mô tả CRF trong libx264 là cơ chế điều khiển tốc độ bit theo chất lượng không đổi; xem tài liệu bộ mã của FFmpeg.
CRF 28 cố tình khá mạnh. Tôi sẽ không sao chép mù quáng giá trị này sang nội dung có hạt phim, đoạn ghi hình từ máy quay có nhiều nhiễu, chữ rất nhỏ trên màn hình hoặc trường hợp mà độ trung thực hình ảnh quan trọng hơn băng thông.
Tôi cũng không có một điểm số cảm nhận phổ quát chứng minh rằng CRF 28 là hoàn toàn trong suốt về mặt thị giác. Điều tôi có thể nói từ nội dung của mình hẹp hơn: tệp nhỏ đi rất nhiều nhưng với tôi vẫn trông bình thường khi phát thông thường. Đó là quan sát thực tế, không phải tuyên bố CRF 28 không có mất mát nhìn thấy được.
veryslow tốn kém cho bộ mã hóa, không tự động tốn kém cho bộ giải mã
Thiết lập sẵn của tôi là -preset veryslow. Thiết lập sẵn chậm hơn cho x264 nhiều cơ hội hơn để tìm các quyết định dự đoán và mã hóa hiệu quả. Cái giá là CPU và thời gian ở phía mã hóa.
Điểm phân biệt quan trọng là: công sức của bộ mã hóa và độ phức tạp của bộ giải mã không phải cùng một thứ.
Tôi có thể để x264 làm việc rất nặng trong khi vẫn giới hạn luồng bit cuối một cách riêng biệt. Ràng buộc đầu ra bảo thủ của tôi là:
H.264, hồ sơ Main
Level 3.1
yuv420p 8 bit
avc1
refs = 4
khung B = 5
B-pyramid = normal
GOP mở = tắt
FFmpeg tách riêng CRF, thiết lập sẵn, tinh chỉnh, giới hạn cấu hình, khung tham chiếu và khung B. Tôi cũng nhìn chúng theo cách đó: để bộ mã hóa tìm kiếm thật kỹ, nhưng giữ phía phát thật bình thường và dễ dự đoán.
GOP và VBV là hàng rào an toàn, không phải bộ điều khiển chất lượng chính
Với các đoạn phim phát dần ngắn này, GOP tối đa của tôi khoảng năm giây: khoảng -g 150 ở 30 fps, -g 120 ở 24 fps hoặc -g 80 ở 16 fps.
Đây là lựa chọn cho trường hợp của tôi, không phải quy tắc phổ quát. Phát trực tuyến thích ứng có ràng buộc khác; chẳng hạn hướng dẫn tạo HLS của Apple khuyến nghị một IDR mỗi hai giây. Tôi không sao chép mù quáng quy tắc HLS đó sang các tệp MP4 tĩnh, phát dần và ngắn.
Tôi cũng dùng xấp xỉ các giá trị sau:
-maxrate:v 4M
-bufsize:v 8M
Các giá trị này là trần để chặn những đỉnh tốc độ bit bất thường. Chúng không có nghĩa là “mã hóa mọi thứ ở 4 Mbps”. CRF vẫn chịu trách nhiệm phân bổ bit bình thường, nên các đoạn phim dễ nén vẫn có thể rất nhỏ.
Tôi cũng giữ vùng chứa MP4 thật đơn giản
Tôi dùng rõ ràng avc1. Tài liệu HLS hiện tại của Apple khuyến nghị các định dạng mẫu như avc1 thay cho avc3. Điều đó không làm tệp của tôi nhỏ hơn, nhưng phù hợp với mục tiêu tạo đầu ra H.264 trong MP4 thông thường.
Tôi cũng dùng -movflags +faststart. Tài liệu định dạng của FFmpeg nói faststart chuyển chỉ mục moov của MP4 lên đầu tệp. Yêu cầu phát trực tuyến qua HTTP của Android cũng nói với MPEG-4, moov phải nằm sau ftyp và trước mdat.
ftyp
moov
mdat
Faststart không cải thiện độ nén. Nó chỉ làm phát dần qua HTTP bớt rắc rối hơn.
Với đầu ra SDR thông thường tôi dùng yuv420p 8 bit và báo hiệu BT.709 với dải video giới hạn. Nếu đoạn phim không có âm thanh, tôi không tự tạo thêm rãnh âm thanh. Với loại nội dung minh họa này tôi còn dùng -tune animation; tôi xem đó là lựa chọn theo loại nội dung, không phải một phần của ràng buộc tương thích phổ quát.
Cấu hình FFmpeg cốt lõi
Với nguồn minh họa 30 fps, phần chính của lệnh gần như sau:
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
Các bước thay đổi kích thước và tốc độ khung hình cố tình không được viết cứng ở đây. Nguồn 900×600 không nên bị phóng to chỉ vì trần là 1280×720, và hoạt ảnh có tốc độ khung hình tự nhiên thấp không nên bị ép lên 30 fps chỉ vì ví dụ dùng -g 150.
Lệnh là một cách triển khai chính sách, không phải bản thân chính sách.
Vì sao tệp nhỏ đi vài lần
Không có tham số thần kỳ nào cả.
Việc giảm kích thước đến từ nhiều quyết định kết hợp với nhau, mỗi quyết định loại bỏ một kiểu lãng phí khác nhau: điểm ảnh thừa, khung hình thừa, mục tiêu chất lượng quá bảo thủ, thiết lập bộ mã hóa ưu tiên tốc độ hơn hiệu quả, khung hình chính quá thường xuyên và những luồng dữ liệu tôi không cần.
Vì vậy câu “tệp này là H.264” nói rất ít về kích thước của nó. Hai lần mã hóa H.264 từ cùng một nguồn có thể khác nhau rất nhiều vì tên bộ mã không mô tả độ phân giải, tốc độ khung hình, điều khiển tốc độ bit, thiết lập sẵn, cấu trúc GOP, hồ sơ hay cách chuẩn bị nguồn.
Trong trường hợp của tôi, thay đổi những quyết định xung quanh bộ mã quan trọng hơn việc đổi bộ mã.
Kết quả này không chứng minh điều gì
Tôi không tách riêng từng thiết lập trong một thí nghiệm có kiểm soát, nên không thể trung thực gán chính xác bao nhiêu phần trăm tiết kiệm cho veryslow, CRF 28, giảm độ phân giải hay giảm tốc độ khung hình.
Tôi cũng không thể tuyên bố mọi đầu ra CRF 28 đều trong suốt về mặt cảm nhận. “Không thấy giảm chất lượng rõ rệt” là quan sát của tôi với loại nội dung minh họa này ở kích thước xem thông thường, không phải bảo đảm khoa học cho mọi video.
Và tôi không cho rằng một tệp H.264 là kiến trúc đúng cho mọi trang mạng. Nhiều phiên bản, phát trực tuyến thích ứng, HDR, 4K và thương lượng bộ mã sẽ thay đổi các đánh đổi.
Điều tôi thực sự có thể khẳng định hẹp hơn: với một thư viện đoạn phim minh họa và hoạt hình ngắn ưu tiên băng thông, phần lớn người xem dùng thiết bị di động, thời gian mã hóa rẻ và khả năng phát ổn định quan trọng, cấu hình này làm tệp của tôi nhỏ đi vài lần mà khi phát bình thường vẫn trông tự nhiên.
Kết quả này đã thay đổi cách tôi tối ưu như thế nào
Trước đây tôi nghĩ tối ưu video chủ yếu là bài toán thiết lập bộ mã hóa. Bây giờ tôi coi nó là bài toán chi phí trong suốt vòng đời của tệp.
Bộ mã hóa có thể chỉ chạy một lần. Các byte có thể đi qua mạng hàng nghìn hoặc hàng triệu lần.
Điều đó thay đổi nghĩa của từ “đắt”.
Tôi sẵn sàng dùng thêm CPU một lần. Tôi ít sẵn sàng hơn nhiều với việc gửi các điểm ảnh được tạo ra do phóng to, các khung hình không thêm chuyển động hữu ích hoặc tốc độ bit mà nội dung không cần trong mọi yêu cầu về sau.
Bộ mã vẫn là lựa chọn rất bình thường: H.264 trong MP4. Tối ưu thực sự nằm ở những quyết định xung quanh nó.
Với trường hợp này, bài học rõ hơn bất kỳ tham số FFmpeg riêng lẻ nào: hãy tối ưu chi phí bạn phải trả lặp đi lặp lại, không phải chi phí chỉ trả một lần.