All posts
FFmpeg: Tại Sao Chỉ Đổi Định Dạng Video Đôi Khi Chỉ Mất Vài Giây?
ffmpegvideo processingmultimediaopen sourcedeveloper tools

FFmpeg: Tại Sao Chỉ Đổi Định Dạng Video Đôi Khi Chỉ Mất Vài Giây?

Tuan Phan V. Q.'s avatarTuan Phan V. Q.
Table of Contents31 sections

FFmpeg: Không Chỉ Là Một Lệnh Convert Video

Có một nghịch lý khá thú vị: rất nhiều developer dùng FFmpeg mỗi tuần, nhưng nếu hỏi FFmpeg là gì thì câu trả lời thường chỉ gói gọn trong một câu:

"Tool convert video."

Không sai. Nhưng cũng giống như gọi Git là "phần mềm để commit code".

Bạn vẫn làm được việc, nhưng đã bỏ qua gần như toàn bộ giá trị của nó.

Phần lớn bài viết về FFmpeg trên Internet đều đi theo một công thức quen thuộc: 10 lệnh hữu ích, 20 mẹo nên biết, copy rồi chạy. Những bài đó rất tốt nếu mục tiêu chỉ là xử lý xong một file video.

Vấn đề là vài tuần sau, khi gặp một bài toán hơi khác, bạn lại quay về Google tìm đúng một câu lệnh khác.

Đó là vì bạn đang nhớ cú pháp, chứ chưa hiểu mô hình.

Bài viết này đi theo hướng ngược lại.

Thay vì học thuộc lệnh, chúng ta sẽ tìm hiểu FFmpeg thực sự làm gì phía sau. Khi nắm được mô hình đó, bạn sẽ biết:

  • Vì sao có lệnh chạy trong 2 giây.

  • Vì sao có lệnh khiến CPU chạy hết công suất 40 phút.

  • Và quan trọng hơn, khi nào nên dùng cách nào.


1. FFmpeg thực sự là gì?

Nếu chỉ nhìn từ Terminal, FFmpeg có vẻ chỉ là một executable tên ffmpeg.

Thực tế, đó chỉ là lớp ngoài cùng.

FFmpeg là cả một hệ sinh thái thư viện xử lý media. Lệnh ffmpeg chỉ đóng vai trò như một CLI để gọi các thư viện đó.

Đó cũng là lý do rất nhiều ứng dụng không bao giờ gọi ffmpeg bằng command line. Họ nhúng trực tiếp các thư viện vào chương trình của mình.

Có thể hình dung như sau:

Ba executable bạn sẽ gặp

Công cụ

Mục đích

ffmpeg

Chuyển đổi, encode, filter, stream

ffprobe

Đọc thông tin file media

ffplay

Phát thử nhanh một file hoặc filter

Trong ba công cụ này, ffprobe thường bị bỏ quên nhất.

Rất nhiều script ngoài đời thực sẽ chạy ffprobe trước, rồi mới quyết định có nên gọi ffmpeg hay không.

Ví dụ, thay vì đoán một file MP4 chứa H.264 hay H.265, bạn chỉ cần:

ffprobe input.mp4

và lấy đúng codec, bitrate, độ phân giải, frame rate hay metadata cần thiết.

Giá trị thật nằm ở các thư viện

Thứ khiến FFmpeg trở thành tiêu chuẩn của ngành không phải CLI.

Đó là các thư viện như:

  • libavformat: đọc và ghi container (MP4, MKV, MOV, MPEG-TS...)

  • libavcodec: encode và decode hàng trăm codec video và audio.

  • libavfilter: crop, resize, overlay, subtitle, denoise...

  • libswscale: chuyển đổi pixel format và resize.

  • libswresample: xử lý audio.

Nhờ kiến trúc này, một media player, video editor hay media server có thể sử dụng đúng phần mình cần mà không phải gọi thêm một process bên ngoài.

Đó là khác biệt rất lớn giữa việc dùng FFmpeggọi lệnh ffmpeg.


2. Vì sao FFmpeg xuất hiện ở khắp mọi nơi?

Nếu phải viết lại FFmpeg từ đầu, bạn sẽ không chỉ phải hỗ trợ H.264 hay AAC.

Bạn còn phải xử lý vô số file lỗi ngoài đời.

Camera ghi sai timestamp.

Encoder đời cũ tạo header không đúng chuẩn.

Một container hợp lệ về mặt kỹ thuật nhưng lại khiến nhiều decoder khác bị crash.

Đó là loại kinh nghiệm không thể mua bằng tiền và cũng không thể viết xong trong vài tháng.

Hơn hai mươi năm phát triển đã biến FFmpeg thành một bộ sưu tập khổng lồ các edge case mà hầu hết dự án mới sẽ không bao giờ có đủ thời gian để tích lũy.

Đó là lý do rất nhiều phần mềm lựa chọn tích hợp FFmpeg thay vì tự xây media stack riêng.

Bạn có thể bắt gặp FFmpeg hoặc các thư viện libav* trong nhiều dự án quen thuộc như:

  • OBS Studio

  • HandBrake

  • Jellyfin

  • Plex

  • Kdenlive

  • Shotcut

  • yt-dlp

Điểm chung của chúng không phải là giao diện hay tính năng.

Điểm chung là tất cả đều cần giải quyết cùng một bài toán: đọc, ghi, chuyển đổi hoặc xử lý media một cách ổn định.

Thay vì giải lại bài toán đó từ đầu, họ tận dụng một dự án đã được kiểm chứng qua hàng triệu file video ngoài thực tế.

Đó cũng là lý do FFmpeg không chỉ là một công cụ dòng lệnh.

Nó là một trong những nền tảng quan trọng nhất của hệ sinh thái multimedia hiện đại.

3. Một file video không chỉ là "một video"

Nếu có một khái niệm đáng nhớ nhất trong cả bài, thì đó là điều này:

Một file .mp4 hay .mkv không phải là "video". Nó là một container chứa nhiều luồng dữ liệu khác nhau.

Ví dụ:

movie.mkv
├── Video (H.264)
├── Audio (AAC)
├── Audio (AC3)
├── Subtitle (SRT)
└── Metadata

Mỗi thành phần bên trong có thể được thay đổi độc lập.

Đó là lý do bạn có thể:

  • đổi phụ đề mà không đụng tới video,

  • xóa audio mà không encode lại,

  • hoặc đổi từ MKV sang MP4 chỉ trong vài giây.

Muốn hiểu FFmpeg, trước tiên phải tách bạch sáu khái niệm sau.

Khái niệm

Ý nghĩa

Container

Cái "vỏ" của file (MP4, MKV, MOV...)

Codec

Thuật toán nén (H.264, H.265, AV1...)

Bitrate

Lượng dữ liệu mỗi giây

Resolution

Kích thước khung hình

Frame Rate

Số khung hình mỗi giây

Color Space

Cách biểu diễn màu sắc

Điều thú vị là các thuộc tính này gần như độc lập với nhau.

Một video có thể là:

  • MP4 + H.264

  • MP4 + AV1

  • MKV + H.264

  • MOV + ProRes

Đuôi file chỉ nói cho bạn biết container, không nói codec nằm bên trong.

Đó cũng là lý do ffprobe luôn đáng chạy trước khi quyết định sẽ xử lý file như thế nào.

Một quy tắc rất hữu ích

Sau khi dùng FFmpeg một thời gian, mình gần như chỉ tự hỏi đúng một câu trước mỗi lệnh:

Thao tác này có làm thay đổi pixel hay không?

Nếu câu trả lời là không, khả năng rất cao bạn không cần re-encode.

Nếu câu trả lời là , gần như chắc chắn bạn sẽ phải decode rồi encode lại.

Quy tắc đơn giản này giúp tiết kiệm rất nhiều thời gian.


4. Container và Codec: Hai khái niệm thường bị nhầm nhất

Rất nhiều người nói:

"Video của mình là MP4."

Thực ra câu đó mới chỉ đúng một nửa.

MP4 không phải codec.

MP4 chỉ là container.

Có thể hình dung như sau:

MP4
└── H.264 + AAC

MKV
└── H.264 + AAC

Hai file trên có thể chứa chính xác cùng một luồng dữ liệu.

Khác biệt chỉ nằm ở cách container lưu timestamp, metadata và tổ chức các stream.

Đó là lý do lệnh sau thường chạy cực nhanh:

ffmpeg -i input.mkv -c copy output.mp4

FFmpeg không hề giải nén video.

Nó chỉ:

  1. đọc packet từ MKV,

  2. ghi lại chúng vào MP4,

  3. cập nhật timestamp và metadata cần thiết.

Toàn bộ nội dung H.264 vẫn được giữ nguyên.

Khi nào -c copy không hoạt động?

Có ba tình huống phổ biến.

1. Container không hỗ trợ codec

Ví dụ MP4 không hỗ trợ một số định dạng subtitle.

Trong trường hợp đó, chỉ cần encode lại đúng stream subtitle thay vì cả video.

2. Cần đổi định dạng bitstream

Một số codec có nhiều cách đóng gói khác nhau.

FFmpeg cung cấp bitstream filter để chuyển đổi mà không cần decode toàn bộ video.

Đây là khái niệm ít người biết nhưng cực kỳ hữu ích.

3. Bạn thay đổi nội dung hình ảnh

Resize.

Crop.

Overlay.

Watermark.

Thay đổi bitrate.

Đổi codec.

Những thao tác này đều tác động trực tiếp lên frame nên không thể dùng -c copy.

Điều nên nhớ

Container giống như chiếc hộp.

Codec là thứ nằm bên trong chiếc hộp.

Đổi hộp thường rất rẻ.

Đổi nội dung bên trong mới là phần tốn thời gian.

Đó là nền tảng để hiểu hầu hết các lệnh FFmpeg về sau.

5. Vì sao -c copy lại nhanh đến vậy?

Đây có lẽ là lệnh khiến nhiều người bất ngờ nhất khi mới làm quen với FFmpeg.

ffmpeg -i input.mkv -c copy output.mp4

Một file vài GB được xử lý chỉ trong vài giây.

Không giảm chất lượng.

Không làm CPU chạy 100%.

Vậy FFmpeg đã "tối ưu" bằng cách nào?

Câu trả lời đơn giản hơn nhiều:

Nó không hề encode.

Hai pipeline hoàn toàn khác nhau

Một lệnh transcode thông thường sẽ đi qua gần như toàn bộ pipeline xử lý video.

Input
  │
Demux
  │
Decode
  │
Raw Frames
  │
Filter / Scale
  │
Encode
  │
Mux
  │
Output

Trong toàn bộ pipeline này, bước tốn thời gian nhất luôn là encode.

Ngược lại, khi dùng -c copy, FFmpeg bỏ qua gần như toàn bộ quá trình đó.

Input
  │
Demux
  │
Packet
  │
Mux
  │
Output

Không có decode.

Không có frame raw.

Không có encode.

CPU chủ yếu chỉ đọc dữ liệu từ ổ đĩa rồi ghi trở lại vào một container mới.

Đó là lý do tốc độ thường chỉ bị giới hạn bởi SSD hoặc HDD.

Packet khác gì Frame?

Đây là khái niệm rất đáng nhớ.

Packet là dữ liệu đã được nén.

Frame là dữ liệu sau khi đã giải nén.

Có thể hình dung như sau:

Video File
    │
 Packet
    │
 Decode
    ▼
 Raw Frame

Nếu bạn chỉ đổi container, packet vẫn giữ nguyên.

Nếu bạn muốn resize, crop hoặc chèn watermark, FFmpeg buộc phải biến packet thành frame, xử lý từng pixel rồi nén lại.

Đó là lý do hai thao tác nhìn có vẻ giống nhau nhưng thời gian xử lý lại chênh lệch rất lớn.

Timestamp mới là phần "khó"

Nhiều người nghĩ -c copy chỉ giống lệnh cp.

Không hẳn.

Trong lúc remux, FFmpeg vẫn phải xử lý timestamp của từng packet để phù hợp với container mới.

Ví dụ MP4 và MKV sử dụng cách lưu timebase khác nhau.

Nếu làm sai, audio có thể lệch khỏi video hoặc việc seek sẽ hoạt động không chính xác.

Đó là lý do FFmpeg vẫn cần phân tích cấu trúc file dù không hề giải mã nội dung.

Khi nào nên ưu tiên -c copy?

Gần như mọi tình huống dưới đây:

  • đổi MKV sang MP4;

  • xóa audio;

  • tách audio;

  • đổi container;

  • cắt nhanh tại keyframe;

  • remux video để tương thích thiết bị.

Nếu mục tiêu không thay đổi nội dung hình ảnh, hãy nghĩ đến -c copy trước.

Nó thường là lựa chọn nhanh nhất và cũng an toàn nhất.


6. Vì sao encode luôn là bước đắt nhất?

Nếu decode giống như đọc hướng dẫn để lắp một mô hình LEGO, thì encode giống như phải tự tìm cách đóng gói hàng nghìn mảnh LEGO đó vào chiếc hộp nhỏ nhất có thể.

Đó là một bài toán tối ưu.

Encoder phải liên tục đưa ra quyết định:

  • Block nên chia lớn hay nhỏ?

  • Có nên tham chiếu frame trước không?

  • Dùng mode dự đoán nào?

  • Bao nhiêu bitrate là đủ?

Tất cả những lựa chọn này đều ảnh hưởng đến:

  • chất lượng,

  • kích thước file,

  • và thời gian encode.

Đó là lý do cùng một video nhưng:

-preset ultrafast

có thể hoàn thành nhanh gấp nhiều lần:

-preset slow

Điều thú vị là preset chậm không tạo ra hình đẹp hơn.

Nó chỉ dành nhiều thời gian hơn để tìm cách nén hiệu quả hơn.

Kết quả là:

  • chất lượng tương đương,

  • nhưng file nhỏ hơn.

Đây là một trong những hiểu lầm phổ biến nhất khi sử dụng x264 và x265.

CRF hay Bitrate?

Nếu chỉ nhớ một lời khuyên:

Hãy bắt đầu bằng CRF.

CRF cho phép encoder tự phân bổ bitrate theo độ phức tạp của từng cảnh.

Một cảnh đứng yên sẽ tự dùng ít dữ liệu.

Một cảnh cháy nổ hoặc chuyển động nhanh sẽ được cấp nhiều bitrate hơn.

Trong khi đó, bitrate cố định chỉ thật sự phù hợp khi bạn có giới hạn băng thông hoặc dung lượng cụ thể.

Đó cũng là lý do phần lớn workflow hiện đại đều ưu tiên CRF thay vì đặt bitrate cố định ngay từ đầu.

7. Resize video: Không chỉ là đổi độ phân giải

Nhìn bề ngoài, resize từ 4K xuống 1080p có vẻ rất đơn giản.

ffmpeg -i input.mp4 -vf scale=1920:-2 output.mp4

Nhưng phía sau lệnh này là cả một bài toán xử lý ảnh.

FFmpeg không "bỏ bớt pixel". Nó phải tính toán lại từng điểm ảnh mới từ dữ liệu cũ để giữ được càng nhiều chi tiết càng tốt.

Đó là lý do filter scale có nhiều thuật toán khác nhau.

Thuật toán

Đặc điểm

Khi nào dùng

Bilinear

Nhanh

Preview, realtime

Bicubic

Cân bằng

Mặc định cho đa số trường hợp

Lanczos

Sắc nét nhất

Downscale chất lượng cao

Area

Tốt khi giảm kích thước nhiều

Thumbnail, giảm mạnh độ phân giải

Ví dụ:

ffmpeg -i input.mp4 -vf "scale=1920:-2:flags=lanczos" output.mp4

-2 cũng là một chi tiết đáng nhớ.

Nó yêu cầu FFmpeg giữ nguyên tỉ lệ khung hình, sau đó tự làm tròn về số chẵn gần nhất. Điều này giúp tránh lỗi với các codec sử dụng chroma subsampling như H.264.

Một mẹo nhỏ nhưng gần như mình luôn áp dụng là:

  • resize video → -c:a copy

  • chỉ encode lại video, giữ nguyên audio.


8. 10 lệnh FFmpeg mình dùng nhiều nhất

Đây không phải danh sách "hay nhất", mà là những lệnh mình thực sự dùng thường xuyên.

1. Đổi container

ffmpeg -i input.mkv -c copy output.mp4

Nhanh, không mất chất lượng.


2. Cắt video không encode

ffmpeg -ss 00:01:00 -to 00:02:00 -i input.mp4 -c copy output.mp4

Phù hợp khi chỉ cần cắt theo keyframe.


3. Resize

ffmpeg -i input.mp4 -vf "scale=1920:-2:flags=lanczos" -c:v libx264 -crf 20 -c:a copy output.mp4

4. Giảm dung lượng

ffmpeg -i input.mp4 -c:v libx264 -crf 24 -preset slow output.mp4

Nếu không có yêu cầu bitrate cố định, CRF gần như luôn là lựa chọn đầu tiên.


5. Tách audio

ffmpeg -i input.mp4 -vn -c:a copy output.m4a

Không cần encode lại nếu chỉ muốn lấy audio gốc.


6. Xóa audio

ffmpeg -i input.mp4 -an -c:v copy output.mp4

7. Gắn phụ đề

Soft subtitle:

ffmpeg -i video.mp4 -i sub.srt -c copy -c:s mov_text output.mp4

Hard subtitle:

ffmpeg -i video.mp4 -vf "subtitles=sub.srt" output.mp4

Một cách giữ nguyên video, một cách thay đổi trực tiếp pixel.


8. Tạo thumbnail

ffmpeg -ss 10 -i input.mp4 -frames:v 1 thumb.jpg

Rất hữu ích khi tạo preview hoặc ảnh đại diện.


9. Tạo GIF

Thay vì convert trực tiếp, hãy tạo palette trước để GIF ít bị bệt màu hơn.

Đây là kỹ thuật đơn giản nhưng tạo khác biệt rất lớn về chất lượng.


10. Luôn dùng ffprobe

ffprobe -hide_banner input.mp4

Đây gần như là lệnh đầu tiên mình chạy khi nhận một file video lạ.

Hiểu file trước rồi mới quyết định nên -c copy hay transcode sẽ tiết kiệm rất nhiều thời gian.

Điểm chung của cả 10 lệnh trên không nằm ở cú pháp.

Điểm chung là: trước khi gõ lệnh, hãy hiểu mình đang thay đổi điều gì trong file video.

9. CPU hay GPU? Chọn đúng còn quan trọng hơn chọn nhanh

Một trong những câu hỏi phổ biến nhất khi bắt đầu encode video là:

Có nên dùng GPU không?

Câu trả lời ngắn là:

Tùy mục đích.

Nếu mục tiêu là encode nhanh để upload, stream hoặc xử lý hàng loạt, GPU gần như luôn là lựa chọn hợp lý.

Nếu mục tiêu là lưu trữ lâu dài hoặc tạo bản phân phối cuối cùng với dung lượng nhỏ nhất ở cùng chất lượng, CPU vẫn thường cho kết quả tốt hơn.

CPU Encoding

Các encoder như libx264libx265 dành rất nhiều thời gian để tìm cách biểu diễn hình ảnh hiệu quả nhất.

Đó là lý do preset slow hay veryslow có thể mất lâu hơn rất nhiều, nhưng đổi lại file nhỏ hơn ở cùng chất lượng.

Điểm mạnh:

  • Chất lượng cao hơn trên cùng bitrate.

  • Tùy chỉnh rất sâu.

  • Phù hợp để archive hoặc encode một lần dùng lâu dài.

Điểm yếu:

  • Chậm.

  • Tốn CPU.


GPU Encoding

GPU encoding thường xuất hiện dưới các tên:

  • NVIDIA NVENC

  • Intel Quick Sync

  • Apple VideoToolbox

Điểm thú vị là những encoder này không chạy trên CUDA core hay shader thông thường.

Chúng sử dụng phần cứng chuyên dụng dành riêng cho encode và decode video.

Điều đó giúp:

  • tốc độ rất cao,

  • ít ảnh hưởng đến CPU,

  • phù hợp cho livestream hoặc transcoding realtime.

Đổi lại, chất lượng ở cùng bitrate thường thấp hơn một chút so với encoder CPU.

Một quy tắc đơn giản

Nếu chỉ cần nhớ một câu:

Encode một lần để lưu lâu → CPU.

Encode liên tục hoặc realtime → GPU.

Đó cũng là cách phần lớn media server như Jellyfin hay Plex hoạt động khi có phần cứng hỗ trợ.


10. Những hiểu lầm phổ biến

Sau một thời gian dùng FFmpeg, mình thấy hầu hết lỗi đều đến từ việc hiểu sai một vài khái niệm cơ bản.

"MP4 là codec"

Không.

MP4 chỉ là container.

Codec có thể là H.264, H.265, AV1 hoặc rất nhiều định dạng khác.


"MKV nét hơn MP4"

Không.

Container không quyết định chất lượng hình ảnh.

Nếu hai file chứa cùng một stream H.264 thì chất lượng sẽ giống hệt nhau.


"ffmpeg lúc nào cũng encode"

Không.

-c copy tồn tại chính vì rất nhiều tác vụ hoàn toàn không cần encode lại.


"Bitrate càng cao càng đẹp"

Không hẳn.

Bitrate chỉ là ngân sách dữ liệu.

Một encoder tốt có thể tạo chất lượng tương đương với bitrate thấp hơn.


"Đổi sang MP3 để nhẹ hơn"

Nếu nguồn đã là AAC thì việc chuyển sang MP3 thường chỉ làm mất thêm chất lượng.

Nếu không có lý do đặc biệt, hãy giữ nguyên audio bằng -c:a copy.


Kết

Điều mình thích nhất ở FFmpeg không phải là số lượng codec mà nó hỗ trợ.

Cũng không phải vì nó có hàng trăm filter.

Điều đáng giá nhất là cách nó buộc chúng ta phải hiểu video hoạt động như thế nào.

Sau khi hiểu:

  • container khác codec,

  • packet khác frame,

  • remux khác transcode,

rất nhiều câu lệnh FFmpeg trở nên dễ đoán hơn rất nhiều.

Bạn không còn phải học thuộc từng command.

Chỉ cần tự hỏi:

Mình đang thay đổi cái vỏ của video, hay đang thay đổi chính nội dung bên trong?

Nếu chỉ thay đổi cái vỏ, rất có thể -c copy là tất cả những gì bạn cần.

Nếu thay đổi nội dung hình ảnh, hãy chuẩn bị cho quá trình decode và encode.

Hiểu được điều đó quan trọng hơn việc nhớ thêm mười câu lệnh mới.

Đó cũng là lý do sau nhiều năm, FFmpeg vẫn là một trong những dự án mã nguồn mở có ảnh hưởng nhất trong lĩnh vực multimedia.