FFmpeg 101 (2024)
FFmpeg 101 (2024)
Bài viết này phác thảo kiến trúc cấp cao của FFmpeg, giới thiệu bộ công cụ và thư viện mạnh mẽ cho việc xử lý media. Đối với các developer Việt Nam, điều này có nghĩa là FFmpeg cung cấp các API mạnh mẽ (như `libavformat` cho I/O và `libavcodec` cho encoding/decoding) để tích hợp các tính năng multimedia tiên tiến vào ứng dụng của mình, chứ không chỉ đơn thuần là các tiện ích dòng lệnh. Các bạn có thể xem FFmpeg như một bộ "building blocks" để xây dựng các custom media players, bộ chuyển đổi định dạng, hay các giải pháp streaming nhờ vào thiết kế thư viện theo dạng module của nó.
Tổng quan về kiến trúc cấp cao để bắt đầu với FFmpeg. Kho lưu trữ mã: ffmpeg-101 Nội dung gói FFmpeg # FFmpeg bao gồm một bộ công cụ và thư viện. Công cụ FFmpeg # Các công cụ này có thể được sử dụng để...
Tổng quan về kiến trúc cấp cao để bắt đầu với FFmpeg.
Kho lưu trữ mã: ffmpeg-101
Nội dung gói FFmpeg #
FFmpeg bao gồm một bộ công cụ và thư viện.
Công cụ FFmpeg #
Các công cụ này có thể được sử dụng để mã hóa/giải mã/chuyển mã vô số định dạng âm thanh và video khác nhau cũng như để phát trực tuyến phương tiện được mã hóa qua mạng.
- ffmpeg: công cụ dòng lệnh để chuyển đổi các tệp đa phương tiện giữa các định dạng
- ffplay: một trình phát đa phương tiện đơn giản dựa trên SDL và thư viện FFmpeg
- ffprobe: một công cụ phân tích luồng đa phương tiện đơn giản
Thư viện FFmpeg #
Bạn có thể sử dụng thư viện để tích hợp những tính năng tương tự đó vào sản phẩm của riêng mình.
- libavformat : I/O và trộn/giải mã
- libavcodec: mã hóa/giải mã
- libavfilter: bộ lọc dựa trên biểu đồ cho phương tiện thô
- libavdevice: thiết bị đầu vào/đầu ra
- libavutil: các tiện ích đa phương tiện phổ biến
- libswresample: lấy mẫu lại âm thanh, chuyển đổi định dạng mẫu và trộn âm thanh
- libswscale : chuyển đổi màu sắc và chia tỷ lệ hình ảnh
- libpostproc: xử lý hậu kỳ video (khử chặn/bộ lọc nhiễu)
Trình phát đơn giản FFmpeg #
Cách sử dụng cơ bản của FFmpeg là giải mã luồng đa phương tiện (thu được từ một tệp hoặc từ mạng) vào luồng đó. luồng âm thanh và video, sau đó giải mã các luồng đó thành dữ liệu âm thanh và video thô.
Để quản lý luồng phương tiện, FFmpeg sử dụng các cấu trúc sau:
- AVFormatContext: cấu trúc cấp cao cung cấp tính năng đồng bộ hóa, siêu dữ liệu và kết hợp cho các luồng
- AVStream : luồng liên tục (âm thanh hoặc video)
- AVCodec: xác định cách mã hóa và giải mã dữ liệu
- AVPacket: dữ liệu được mã hóa trong luồng
- AVFrame: dữ liệu được giải mã (khung hình video thô hoặc mẫu âm thanh thô)
Quy trình được sử dụng để giải mã và giải mã tuân theo logic sau:
Đây là mã cơ bản cần thiết để đọc luồng đa phương tiện được mã hóa từ một tệp, phân tích nội dung của nó và giải mã âm thanh
và các luồng video. Những tính năng đó được cung cấp bởi thư viện libavformat và nó sử dụng AVFormatContext và
Cấu trúc AVStream để lưu trữ thông tin.
// Cấp phát bộ nhớ cho cấu trúc ngữ cảnh
AVFormatContext* format_context = avformat_alloc_context();
// Mở tệp đa phương tiện (như tệp mp4 hoặc bất kỳ định dạng nào được FFmpeg nhận dạng)
avformat_open_input(&format_context, tên tệp, NULL, NULL);
printf("Tệp: %s, định dạng: %s\n", tên tệp, format_context->iformat-> tên);
// Phân tích nội dung tệp và xác định các luồng bên trong
avformat_find_stream_info(format_context, NULL);
// Liệt kê các luồng
for (unsigned int i = 0; i < format_context->nb_streams ; ++i)
{
AVStream* luồng = format_context->luồng[i];
printf("---- Phát trực tiếp %02d\n", i);
printf(" Cơ sở thời gian: %d/%d\n", luồng->time_base.num , luồng->time_base.den);
printf(" Tốc độ khung hình: %d/%d\n", luồng->r_frame_rate.num, luồng->r_frame_rate.den);
printf(" Thời gian bắt đầu: %" PRId64 "\n", luồng ->thời gian bắt đầu);
printf(" Thời lượng: %" PRId64 "\n", luồng->thời lượng);
printf(" Loại: %s\n", av_get_media_type_string(stream->codecpar->codec_type)); uint32_t Fourcc = luồng->codecpar->codec_tag;
printf(" FourCC: %c%c%c%c\n", Fourcc & 0xff, (fourcc >> 8) & 0xff, (fourcc >> 16 ) & 0xff, (fourcc >> 24) & 0xff);
// Đóng tệp đa phương tiện và giải phóng cấu trúc ngữ cảnh
avformat_close_input(&format_context);
Khi chúng tôi đã có các luồng khác nhau từ bên trong tệp đa phương tiện, chúng tôi cần tìm các codec cụ thể để giải mã
truyền tới dữ liệu âm thanh thô và video thô. Tất cả các codec đều được đưa tĩnh vào libavcodec. Bạn có thể dễ dàng tạo
codec của riêng bạn bằng cách tạo một phiên bản của cấu trúc FFCodec và đăng ký nó làm
extern const FFCodec trong libavcodec/allcodecs.c, nhưng đây sẽ là một chủ đề khác cho một bài đăng khác.
Để tìm codec tương ứng với nội dung của AVStream, chúng ta có thể sử dụng mã sau:
// Luồng thu được từ cấu trúc AVFormatContext trong vòng lặp liệt kê luồng trước đây
AVStream* luồng = format_context->luồng[i];
// Tìm kiếm codec tương thích
const AVCodec* codec = avcodec_find_decoding(stream->codecpar->codec_id) ;
if (!codec)
{
fprintf(stderr, "Bộ giải mã không được hỗ trợ\n");
tiếp tục;
printf(" Codec: %s, bitrate: %" PRId64 "\n", codec->tên, luồng ->codecpar->bit_rate);
if (codec->loại == AVMEDIA_TYPE_VIDEO)
{
printf(" Độ phân giải video: %dx%d\n", luồng->codecpar->width, luồng->codecpar->height);
else if (codec->loại == AVMEDIA_TYPE_AUDIO)
{
printf(" Âm thanh: %d kênh, tốc độ mẫu: %d Hz\n",
phát trực tuyến->codecpar->ch_layout.nb_channels,
luồng->codecpar->sample_rate );
>
Với các tham số codec và codec phù hợp được trích xuất từ thông tin AVStream, giờ đây chúng tôi có thể phân bổ
Cấu trúc AVCodecContext sẽ được sử dụng để giải mã luồng tương ứng. Điều quan trọng cần nhớ là chỉ số
của luồng mà chúng tôi muốn giải mã từ danh sách luồng cũ (format_context->streams) vì chỉ mục này sẽ
được sử dụng sau này để xác định các gói được giải mã được trích xuất bởi AVFormatContext.
Trong đoạn mã sau, chúng ta sẽ chọn luồng video đầu tiên có trong tệp đa phương tiện.
// first_video_stream_index được xác định trong quá trình liệt kê các luồng ở vòng lặp cũ
int first_video_stream_index = ...;
AVStream* first_video_stream = format_context->luồng[first_video_stream_index];
AVCodecParameter* first_video_stream_codec_params = first_video_stream->codecpar;
const AVCodec* first_video_stream_codec = avcodec_find_decoding(first_video_stream_codec_params->codec_id);
// Cấp phát bộ nhớ cho cấu trúc ngữ cảnh giải mã
AVCodecContext* codec_context = avcodec_alloc_context3( first_video_stream_codec);
// Định cấu hình bộ giải mã với các tham số codec
avcodec_parameters_to_context(codec_context, first_video_stream_codec_params);
// Mở bộ giải mã
avcodec_open2(codec_context, first_video_stream_codec, NULL);
Bây giờ chúng ta có bộ giải mã đang chạy, chúng ta có thể trích xuất các gói được giải mã bằng cách sử dụng cấu trúc AVFormatContext và giải mã
chúng thành các khung hình video thô. Để làm được điều đó, chúng ta cần 2 cấu trúc khác nhau:
AVPacketchứa các gói được mã hóa được trích xuất từ tệp đa phương tiện đầu vào,AVFramesẽ chứa khung hình video thô sau khiAVCodecContextgiải mã các gói cũ.
// Cấp phát bộ nhớ cho cấu trúc gói được mã hóa
AVPacket* gói = av_packet_alloc();
// Cấp phát bộ nhớ cho cấu trúc khung được giải mã
AVFrame* khung = av_frame_alloc();
// Giải mã gói tiếp theo khỏi tệp đa phương tiện đầu vào
while (av_read_frame(format_context, gói) >= 0)
{
// Gói được giải mã sử dụng chỉ mục luồng để xác định AVStream mà nó đến từ đó
printf("Đã nhận được gói cho luồng %02d, pts: %" PRId64 "\n", gói->stream_index, gói->pts);
// Trong ví dụ của chúng tôi, chúng tôi chỉ giải mã luồng video đầu tiên được xác định trước đó bởi first_video_stream_index
if (gói->stream_index == first_video_stream_index)
{
// Gửi gói đến bộ giải mã được khởi tạo trước
int độ phân giải = avcodec_send_packet(codec_context , gói);
if (res < 0)
{
fprintf(stderr, "Không thể gửi gói đến bộ giải mã: %s\n", av_err2str(res));
ngắt;
// Bộ giải mã (AVCodecContext) hoạt động giống như một hàng đợi FIFO, chúng ta đẩy các gói được mã hóa ở một đầu và chúng ta cần
// thăm dò đầu kia để lấy các khung được giải mã. Việc triển khai codec có thể (hoặc có thể không) sử dụng khác nhau
// chủ đề để thực hiện việc giải mã thực tế.
// Thăm dò bộ giải mã đang chạy để tìm nạp tất cả các khung được giải mã có sẵn cho đến bây giờ
while (res >= 0)
{
// Lấy khung được giải mã có sẵn tiếp theo
độ phân giải = avcodec_receive_frame(codec_context, frame);
if (res == AVERROR(EAGAIN) || res == AVERROR_EOF)
{
// Không còn khung được giải mã nào trong hàng đợi đầu ra của bộ giải mã, hãy chuyển sang gói được mã hóa tiếp theo ngắt;
else if (res < 0)
{
fprintf(stderr, "Lỗi khi nhận khung từ bộ giải mã: %s\n", av_err2str(res)); goto kết thúc;
// Bây giờ cấu trúc AVFrame chứa khung video thô được giải mã, chúng ta có thể xử lý thêm...
printf("Frame %02" PRId64 ", loại: %c, định dạng: %d, điểm: %03" PRId64 ", khung hình chính: %s\n",
codec_context->frame_num, av_get_picture_type_char(frame->pict_type), frame->format, frame->điểm,
(frame->cờ & AV_FRAME_FLAG_KEY) ? "true" : "false");
// Nội dung bên trong AVFrame tự động được hủy bỏ và tái sử dụng trong lệnh gọi tiếp theo tới
// avcodec_receive_frame(codec_context, frame)
// Hủy giới thiệu nội dung bên trong gói để tái chế nó cho gói được giải mã tiếp theo
av_packet_unref(gói);
// Giải phóng bộ nhớ được phân bổ trước đó cho các cấu trúc FFmpeg khác nhau
kết thúc:
av_packet_free(&gói);
av_frame_free(&frame);
avcodec_free_context(&codec_context);
avformat_close_input (&format_context);
Cách hoạt động của mã cũ được tiếp tục trong sơ đồ tiếp theo:
Bạn có thể tải xuống mã đầy đủ tại đây hoặc truy cập trực tiếp vào kho lưu trữ mã.
Để xây dựng ví dụ, bạn sẽ cần meson và ninja. Nếu bạn có
đã cài đặt python và pip, bạn có thể cài đặt chúng rất dễ dàng bằng cách gọi pip3 install meson ninja. Sau đó, một khi
kho lưu trữ ví dụ được trích xuất vào thư mục ffmpeg-101, hãy chuyển đến thư mục này và gọi: meson setup build. Nó sẽ
tự động tải xuống phiên bản FFmpeg phù hợp nếu bạn chưa cài đặt nó trên hệ thống của mình. Sau đó gọi:
ninja -C build để xây dựng mã và ./build/ffmpeg-101 sample.mp4 để chạy mã.
Bạn sẽ nhận được kết quả sau:
Tệp: mẫu.mp4, định dạng: mov,mp4,m4a,3gp,3g2 ,mj2
---- Luồng 00
Cơ sở thời gian: 1/3000
Tốc độ khung hình: 30/1
Thời gian bắt đầu: 0
Thời lượng: 30000
Loại: video
FourCC: avc1
Codec: h264, tốc độ bit: 47094 Độ phân giải video: 206x80
---- Luồng 01
Cơ sở thời gian: 1/44100
Tốc độ khung hình: 0/0
Thời gian bắt đầu: 0
Thời lượng: 440320
Loại: âm thanh
FourCC: mp4a
Codec: aac, tốc độ bit: 112000
Âm thanh: 2 kênh, tốc độ mẫu: 44100 Hz
Gói đã nhận được cho luồng 00, điểm: 0
Gửi gói video tới bộ giải mã...
Khung 01, loại: Tôi, định dạng: 0, điểm: 000 , khung hình chính: true
Gói đã nhận được cho luồng 00, điểm: 100
Gửi gói video tới bộ giải mã...
Khung 02, loại: Định dạng P,: 0, điểm: 100, khung hình chính: false
Đã nhận được gói cho luồng 00, điểm: 200
Gửi gói video tới bộ giải mã...
Khung 03, loại: Định dạng P,: 0, điểm: 200, khung hình chính: false
Gói đã nhận được cho luồng 00, điểm: 300
Gửi gói video tới bộ giải mã...
Khung 04, loại: Định dạng P,: 0, điểm: 300, khung hình chính: false
Gói đã nhận được cho luồng 00, điểm: 400
Gửi gói video tới bộ giải mã...
Khung 05, loại: P, định dạng: 0, điểm: 400, khung hình chính: false
Gói đã nhận được cho luồng 00, điểm: 500
Gửi gói video tới bộ giải mã...
Khung 06, loại: P, định dạng: 0, điểm: 500, khung hình chính: false
Gói đã nhận được cho luồng 00, điểm: 600
Gửi gói video tới bộ giải mã...
Khung 07, loại: P, định dạng: 0, điểm: 600, khung hình chính: false
Gói đã nhận được cho luồng 00, điểm: 700
Gửi gói video tới bộ giải mã...
Khung 08, loại: Định dạng P,: 0, điểm: 700, khung hình chính: false Gói đã nhận được cho luồng 01, điểm: 0
Gói đã nhận được cho luồng 01, điểm: 1024
Gói đã nhận được cho luồng 01, điểm: 2048
Gói đã nhận được cho luồng 01, điểm: 3072
Đã nhận được gói cho luồng 01, điểm: 4096
Gói đã nhận được cho luồng 01, điểm: 5120
Gói đã nhận được cho luồng 01, điểm: 6144
Gói đã nhận được cho luồng 01, điểm: 7168
Đã nhận được gói cho luồng 01, điểm: 8192
Gói đã nhận được cho luồng 01, điểm: 9216
Gói đã nhận được cho luồng 01, điểm: 10240
Gói đã nhận được cho luồng 01, điểm: 11264
Đã nhận được gói cho luồng 01, điểm: 12288
Gói đã nhận được cho luồng 01, điểm: 13312
Gói đã nhận được cho luồng 01, điểm: 14336
Gói đã nhận được cho luồng 01, điểm: 15360
Đã nhận được gói cho luồng 01, điểm: 16384
Gói đã nhận được cho luồng 01, điểm: 17408
Gói đã nhận được cho luồng 01, điểm: 18432
Gói đã nhận được cho luồng 01, điểm: 19456
Gói đã nhận được cho luồng 01, điểm: 20480 Gói đã nhận được cho luồng 01, điểm: 21504
Gói đã nhận được cho luồng 00, điểm: 800
Gửi gói video tới bộ giải mã...
Khung 09, loại: P, định dạng: 0, điểm: 800, khung hình chính: false
Gói đã nhận được cho luồng 00, điểm: 900
Gửi gói video tới bộ giải mã...
Khung 10, loại: Định dạng P,: 0, điểm: 900, keyframe: false
Tác giả: vinhnx