FFMPEG 使用总结

好,我们系统讲一遍 FFmpeg 编解码流程,并且给出完整可运行 C 代码 + 详细注释。内容覆盖:

1️⃣ 解码流程(输入 → 解封装 → 解码 → 得到原始数据) 2️⃣ 编码流程(原始数据 → 编码 → 封装 → 输出文件) 3️⃣ 必知关键函数说明 4️⃣ 常见坑与经验


🟥 一、FFmpeg 解码流程(Demux + Decode)

🎯 目标:

input.mp4 → 解封装 → 解码 → 得到 原始视频帧(YUV) 或音频 PCM 数据。


✅ 解码流程概念图

1
2
3
4
5
6
7
文件 / 流

AVFormat (demuxer)
↓ 提取 Packet(压缩数据 H264/H265/AAC)
AVCodec (decoder)
↓ 解码
得到 Frame(原始像素 YUV / PCM)

✅ FFmpeg 视频解码示例 (C)

解码视频,输出每一帧信息(可改成保存 YUV)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
#include <stdio.h>
#include <libavformat/avformat.h>
#include <libavcodec/avcodec.h>

int main() {
const char *filename = "input.mp4";

AVFormatContext *fmt_ctx = NULL;
AVCodecContext *codec_ctx = NULL;
AVCodec *codec = NULL;
AVPacket *pkt = NULL;
AVFrame *frame = NULL;
int video_stream_index = -1;

// 1️⃣ 打开输入文件并读取封装格式
if (avformat_open_input(&fmt_ctx, filename, NULL, NULL) < 0) {
printf("Failed to open input\n");
return -1;
}

// 2️⃣ 读取流信息
avformat_find_stream_info(fmt_ctx, NULL);

// 3️⃣ 找视频流
for (int i = 0; i < fmt_ctx->nb_streams; i++) {
if (fmt_ctx->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) {
video_stream_index = i;
break;
}
}

if (video_stream_index == -1) {
printf("No video stream found\n");
return -1;
}

// 4️⃣ 找解码器
AVCodecParameters *codecpar = fmt_ctx->streams[video_stream_index]->codecpar;
codec = avcodec_find_decoder(codecpar->codec_id);

codec_ctx = avcodec_alloc_context3(codec);
avcodec_parameters_to_context(codec_ctx, codecpar);

// 5️⃣ 打开解码器
if (avcodec_open2(codec_ctx, codec, NULL) < 0) {
printf("Failed to open decoder\n");
return -1;
}

pkt = av_packet_alloc();
frame = av_frame_alloc();

// 6️⃣ 不停读取 Packet(压缩数据)
while (av_read_frame(fmt_ctx, pkt) >= 0) {
if (pkt->stream_index == video_stream_index) {

// 7️⃣ 发送 Packet 到解码器
avcodec_send_packet(codec_ctx, pkt);

// 8️⃣ 不停收 Frame(可能 1 packet → 多 frame)
while (avcodec_receive_frame(codec_ctx, frame) == 0) {
printf("Got frame %d x %d PTS=%lld\n",
frame->width,
frame->height,
frame->pts);
}
}

av_packet_unref(pkt);
}

// 9️⃣ 刷新解码器(处理缓冲区剩余帧)
avcodec_send_packet(codec_ctx, NULL);
while (avcodec_receive_frame(codec_ctx, frame) == 0) {
printf("Flushed frame\n");
}

// 🔟 释放资源
av_frame_free(&frame);
av_packet_free(&pkt);
avcodec_free_context(&codec_ctx);
avformat_close_input(&fmt_ctx);

return 0;
}

🧠 关键 API 解释

函数 作用
avformat_open_input 打开文件/流
avformat_find_stream_info 读取媒体信息
avcodec_find_decoder 找到解码器
avcodec_open2 打开解码器
av_read_frame 从封装读取 Packet
avcodec_send_packet 把 Packet 送入解码器
avcodec_receive_frame 得到原始 Frame
avcodec_send_packet(NULL) flush
av_packet_unref 释放 packet 引用

🟩 二、FFmpeg 编码流程(Encode + Mux)

🎯 目标:

原始 YUV/PCM → 编码成 H264 → 封装 MP4


✅ 编码总体流程图

1
2
3
4
5
原始数据 (Frame)
→ AVCodec (encoder)
→ Packet(压缩数据)
→ AVFormat(封装 MP4/TS/FLV)
→ 输出文件

✅ FFmpeg 视频编码示例 (H264 → MP4)

演示:生成一个假的视频帧,编码写 MP4 (真实项目你会把 frame 数据替换成 camera / YUV 数据)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
#include <libavformat/avformat.h>
#include <libavcodec/avcodec.h>

int main() {
const char *out_file = "out.mp4";

AVFormatContext *fmt_ctx;
AVStream *video_stream;
AVCodec *codec;
AVCodecContext *codec_ctx;
AVFrame *frame;
AVPacket *pkt;

int width = 640, height = 480;

// 1️⃣ 创建输出上下文
avformat_alloc_output_context2(&fmt_ctx, NULL, NULL, out_file);

// 2️⃣ 找编码器
codec = avcodec_find_encoder(AV_CODEC_ID_H264);

// 3️⃣ 创建流
video_stream = avformat_new_stream(fmt_ctx, codec);

// 4️⃣ 创建编码器上下文
codec_ctx = avcodec_alloc_context3(codec);
codec_ctx->width = width;
codec_ctx->height = height;
codec_ctx->pix_fmt = AV_PIX_FMT_YUV420P;
codec_ctx->time_base = (AVRational){1, 25};

avcodec_open2(codec_ctx, codec, NULL);

avcodec_parameters_from_context(video_stream->codecpar, codec_ctx);

// 5️⃣ 打开输出文件
avio_open(&fmt_ctx->pb, out_file, AVIO_FLAG_WRITE);

// 6️⃣ 写文件头
avformat_write_header(fmt_ctx, NULL);

frame = av_frame_alloc();
frame->format = codec_ctx->pix_fmt;
frame->width = width;
frame->height = height;
av_frame_get_buffer(frame, 32);

pkt = av_packet_alloc();

for (int i = 0; i < 100; i++) {
av_frame_make_writable(frame);

// 7️⃣ 填充假数据(真实项目这里写 YUV 数据)
memset(frame->data[0], i, width * height);
memset(frame->data[1], 128, width * height / 4);
memset(frame->data[2], 128, width * height / 4);

frame->pts = i;

// 8️⃣ 编码
avcodec_send_frame(codec_ctx, frame);

while (avcodec_receive_packet(codec_ctx, pkt) == 0) {
pkt->stream_index = video_stream->index;
av_interleaved_write_frame(fmt_ctx, pkt);
av_packet_unref(pkt);
}
}

// 9️⃣ flush
avcodec_send_frame(codec_ctx, NULL);
while (avcodec_receive_packet(codec_ctx, pkt) == 0) {
av_interleaved_write_frame(fmt_ctx, pkt);
av_packet_unref(pkt);
}

// 🔟 写文件尾
av_write_trailer(fmt_ctx);

av_frame_free(&frame);
av_packet_free(&pkt);
avcodec_free_context(&codec_ctx);
avio_close(fmt_ctx->pb);
avformat_free_context(fmt_ctx);

return 0;
}

🧠 编码关键 API

函数 作用
avformat_alloc_output_context2 创建输出
avcodec_find_encoder 找编码器
avcodec_open2 打开编码器
av_frame_get_buffer 分配 Frame 数据区
avcodec_send_frame 输入 Frame
avcodec_receive_packet 得到压缩 Packet
av_interleaved_write_frame 写入封装文件
av_write_trailer 写尾

⚠️ 常见坑

❌ 不 flush → 会少帧

1
2
avcodec_send_packet(NULL)
avcodec_send_frame(NULL)

必须要!


❌ Packet vs Frame 弄混

  • Frame = 原始数据(YUV / PCM)
  • Packet = 压缩数据(H264 / AAC)

❌ 忘记 av_packet_unref

必泄漏。


❌ pts / time_base 处理错误

视频会抖 / 播放不了。


如果你告诉我:

  • Android / Linux / macOS?
  • 你要做:
    • 播放器?
    • 转码?
    • 推流(RTMP / WebRTC)?
    • OpenGL + YUV 渲染?
    • FFmpeg + 多线程?

我可以把 流程改成你项目专用版本 + 完整可运行工程模板(非常适合你现在的视频 SDK 项目 👍)。