FFmpeg Thực Sự Hoạt Động Như Thế Nào (Và Tại Sao Lệnh Đầu Tiên Của Bạn Thất Bại)
FFmpeg hoạt động theo một nguyên tắc đơn giản mà ai cũng sai ngay từ đầu: nó đọc các luồng từ đầu vào, xử lý chúng qua các bộ lọc và ghi chúng vào đầu ra. Sự nhầm lẫn xuất phát từ việc một tệp video đơn chứa nhiều luồng—thường là một luồng video, một hoặc nhiều luồng âm thanh, và đôi khi là luồng phụ đề hoặc luồng siêu dữ liệu. Khi bạn chạy `ffmpeg -i input.mp4 output.mp4`, FFmpeg đưa ra một loạt giả định về những gì bạn muốn. Nó chọn luồng video "tốt nhất", luồng âm thanh "tốt nhất", sao chép chúng qua các bộ mã hóa mặc định và mux chúng vào container đầu ra. Điều này hoạt động tốt cho các chuyển đổi đơn giản, nhưng nó sẽ sụp đổ ngay khi bạn cần kiểm soát. Lý do lệnh đầu tiên của tôi tạo ra một tệp 0 byte là vì tôi đã chỉ định các kết hợp codec và container không tương thích. Tôi đã cố gắng đưa một luồng video VP9 vào một container MP4, điều này không được hỗ trợ. FFmpeg bắt đầu mã hóa, nhận ra nó không thể ghi đầu ra và từ bỏ. Thông điệp lỗi bị chôn vùi trong 200 dòng đầu ra mà tôi không biết cách đọc. Dưới đây là mô hình tư duy đã thay đổi mọi thứ cho tôi: hãy nghĩ về FFmpeg như một pipeline với ba giai đoạn. Đầu tiên, demuxing—FFmpeg mở tệp đầu vào của bạn và tách nó thành các luồng riêng biệt. Thứ hai, processing—mỗi luồng đi qua một codec (mã hóa/giải mã) và các bộ lọc tùy chọn. Thứ ba, muxing—các luồng đã xử lý được gói vào một container đầu ra. Mỗi lệnh FFmpeg theo mẫu này: ``` ffmpeg [các tùy chọn toàn cục] [các tùy chọn đầu vào] -i input [các tùy chọn đầu ra] output ``` Thứ tự rất quan trọng. Các tùy chọn trước `-i` áp dụng cho đầu vào. Các tùy chọn sau `-i` áp dụng cho đầu ra. Nếu bạn để `-c:v libx264` trước `-i`, FFmpeg sẽ cố gắng giải mã đầu vào như H.264, điều này có lẽ không phải là điều bạn muốn. Để sau `-i`, và nó mã hóa đầu ra như H.264. Khái niệm quan trọng khác là các chỉ định luồng. Khi bạn viết `-c:v`, bạn đang nói "áp dụng codec này cho các luồng video." `-c:a` nhắm đến các luồng âm thanh. `-c:s` nhắm đến phụ đề. Bạn có thể cụ thể hơn với `-c:v:0` cho luồng video đầu tiên hoặc `-c:a:1` cho luồng âm thanh thứ hai. Khi tôi hiểu cấu trúc này, tôi ngừng tạo ra các tệp 0 byte. Tôi có thể đọc đầu ra của FFmpeg và hiểu nó đang làm gì ở mỗi giai đoạn. Tôi có thể gỡ lỗi các vấn đề bằng cách cô lập xem vấn đề nằm ở demuxing, processing hay muxing.Ngày Tôi Chuyển Đổi 50,000 Video (Và Những Gì Tôi Học Được)
Ba năm trước, công ty chúng tôi đã mua lại một đối thủ. Một phần của việc mua lại bao gồm toàn bộ thư viện video của họ—50,000 video ở định dạng mà chúng tôi không hỗ trợ. Họ đã sử dụng một codec độc quyền yêu cầu một trình phát cụ thể, và chúng tôi cần tất cả được chuyển đổi sang H.264 tiêu chuẩn cho nền tảng của chúng tôi. Cách tiếp cận ngây thơ sẽ là viết một vòng lặp đơn giản: cho mỗi video, chạy FFmpeg với các cài đặt cơ bản, chờ nó hoàn thành, chuyển sang video tiếp theo. Với trung bình 2 phút cho mỗi video, điều này sẽ mất 69 ngày xử lý liên tục. Chúng tôi có hai tuần. Dự án này đã dạy tôi nhiều hơn về FFmpeg so với ba năm trước cộng lại. Tôi đã học về tăng tốc phần cứng, xử lý song song và hàng chục cài đặt mã hóa thực sự quan trọng. Tôi đã học về những chỉ số chất lượng có ý nghĩa và những gì chỉ là marketing. Quan trọng nhất, tôi đã học rằng lệnh FFmpeg "tốt nhất" phụ thuộc hoàn toàn vào các ràng buộc của bạn. Cuối cùng chúng tôi xây dựng một hệ thống chuyển đổi phân tán xử lý 200 video song song trên 40 máy. Mỗi máy chạy 5 phiên bản FFmpeg, được điều chỉnh cẩn thận để tối đa hóa việc sử dụng CPU mà không bị ép. Chúng tôi đã sử dụng giải mã tăng tốc phần cứng khi có thể, nhưng mã hóa phần mềm vì sự khác biệt về chất lượng là rõ rệt cho trường hợp sử dụng của chúng tôi. Lệnh mà chúng tôi quyết định như sau: ```bash ffmpeg -hwaccel auto -i input.mov \ -c:v libx264 -preset medium -crf 23 \ -c:a aac -b:a 128k \ -movflags +faststart \ -max_muxing_queue_size 1024 \ output.mp4 ``` Để tôi phân tích lý do tại sao mỗi tùy chọn quan trọng. `-hwaccel auto` cho FFmpeg biết sử dụng giải mã phần cứng nếu có—điều này đã giảm thời gian giải mã của chúng tôi xuống 60% trên các máy có GPU tương thích. `-preset medium` cân bằng tốc độ mã hóa với hiệu quả nén. Chúng tôi đã kiểm tra tất cả các preset; `medium` là điểm ngọt mà chúng tôi đạt được 95% chất lượng của `slower` trong một nửa thời gian. Cài đặt `-crf 23` kiểm soát chất lượng bằng cách sử dụng Hệ số Tốc độ Không Đổi. Các số thấp hơn có nghĩa là chất lượng cao hơn và tệp lớn hơn. Chúng tôi đã thử nghiệm các giá trị CRF từ 18 đến 28 trên một mẫu 100 video và để đội ngũ video của chúng tôi thực hiện so sánh chất lượng mù. Không ai có thể phân biệt CRF 23 với CRF 20 một cách đáng tin cậy, nhưng kích thước tệp nhỏ hơn 30%. `-movflags +faststart` di chuyển atom moov đến đầu tệp, cho phép phát lại theo tiến trình qua HTTP. Nếu không có cờ này, các trình duyệt sẽ phải tải toàn bộ tệp trước khi có thể bắt đầu phát. Tùy chọn duy nhất này đã cải thiện các chỉ số trải nghiệm người dùng của chúng tôi lên 15%. Tùy chọn `-max_muxing_queue_size 1024` đã giải quyết một vấn đề mà đã làm tốn cho chúng tôi ba ngày gỡ lỗi. Một số video nguồn có tốc độ khung hình biến đổi làm tràn bộ đệm nội bộ của FFmpeg. Kích thước hàng đợi mặc định là 8 gói, điều này không đủ cho nội dung VFR. Tăng nó lên 1024 đã loại bỏ các lỗi "Quá nhiều gói được đệm cho luồng đầu ra" đã gây lỗi cho 5% chuyển đổi của chúng tôi. Chúng tôi đã hoàn thành dự án trong 11 ngày. Hệ thống phân tán đã xử lý 4,545 video mỗi ngày, với tỷ lệ thành công 99.2%. Những lỗi là tất cả các tệp nguồn bị hỏng hoặc sử dụng các codec mà chúng tôi không thể giải mã. Tôi vẫn sử dụng các biến thể của lệnh đó hôm nay—nó là nền tảng cho toàn bộ pipeline xử lý video của chúng tôi.Ma Trận Tương Thích Codec và Container
Một trong những khía cạnh khó chịu nhất của FFmpeg cho người mới bắt đầu là hiểu codec nào hoạt động với container nào. Bạn có thể dành một giờ để tạo ra lệnh hoàn hảo nhưng lại thất bại chỉ vì bạn đang cố đưa một codec không tương thích vào một container không hỗ trợ nó. Dưới đây là ma trận tương thích mà tôi thường tham khảo:| Container | Video Codecs | Audio Codecs | Tốt Nhất Cho |
|---|---|---|---|
| MP4 | H.264, H.265, AV1 | AAC, MP3, Opus | Phát trực tuyến web, thiết bị di động, tương thích toàn cầu |
| WebM | VP8, VP9, AV1 | Vorbis, Opus | Phát trực tuyến web, dự án mã nguồn mở, YouTube |
| MKV | Bất kỳ | Bất kỳ | Lưu trữ, nhiều âm thanh, phụ đề |
| MOV | H.264, ProRes, DNxHD | AAC, PCM | Chỉnh sửa chuyên nghiệp, hệ sinh thái Apple |
| AVI | MPEG-4, H.264 (hạn chế) | MP3, PCM | Hệ thống cũ (tránh cho các dự án mới) |
| TS | H.264, H.265, MPEG-2 | AAC, MP3, AC-3 | Phát sóng, phát trực tiếp HLS |
Các Cài Đặt Chất Lượng Mà Không Ai Giải Thích Đúng
Mỗi hướng dẫn FFmpeg đều bảo bạn sử dụng `-crf 23` cho "chất lượng tốt" hoặc `-b:v 5M` cho "5 megabit mỗi giây." Nhưng không ai giải thích những cài đặt này thực sự làm gì hoặc cách chọn các giá trị phù hợp cho nội dung của bạn. Tôi đã dành hàng trăm giờ thử nghiệm các cài đặt chất lượng trên các loại nội dung khác nhau. Đây là những gì tôi đã học được: không có cài đặt "tốt nhất" phổ quát. Các tham số chất lượng tối ưu phụ thuộc vào loại nội dung của bạn, đối tượng mục tiêu và phương pháp phân phối."Hệ số Tốc độ Không Đổi (CRF) là một chế độ mã hóa dựa trên chất lượng mà bạn chỉ định một mức chất lượng và để cho bộ mã hóa sử dụng nhiều bit như cần thiết để đạt được chất lượng đó. Các giá trị CRF thấp hơn có nghĩa là chất lượng cao hơn và tệp lớn hơn. Phạm vi là 0-51 cho H.264, trong đó 0 là không mất dữ liệu và 51 là chất lượng tồi tệ nhất có thể. Mặc định là 23, được coi là 'trong suốt về mặt thị giác' cho hầu hết nội dung—có nghĩa là hầu hết mọi người không thể phân biệt nó với nguồn."Vấn đề với CRF là nó tạo ra đầu ra bitrate biến đổi. Một cảnh hành động chuyển động cao có thể sử dụng 10 Mbps trong khi một cảnh nói đứng yên sử dụng 2 Mbps. Điều này tiết kiệm cho kích thước tệp, nhưng có thể gây ra vấn đề cho dòng phát nơi bạn cần sử dụng băng thông có thể dự đoán. Đối với streaming, bạn muốn bitrate không đổi (CBR) hoặc bitrate biến đổi với ràng buộc (VBR). Đây là lệnh mà tôi sử dụng cho streaming: ```bash ffmpeg -i input.mp4 \ -c:v libx264 -preset medium \ -b:v 5M -maxrate 5M -bufsize 10M \ -c:a aac -b:a 128k \ output.mp4 ``` Tùy chọn `-b:v 5M` đặt bitrate mục tiêu là 5 megabit mỗi giây. `-maxrate 5M` đảm bảo nó không vượt quá tỷ lệ đó. `-bufsize 10M` đặt kích thước bộ đệm giải mã gấp đôi bitrate, điều này là hướng dẫn tiêu chuẩn. Điều này tạo ra đầu ra phát trực tuyến một cách mượt mà mà không bị đệm. Nhưng đây là những gì hầu hết mọi người hiểu sai: yêu cầu về bitrate tỉ lệ với độ phân giải và chuyển động, không theo chiều tuyến tính. Một video 1080p không cần gấp đôi bitrate của một video 720p—nó cần khoảng 1.5x. Một video 4K không cần gấp bốn lần bitrate của 1080p—nó cần khoảng 2.5x.
"Hệ thống thị giác của con người là logarit, không phải tuyến tính. Gấp đôi bitrate không làm tăng gấp đôi chất lượng."