FFmpeg开源项目深入解析:架构设计与实战应用

FFmpeg是当今最重要的开源音视频处理框架,被广泛应用于视频播放器、转码服务、直播系统等各种多媒体应用中。本文将深入解析FFmpeg的架构设计、核心组件和实战应用。

1. FFmpeg项目概述

1.1 项目历史与发展

FFmpeg项目始于2000年,由Fabrice Bellard创建,经过20多年的发展,已成为音视频领域最重要的开源项目之一。

发展历程:

  • 2000年:项目启动,最初名为FFmpeg
  • 2004年:Michael Niedermayer接手项目维护
  • 2011年:项目分叉,产生Libav项目
  • 2015年:重新合并,统一开发
  • 至今:持续活跃开发,支持最新的音视频标准

1.2 项目组成

FFmpeg项目包含多个组件:

1
2
3
4
5
6
7
8
9
10
11
12
FFmpeg项目结构
├── libavutil # 工具库(数学、字符串、内存管理等)
├── libavcodec # 编解码库(音视频编解码器)
├── libavformat # 格式库(容器格式处理)
├── libavdevice # 设备库(音视频设备访问)
├── libavfilter # 滤镜库(音视频处理滤镜)
├── libswscale # 图像缩放库(像素格式转换)
├── libswresample # 音频重采样库
├── ffmpeg # 命令行工具
├── ffplay # 播放器工具
├── ffprobe # 媒体信息分析工具
└── ffserver # 流媒体服务器(已废弃)

2. FFmpeg架构设计

2.1 整体架构

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
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
/*
* FFmpeg架构层次图
*
* 应用层
* ┌─────────────────────────────────────┐
* │ ffmpeg, ffplay, ffprobe, 用户应用 │
* └─────────────────────────────────────┘
* │
* API层
* ┌─────────────────────────────────────┐
* │ libavformat, libavcodec │
* │ libavfilter, libavdevice │
* └─────────────────────────────────────┘
* │
* 核心层
* ┌─────────────────────────────────────┐
* │ libavutil, libswscale, libswresample │
* └─────────────────────────────────────┘
* │
* 系统层
* ┌─────────────────────────────────────┐
* │ 操作系统API, 硬件驱动 │
* └─────────────────────────────────────┘
*/

// 核心数据结构详解

/**
* AVFormatContext - FFmpeg的核心格式上下文结构
* 这是FFmpeg中最重要的数据结构之一,用于管理音视频文件的格式信息
* 无论是读取还是写入媒体文件,都需要通过这个结构来操作
*/
typedef struct AVFormatContext {
const AVClass *av_class; // 类信息 - 用于日志和选项管理
struct AVInputFormat *iformat; // 输入格式 - 指向具体的输入格式处理器(如MP4、AVI等)
struct AVOutputFormat *oformat; // 输出格式 - 指向具体的输出格式处理器
void *priv_data; // 私有数据 - 格式特定的私有数据指针
AVIOContext *pb; // I/O上下文 - 处理实际的文件读写操作

unsigned int nb_streams; // 流数量 - 文件中包含的音视频流总数
AVStream **streams; // 流数组 - 指向所有流的指针数组

char filename[1024]; // 文件名 - 输入或输出文件的路径
int64_t start_time; // 开始时间 - 媒体文件的起始时间戳(微秒)
int64_t duration; // 持续时间 - 媒体文件的总时长(微秒)
int64_t bit_rate; // 比特率 - 平均比特率(比特/秒)

AVDictionary *metadata; // 元数据 - 存储标题、作者、版权等信息
// ... 更多字段
} AVFormatContext;

/**
* 使用说明:
* 1. 对于输入文件:通过avformat_open_input()创建并初始化
* 2. 对于输出文件:通过avformat_alloc_output_context2()创建
* 3. streams数组包含了文件中的所有音视频轨道
* 4. 每个流都有自己的编解码参数和时间基准
*/

/**
* AVCodecContext - 编解码器上下文结构
* 这个结构包含了编解码器的所有配置参数和状态信息
* 是进行音视频编解码操作的核心数据结构
*/
typedef struct AVCodecContext {
const AVClass *av_class; // 类信息 - 用于日志系统和参数设置
enum AVMediaType codec_type; // 编解码器类型 - AVMEDIA_TYPE_VIDEO/AUDIO/SUBTITLE等
const struct AVCodec *codec; // 编解码器 - 指向具体的编解码器实现
enum AVCodecID codec_id; // 编解码器ID - 如AV_CODEC_ID_H264、AV_CODEC_ID_AAC等

unsigned int codec_tag; // 编解码器标签 - 四字符代码,用于容器格式识别
void *priv_data; // 私有数据 - 编解码器特定的内部数据

int bit_rate; // 比特率 - 目标编码比特率(比特/秒)
int width, height; // 视频尺寸 - 视频帧的宽度和高度(像素)
AVRational time_base; // 时间基准 - 时间戳的单位,表示为分数(分子/分母)
AVRational framerate; // 帧率 - 视频帧率,表示为分数形式

int sample_rate; // 采样率 - 音频采样频率(赫兹)
int channels; // 声道数 - 音频声道数量(1=单声道,2=立体声等)
enum AVSampleFormat sample_fmt; // 采样格式 - 音频样本的数据格式(16位整数、32位浮点等)

// ... 更多字段
} AVCodecContext;

/**
* 重要概念解释:
*
* 1. time_base(时间基准):
* - 定义了时间戳的精度,例如{1, 25}表示每个时间单位是1/25秒
* - pts = timestamp * time_base 得到实际时间
* - 不同的流可能有不同的时间基准
*
* 2. codec_id vs codec_tag:
* - codec_id是FFmpeg内部的标识符,跨平台统一
* - codec_tag是容器格式中的标识符,可能因格式而异
*
* 3. 编解码器类型:
* - AVMEDIA_TYPE_VIDEO:视频流
* - AVMEDIA_TYPE_AUDIO:音频流
* - AVMEDIA_TYPE_SUBTITLE:字幕流
* - AVMEDIA_TYPE_DATA:数据流
*/

2.2 数据流处理模型

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
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
// FFmpeg数据流处理模型
/*
* 输入 -> 解复用 -> 解码 -> 处理 -> 编码 -> 复用 -> 输出
* │ │ │ │ │ │ │
* │ AVFormat AVCodec Filter AVCodec AVFormat │
* │ │ │ │ │ │ │
* └───────┴───────┴──────┴──────┴──────┴──────┘
*/

/**
* 典型的媒体文件处理流程
* 这个函数展示了FFmpeg处理媒体文件的标准步骤:
* 打开输入 -> 分析流 -> 设置解码器 -> 处理数据 -> 清理资源
*/
int process_media_file(const char *input_file, const char *output_file) {
// 声明所有需要的上下文和数据结构
AVFormatContext *input_ctx = NULL; // 输入文件格式上下文
AVFormatContext *output_ctx = NULL; // 输出文件格式上下文
AVCodecContext *decoder_ctx = NULL; // 解码器上下文
AVCodecContext *encoder_ctx = NULL; // 编码器上下文
AVFrame *frame = NULL; // 存储解码后的原始帧数据
AVPacket *packet = NULL; // 存储编码后的压缩数据包
int ret = 0; // 返回值,用于错误检查

/**
* 步骤1: 打开输入文件
* avformat_open_input()是FFmpeg中打开媒体文件的标准函数
* 参数说明:
* - &input_ctx: 输出参数,函数会分配并初始化AVFormatContext
* - input_file: 输入文件路径
* - NULL: 输入格式(NULL表示自动检测)
* - NULL: 格式选项字典(NULL表示使用默认选项)
*/
ret = avformat_open_input(&input_ctx, input_file, NULL, NULL);
if (ret < 0) {
fprintf(stderr, "无法打开输入文件: %s\n", av_err2str(ret));
return ret;
}

/**
* 步骤2: 获取流信息
* avformat_find_stream_info()分析文件中的所有流
* 这个函数会:
* - 读取文件头部信息
* - 分析每个流的编解码参数
* - 填充streams数组中的codecpar字段
* - 可能需要读取一些数据包来确定流的特性
*/
ret = avformat_find_stream_info(input_ctx, NULL);
if (ret < 0) {
fprintf(stderr, "无法获取流信息: %s\n", av_err2str(ret));
goto cleanup;
}

// 打印文件信息(调试用)
av_dump_format(input_ctx, 0, input_file, 0);

/**
* 步骤3: 查找视频流
* av_find_best_stream()在所有流中查找指定类型的最佳流
* 参数说明:
* - input_ctx: 格式上下文
* - AVMEDIA_TYPE_VIDEO: 要查找的媒体类型(视频)
* - -1: 期望的流索引(-1表示自动选择)
* - -1: 相关流索引(-1表示无关联)
* - NULL: 返回找到的解码器(我们这里不需要)
* - 0: 标志位
*/
int video_stream_index = av_find_best_stream(input_ctx, AVMEDIA_TYPE_VIDEO, -1, -1, NULL, 0);
if (video_stream_index < 0) {
fprintf(stderr, "未找到视频流\n");
ret = video_stream_index;
goto cleanup;
}

printf("找到视频流,索引: %d\n", video_stream_index);

/**
* 步骤4: 创建解码器上下文
* 这个过程包括几个子步骤:
* 4.1 获取流信息
* 4.2 查找对应的解码器
* 4.3 分配解码器上下文
* 4.4 复制编解码参数
* 4.5 打开解码器
*/

// 4.1 获取视频流
AVStream *video_stream = input_ctx->streams[video_stream_index];

// 4.2 根据编解码器ID查找解码器
const AVCodec *decoder = avcodec_find_decoder(video_stream->codecpar->codec_id);
if (!decoder) {
fprintf(stderr, "未找到解码器,编解码器ID: %d\n", video_stream->codecpar->codec_id);
ret = AVERROR_DECODER_NOT_FOUND;
goto cleanup;
}

printf("找到解码器: %s\n", decoder->name);

// 4.3 分配解码器上下文内存
decoder_ctx = avcodec_alloc_context3(decoder);
if (!decoder_ctx) {
fprintf(stderr, "无法分配解码器上下文内存\n");
ret = AVERROR(ENOMEM);
goto cleanup;
}

/**
* 4.4 复制编解码参数
* 将流中的编解码参数复制到解码器上下文中
* 这包括视频尺寸、像素格式、比特率等信息
*/
ret = avcodec_parameters_to_context(decoder_ctx, video_stream->codecpar);
if (ret < 0) {
fprintf(stderr, "复制编解码参数失败: %s\n", av_err2str(ret));
goto cleanup;
}

/**
* 4.5 打开解码器
* 初始化解码器,准备开始解码工作
* 第三个参数是选项字典,可以用来设置解码器特定的选项
*/
ret = avcodec_open2(decoder_ctx, decoder, NULL);
if (ret < 0) {
fprintf(stderr, "无法打开解码器: %s\n", av_err2str(ret));
goto cleanup;
}

printf("解码器初始化成功,视频尺寸: %dx%d\n", decoder_ctx->width, decoder_ctx->height);

// 5. 创建输出文件
ret = avformat_alloc_output_context2(&output_ctx, NULL, NULL, output_file);
if (ret < 0) {
fprintf(stderr, "无法创建输出上下文: %s\n", av_err2str(ret));
goto cleanup;
}

// 6. 处理循环
frame = av_frame_alloc();
packet = av_packet_alloc();
if (!frame || !packet) {
ret = AVERROR(ENOMEM);
goto cleanup;
}

while (av_read_frame(input_ctx, packet) >= 0) {
if (packet->stream_index == video_stream_index) {
ret = decode_and_process(decoder_ctx, frame, packet);
if (ret < 0) {
break;
}
}
av_packet_unref(packet);
}

cleanup:
av_frame_free(&frame);
av_packet_free(&packet);
avcodec_free_context(&decoder_ctx);
avcodec_free_context(&encoder_ctx);
avformat_close_input(&input_ctx);
if (output_ctx) {
if (!(output_ctx->oformat->flags & AVFMT_NOFILE))
avio_closep(&output_ctx->pb);
avformat_free_context(output_ctx);
}

return ret;
}

3. 核心组件详解

3.1 libavformat - 格式处理库

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
// 格式检测和处理
typedef struct AVInputFormat {
const char *name; // 格式名称
const char *long_name; // 长名称
int flags; // 标志
const char *extensions; // 文件扩展名
const struct AVCodecTag * const *codec_tag; // 编解码器标签

// 回调函数
int (*read_probe)(const AVProbeData *); // 探测函数
int (*read_header)(struct AVFormatContext *); // 读取头部
int (*read_packet)(struct AVFormatContext *, AVPacket *); // 读取包
int (*read_close)(struct AVFormatContext *); // 关闭
int (*read_seek)(struct AVFormatContext *, int, int64_t, int); // 定位
} AVInputFormat;

// 自定义格式示例
static int my_format_probe(const AVProbeData *p) {
// 检查文件头部特征
if (p->buf_size < 4)
return 0;

if (AV_RL32(p->buf) == MKTAG('M', 'Y', 'F', 'T'))
return AVPROBE_SCORE_MAX;

return 0;
}

static int my_format_read_header(AVFormatContext *s) {
AVIOContext *pb = s->pb;
AVStream *st;
uint32_t magic;

// 读取文件头
magic = avio_rl32(pb);
if (magic != MKTAG('M', 'Y', 'F', 'T'))
return AVERROR_INVALIDDATA;

// 创建流
st = avformat_new_stream(s, NULL);
if (!st)
return AVERROR(ENOMEM);

// 设置流参数
st->codecpar->codec_type = AVMEDIA_TYPE_VIDEO;
st->codecpar->codec_id = AV_CODEC_ID_H264;

return 0;
}

static int my_format_read_packet(AVFormatContext *s, AVPacket *pkt) {
AVIOContext *pb = s->pb;
int ret, size;

if (avio_feof(pb))
return AVERROR_EOF;

// 读取包大小
size = avio_rl32(pb);
if (size <= 0)
return AVERROR_INVALIDDATA;

// 分配包内存
ret = av_new_packet(pkt, size);
if (ret < 0)
return ret;

// 读取包数据
ret = avio_read(pb, pkt->data, size);
if (ret < 0) {
av_packet_unref(pkt);
return ret;
}

pkt->stream_index = 0;
return 0;
}

AVInputFormat my_format = {
.name = "myformat",
.long_name = "My Custom Format",
.extensions = "myf",
.read_probe = my_format_probe,
.read_header = my_format_read_header,
.read_packet = my_format_read_packet,
};

3.2 libavcodec - 编解码库

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
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
// 编解码器结构
typedef struct AVCodec {
const char *name; // 编解码器名称
const char *long_name; // 长名称
enum AVMediaType type; // 媒体类型
enum AVCodecID id; // 编解码器ID
int capabilities; // 能力标志
const AVRational *supported_framerates; // 支持的帧率
const enum AVPixelFormat *pix_fmts; // 支持的像素格式
const int *supported_samplerates; // 支持的采样率
const enum AVSampleFormat *sample_fmts; // 支持的采样格式

// 回调函数
int (*init)(AVCodecContext *); // 初始化
int (*encode2)(AVCodecContext *, AVPacket *, const AVFrame *, int *); // 编码
int (*decode)(AVCodecContext *, void *, int *, AVPacket *); // 解码
int (*close)(AVCodecContext *); // 关闭
int (*flush)(AVCodecContext *); // 刷新
} AVCodec;

/**
* 视频帧解码函数详解
*
* FFmpeg 4.0+版本采用了新的编解码API,基于send/receive模式
* 这种设计支持多线程解码和更好的错误处理
*
* 解码流程:
* 1. 发送压缩数据包到解码器
* 2. 从解码器接收解码后的原始帧
* 3. 处理帧数据
* 4. 释放帧引用
*/
int decode_video_frame(AVCodecContext *codec_ctx, AVFrame *frame, AVPacket *packet) {
int ret;

/**
* 步骤1: 发送数据包到解码器
*
* avcodec_send_packet()将压缩的数据包发送给解码器
* 解码器会将数据包放入内部队列中等待处理
*
* 重要特性:
* - 支持多个数据包的缓冲
* - 可以处理不完整的帧(B帧需要参考帧)
* - 支持硬件加速解码
*/
ret = avcodec_send_packet(codec_ctx, packet);
if (ret < 0) {
fprintf(stderr, "发送包到解码器失败: %s\n", av_err2str(ret));
return ret;
}

/**
* 步骤2: 接收解码后的帧
*
* avcodec_receive_frame()从解码器获取解码完成的帧
* 由于视频编码的特性(B帧、帧重排序),一个数据包可能产生0个、1个或多个帧
*
* 返回值说明:
* - 0: 成功获取一帧
* - AVERROR(EAGAIN): 需要更多输入数据
* - AVERROR_EOF: 解码器已刷新完毕
* - 其他负值: 解码错误
*/
while (ret >= 0) {
ret = avcodec_receive_frame(codec_ctx, frame);
if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) {
// EAGAIN: 解码器需要更多数据包才能输出帧
// EOF: 所有帧都已输出完毕
break;
} else if (ret < 0) {
fprintf(stderr, "解码错误: %s\n", av_err2str(ret));
return ret;
}

/**
* 步骤3: 处理解码后的帧
*
* 此时frame包含了解码后的原始视频数据:
* - frame->data[]: 各个颜色平面的数据指针
* - frame->linesize[]: 各个平面的行字节数
* - frame->width/height: 帧尺寸
* - frame->format: 像素格式(YUV420P、RGB24等)
* - frame->pts: 显示时间戳
*/
printf("解码帧: %dx%d, 格式: %s, PTS: %ld\n",
frame->width, frame->height,
av_get_pix_fmt_name(frame->format),
frame->pts);

// 这里可以对帧进行各种处理:
// - 格式转换(YUV转RGB)
// - 缩放操作
// - 滤镜效果
// - 保存为图片
process_decoded_frame(frame);

/**
* 步骤4: 释放帧引用
*
* av_frame_unref()释放帧的数据缓冲区引用
* 这不会释放AVFrame结构本身,只是减少数据的引用计数
* 当引用计数为0时,实际的数据内存才会被释放
*/
av_frame_unref(frame);
}

return 0;
}

/**
* 解码过程中的关键概念:
*
* 1. 时间戳(PTS/DTS):
* - PTS (Presentation Time Stamp): 显示时间戳
* - DTS (Decode Time Stamp): 解码时间戳
* - 由于B帧的存在,解码顺序和显示顺序可能不同
*
* 2. 像素格式:
* - YUV420P: 最常见的格式,Y、U、V分别存储
* - RGB24: 每像素24位的RGB格式
* - NV12: Y平面 + 交错的UV平面
*
* 3. 内存管理:
* - FFmpeg使用引用计数管理内存
* - 多个AVFrame可以共享同一块数据
* - 必须正确调用unref函数避免内存泄漏
*/

/**
* 视频帧编码函数详解
*
* 编码过程是解码的逆过程,将原始视频帧压缩为数据包
* 同样采用send/receive模式,支持多种编码算法和参数配置
*
* 编码流程:
* 1. 发送原始帧到编码器
* 2. 从编码器接收压缩后的数据包
* 3. 处理数据包(写入文件、网络传输等)
* 4. 释放数据包引用
*/
int encode_video_frame(AVCodecContext *codec_ctx, AVFrame *frame, AVPacket *packet) {
int ret;

/**
* 步骤1: 发送帧到编码器
*
* avcodec_send_frame()将原始视频帧发送给编码器
* 编码器会根据配置的参数进行压缩处理
*
* 重要参数影响编码质量:
* - codec_ctx->bit_rate: 目标比特率
* - codec_ctx->gop_size: GOP大小(关键帧间隔)
* - codec_ctx->max_b_frames: 最大B帧数量
* - codec_ctx->qmin/qmax: 量化参数范围
*/
ret = avcodec_send_frame(codec_ctx, frame);
if (ret < 0) {
fprintf(stderr, "发送帧到编码器失败: %s\n", av_err2str(ret));
return ret;
}

/**
* 步骤2: 接收编码后的包
*
* avcodec_receive_packet()从编码器获取压缩后的数据包
* 由于编码器的缓冲和B帧重排序,一个输入帧可能产生0个、1个或多个包
*
* 数据包特性:
* - 包含压缩后的视频数据
* - 携带时间戳信息(PTS/DTS)
* - 包含帧类型信息(I/P/B帧)
* - 可能包含副数据(SEI信息等)
*/
while (ret >= 0) {
ret = avcodec_receive_packet(codec_ctx, packet);
if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) {
// EAGAIN: 编码器需要更多输入帧
// EOF: 编码器已刷新完毕,所有包都已输出
break;
} else if (ret < 0) {
fprintf(stderr, "编码错误: %s\n", av_err2str(ret));
return ret;
}

/**
* 步骤3: 处理编码后的包
*
* 此时packet包含了压缩后的视频数据:
* - packet->data: 压缩数据指针
* - packet->size: 数据大小(字节)
* - packet->pts: 显示时间戳
* - packet->dts: 解码时间戳
* - packet->flags: 包标志(关键帧等)
* - packet->stream_index: 流索引
*/
printf("编码包: 大小=%d, pts=%ld, dts=%ld, 关键帧=%s\n",
packet->size, packet->pts, packet->dts,
(packet->flags & AV_PKT_FLAG_KEY) ? "是" : "否");

// 这里可以对数据包进行各种处理:
// - 写入文件(MP4、AVI等容器格式)
// - 网络传输(RTMP、WebRTC等)
// - 实时流媒体(HLS、DASH等)
write_encoded_packet(packet);

/**
* 步骤4: 释放数据包引用
*
* av_packet_unref()释放数据包的缓冲区引用
* 类似于帧的引用管理,确保内存正确释放
*/
av_packet_unref(packet);
}

return 0;
}

/**
* 编码过程中的关键概念:
*
* 1. 帧类型:
* - I帧(关键帧): 独立编码,不依赖其他帧
* - P帧(预测帧): 依赖前面的I帧或P帧
* - B帧(双向帧): 依赖前后的I帧或P帧
*
* 2. GOP结构:
* - GOP (Group of Pictures): 一组连续的帧
* - 通常以I帧开始,包含若干P帧和B帧
* - GOP大小影响压缩效率和随机访问性能
*
* 3. 码率控制:
* - CBR (Constant Bit Rate): 恒定比特率
* - VBR (Variable Bit Rate): 可变比特率
* - CRF (Constant Rate Factor): 恒定质量因子
*/

3.3 libavfilter - 滤镜库

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
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
/**
* AVFilterGraph - 滤镜图结构详解
*
* 滤镜图是FFmpeg中用于音视频处理的核心概念
* 它将多个滤镜连接成一个处理管道,实现复杂的音视频效果
*
* 主要特性:
* - 支持多输入多输出
* - 自动格式转换和缓冲管理
* - 支持并行处理和多线程
* - 丰富的内置滤镜(300+种)
*/
typedef struct AVFilterGraph {
unsigned nb_filters; // 当前图中的滤镜数量
AVFilterContext **filters; // 滤镜上下文指针数组,每个元素指向一个滤镜实例

char *scale_sws_opts; // libswscale缩放选项字符串
char *resample_lavr_opts; // libavresample重采样选项字符串

AVClass *av_class; // 用于日志和选项系统
void *opaque; // 用户自定义数据指针

// 其他重要字段(简化显示):
// - thread_type: 线程类型配置
// - nb_threads: 线程数量
// - execute: 执行函数指针
} AVFilterGraph;

/**
* 常用滤镜类型说明:
*
* 1. 源滤镜(Source Filters):
* - buffer: 视频帧输入源
* - abuffer: 音频帧输入源
* - color: 生成纯色视频
* - testsrc: 生成测试图案
*
* 2. 处理滤镜(Processing Filters):
* - scale: 视频缩放和格式转换
* - crop: 视频裁剪
* - overlay: 视频叠加
* - blur: 模糊效果
*
* 3. 输出滤镜(Sink Filters):
* - buffersink: 视频帧输出
* - abuffersink: 音频帧输出
* - nullsink: 丢弃输出
*/

/**
* 创建滤镜图函数详解
*
* 这个函数演示了如何创建一个完整的视频处理滤镜图
* 包括输入源、处理滤镜和输出接收器的完整流程
*
* 滤镜图创建步骤:
* 1. 分配滤镜图结构
* 2. 创建输入源滤镜(buffer)
* 3. 创建输出接收滤镜(buffersink)
* 4. 配置滤镜参数
* 5. 解析滤镜链描述字符串
* 6. 配置和验证滤镜图
*/
int create_filter_graph(AVFilterGraph **graph,
AVCodecContext *decoder_ctx,
AVCodecContext *encoder_ctx) {
AVFilterGraph *filter_graph;
AVFilterContext *buffersrc_ctx = NULL; // 输入源滤镜上下文
AVFilterContext *buffersink_ctx = NULL; // 输出接收滤镜上下文
AVFilterContext *scale_ctx = NULL; // 缩放滤镜上下文(可选)

// 获取滤镜定义
const AVFilter *buffersrc = avfilter_get_by_name("buffer"); // 视频输入源
const AVFilter *buffersink = avfilter_get_by_name("buffersink"); // 视频输出接收
const AVFilter *scale = avfilter_get_by_name("scale"); // 缩放滤镜

// 输入输出连接描述符
AVFilterInOut *outputs = avfilter_inout_alloc(); // 输出端点
AVFilterInOut *inputs = avfilter_inout_alloc(); // 输入端点

char args[512]; // 滤镜参数字符串缓冲区
int ret;

/**
* 步骤1: 分配滤镜图
*
* avfilter_graph_alloc()创建一个新的滤镜图实例
* 滤镜图管理所有滤镜的生命周期和连接关系
*/
filter_graph = avfilter_graph_alloc();
if (!filter_graph) {
ret = AVERROR(ENOMEM);
goto cleanup;
}

/**
* 步骤2: 创建buffer源滤镜
*
* buffer滤镜是视频数据的输入点,需要配置:
* - video_size: 视频尺寸
* - pix_fmt: 像素格式
* - time_base: 时间基准
* - pixel_aspect: 像素宽高比
*
* 这些参数通常从解码器上下文中获取
*/
snprintf(args, sizeof(args),
"video_size=%dx%d:pix_fmt=%d:time_base=%d/%d:pixel_aspect=%d/%d",
decoder_ctx->width, decoder_ctx->height, decoder_ctx->pix_fmt,
decoder_ctx->time_base.num, decoder_ctx->time_base.den,
decoder_ctx->sample_aspect_ratio.num, decoder_ctx->sample_aspect_ratio.den);

ret = avfilter_graph_create_filter(&buffersrc_ctx, buffersrc, "in",
args, NULL, filter_graph);
if (ret < 0) {
fprintf(stderr, "创建buffer源滤镜失败: %s\n", av_err2str(ret));
goto cleanup;
}

/**
* 步骤3: 创建buffersink滤镜
*
* buffersink滤镜是视频数据的输出点
* 它收集处理后的帧,供应用程序获取
* 可以配置输出格式限制
*/
ret = avfilter_graph_create_filter(&buffersink_ctx, buffersink, "out",
NULL, NULL, filter_graph);
if (ret < 0) {
fprintf(stderr, "创建buffersink滤镜失败: %s\n", av_err2str(ret));
goto cleanup;
}

/**
* 步骤4: 设置输出像素格式约束
*
* 限制buffersink只输出编码器支持的像素格式
* 这样可以避免不必要的格式转换
*/
enum AVPixelFormat pix_fmts[] = { encoder_ctx->pix_fmt, AV_PIX_FMT_NONE };
ret = av_opt_set_int_list(buffersink_ctx, "pix_fmts", pix_fmts,
AV_PIX_FMT_NONE, AV_OPT_SEARCH_CHILDREN);
if (ret < 0) {
fprintf(stderr, "设置输出像素格式失败: %s\n", av_err2str(ret));
goto cleanup;
}

/**
* 步骤5: 配置输入输出端点
*
* AVFilterInOut结构描述滤镜图的输入输出端点
* 用于连接外部数据源和接收器
*/
outputs->name = av_strdup("in"); // 输出端点名称
outputs->filter_ctx = buffersrc_ctx; // 关联的滤镜上下文
outputs->pad_idx = 0; // 滤镜的输出pad索引
outputs->next = NULL; // 链表下一个元素

inputs->name = av_strdup("out"); // 输入端点名称
inputs->filter_ctx = buffersink_ctx; // 关联的滤镜上下文
inputs->pad_idx = 0; // 滤镜的输入pad索引
inputs->next = NULL; // 链表下一个元素

/**
* 步骤6: 解析滤镜图描述字符串
*
* 滤镜描述语法示例:
* - "scale=640:480" : 缩放到640x480
* - "scale=iw/2:ih/2" : 缩放到原尺寸的一半
* - "crop=100:100:10:10" : 裁剪100x100区域,从(10,10)开始
* - "overlay=10:10" : 叠加到(10,10)位置
* - "hflip,vflip" : 水平翻转后垂直翻转
*/
const char *filter_descr = "scale=640:480"; // 简单的缩放滤镜
ret = avfilter_graph_parse_ptr(filter_graph, filter_descr,
&inputs, &outputs, NULL);
if (ret < 0) {
fprintf(stderr, "解析滤镜图失败: %s\n", av_err2str(ret));
goto cleanup;
}

// 配置滤镜图
ret = avfilter_graph_config(filter_graph, NULL);
if (ret < 0) {
fprintf(stderr, "配置滤镜图失败: %s\n", av_err2str(ret));
goto cleanup;
}

*graph = filter_graph;

cleanup:
avfilter_inout_free(&inputs);
avfilter_inout_free(&outputs);

if (ret < 0)
avfilter_graph_free(&filter_graph);

return ret;
}

// 应用滤镜
int apply_filters(AVFilterGraph *filter_graph, AVFrame *input_frame, AVFrame *output_frame) {
AVFilterContext *buffersrc_ctx = filter_graph->filters[0];
AVFilterContext *buffersink_ctx = filter_graph->filters[filter_graph->nb_filters - 1];
int ret;

// 向滤镜图输入帧
ret = av_buffersrc_add_frame_flags(buffersrc_ctx, input_frame, AV_BUFFERSRC_FLAG_KEEP_REF);
if (ret < 0) {
fprintf(stderr, "向滤镜图输入帧失败: %s\n", av_err2str(ret));
return ret;
}

// 从滤镜图获取输出帧
ret = av_buffersink_get_frame(buffersink_ctx, output_frame);
if (ret < 0) {
if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) {
return ret;
}
fprintf(stderr, "从滤镜图获取输出帧失败: %s\n", av_err2str(ret));
return ret;
}

return 0;
}

4. FFmpeg命令行工具

4.1 ffmpeg命令详解

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
# 基本语法
ffmpeg [全局选项] {[输入文件选项] -i 输入文件} ... {[输出文件选项] 输出文件} ...

# 常用全局选项
-y # 覆盖输出文件
-n # 不覆盖输出文件
-v level # 设置日志级别
-stats # 显示编码统计信息
-progress url # 发送进度信息到URL

# 输入选项
-f format # 强制输入格式
-ss position # 设置开始位置
-t duration # 设置持续时间
-to position # 设置结束位置
-itsoffset offset # 设置输入时间偏移

# 输出选项
-f format # 设置输出格式
-c codec # 设置编解码器
-b:v bitrate # 设置视频比特率
-b:a bitrate # 设置音频比特率
-r fps # 设置帧率
-s size # 设置视频尺寸
-aspect ratio # 设置宽高比

4.2 实用命令示例

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
# 1. 格式转换
ffmpeg -i input.avi output.mp4
ffmpeg -i input.mov -c:v libx264 -c:a aac output.mp4

# 2. 视频压缩
ffmpeg -i input.mp4 -c:v libx264 -crf 23 -c:a aac -b:a 128k output.mp4
ffmpeg -i input.mp4 -vf scale=1280:720 -c:v libx264 -b:v 2M output.mp4

# 3. 音频处理
ffmpeg -i input.mp4 -vn -c:a libmp3lame -b:a 192k output.mp3
ffmpeg -i input.wav -c:a aac -b:a 128k output.m4a

# 4. 视频剪切
ffmpeg -i input.mp4 -ss 00:01:30 -t 00:02:00 -c copy output.mp4
ffmpeg -i input.mp4 -ss 30 -to 150 -c copy output.mp4

# 5. 视频合并
ffmpeg -f concat -safe 0 -i filelist.txt -c copy output.mp4
# filelist.txt内容:
# file 'video1.mp4'
# file 'video2.mp4'
# file 'video3.mp4'

# 6. 添加水印
ffmpeg -i input.mp4 -i watermark.png -filter_complex "overlay=10:10" output.mp4

# 7. 视频滤镜
ffmpeg -i input.mp4 -vf "scale=1920:1080,fps=30" output.mp4
ffmpeg -i input.mp4 -vf "brightness=0.1,contrast=1.2" output.mp4

# 8. 直播推流
ffmpeg -re -i input.mp4 -c copy -f flv rtmp://server/live/stream

# 9. 屏幕录制(Windows)
ffmpeg -f gdigrab -i desktop -c:v libx264 -r 30 screen_record.mp4

# 10. 批量处理脚本
#!/bin/bash
for file in *.avi; do
ffmpeg -i "$file" -c:v libx264 -c:a aac "${file%.avi}.mp4"
done

4.3 ffprobe媒体分析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 基本信息
ffprobe -v quiet -print_format json -show_format -show_streams input.mp4

# 详细信息
ffprobe -v quiet -print_format json -show_format -show_streams -show_chapters input.mp4

# 特定信息提取
ffprobe -v quiet -select_streams v:0 -show_entries stream=width,height,duration input.mp4
ffprobe -v quiet -select_streams a:0 -show_entries stream=sample_rate,channels input.mp4

# 帧信息
ffprobe -v quiet -select_streams v:0 -show_frames input.mp4

# 包信息
ffprobe -v quiet -show_packets input.mp4

5. FFmpeg API编程实战

5.1 简单的视频转码器

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
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
#include <libavformat/avformat.h>
#include <libavcodec/avcodec.h>
#include <libavutil/opt.h>
#include <libavutil/imgutils.h>

typedef struct TranscodeContext {
AVFormatContext *input_ctx;
AVFormatContext *output_ctx;
AVCodecContext *decoder_ctx;
AVCodecContext *encoder_ctx;
int video_stream_index;
AVFrame *frame;
AVPacket *packet;
} TranscodeContext;

int init_transcoder(TranscodeContext *ctx, const char *input_file, const char *output_file) {
int ret;

// 初始化
memset(ctx, 0, sizeof(*ctx));

// 打开输入文件
ret = avformat_open_input(&ctx->input_ctx, input_file, NULL, NULL);
if (ret < 0) {
fprintf(stderr, "无法打开输入文件: %s\n", av_err2str(ret));
return ret;
}

ret = avformat_find_stream_info(ctx->input_ctx, NULL);
if (ret < 0) {
fprintf(stderr, "无法获取流信息: %s\n", av_err2str(ret));
return ret;
}

// 查找视频流
ctx->video_stream_index = av_find_best_stream(ctx->input_ctx, AVMEDIA_TYPE_VIDEO, -1, -1, NULL, 0);
if (ctx->video_stream_index < 0) {
fprintf(stderr, "未找到视频流\n");
return ctx->video_stream_index;
}

// 创建解码器
AVStream *video_stream = ctx->input_ctx->streams[ctx->video_stream_index];
const AVCodec *decoder = avcodec_find_decoder(video_stream->codecpar->codec_id);
if (!decoder) {
fprintf(stderr, "未找到解码器\n");
return AVERROR_DECODER_NOT_FOUND;
}

ctx->decoder_ctx = avcodec_alloc_context3(decoder);
if (!ctx->decoder_ctx) {
return AVERROR(ENOMEM);
}

ret = avcodec_parameters_to_context(ctx->decoder_ctx, video_stream->codecpar);
if (ret < 0) {
return ret;
}

ret = avcodec_open2(ctx->decoder_ctx, decoder, NULL);
if (ret < 0) {
fprintf(stderr, "无法打开解码器: %s\n", av_err2str(ret));
return ret;
}

// 创建输出文件
ret = avformat_alloc_output_context2(&ctx->output_ctx, NULL, NULL, output_file);
if (ret < 0) {
fprintf(stderr, "无法创建输出上下文: %s\n", av_err2str(ret));
return ret;
}

// 创建编码器
const AVCodec *encoder = avcodec_find_encoder(AV_CODEC_ID_H264);
if (!encoder) {
fprintf(stderr, "未找到H.264编码器\n");
return AVERROR_ENCODER_NOT_FOUND;
}

ctx->encoder_ctx = avcodec_alloc_context3(encoder);
if (!ctx->encoder_ctx) {
return AVERROR(ENOMEM);
}

// 设置编码器参数
ctx->encoder_ctx->width = ctx->decoder_ctx->width;
ctx->encoder_ctx->height = ctx->decoder_ctx->height;
ctx->encoder_ctx->time_base = (AVRational){1, 25};
ctx->encoder_ctx->framerate = (AVRational){25, 1};
ctx->encoder_ctx->pix_fmt = AV_PIX_FMT_YUV420P;
ctx->encoder_ctx->bit_rate = 2000000;

if (ctx->output_ctx->oformat->flags & AVFMT_GLOBALHEADER)
ctx->encoder_ctx->flags |= AV_CODEC_FLAG_GLOBAL_HEADER;

ret = avcodec_open2(ctx->encoder_ctx, encoder, NULL);
if (ret < 0) {
fprintf(stderr, "无法打开编码器: %s\n", av_err2str(ret));
return ret;
}

// 创建输出流
AVStream *output_stream = avformat_new_stream(ctx->output_ctx, NULL);
if (!output_stream) {
return AVERROR(ENOMEM);
}

ret = avcodec_parameters_from_context(output_stream->codecpar, ctx->encoder_ctx);
if (ret < 0) {
return ret;
}

output_stream->time_base = ctx->encoder_ctx->time_base;

// 打开输出文件
if (!(ctx->output_ctx->oformat->flags & AVFMT_NOFILE)) {
ret = avio_open(&ctx->output_ctx->pb, output_file, AVIO_FLAG_WRITE);
if (ret < 0) {
fprintf(stderr, "无法打开输出文件: %s\n", av_err2str(ret));
return ret;
}
}

ret = avformat_write_header(ctx->output_ctx, NULL);
if (ret < 0) {
fprintf(stderr, "写入文件头失败: %s\n", av_err2str(ret));
return ret;
}

// 分配帧和包
ctx->frame = av_frame_alloc();
ctx->packet = av_packet_alloc();
if (!ctx->frame || !ctx->packet) {
return AVERROR(ENOMEM);
}

return 0;
}

int transcode_frame(TranscodeContext *ctx) {
int ret;

// 读取包
ret = av_read_frame(ctx->input_ctx, ctx->packet);
if (ret < 0) {
return ret;
}

if (ctx->packet->stream_index != ctx->video_stream_index) {
av_packet_unref(ctx->packet);
return 0;
}

// 解码
ret = avcodec_send_packet(ctx->decoder_ctx, ctx->packet);
if (ret < 0) {
av_packet_unref(ctx->packet);
return ret;
}

while (ret >= 0) {
ret = avcodec_receive_frame(ctx->decoder_ctx, ctx->frame);
if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) {
break;
} else if (ret < 0) {
av_packet_unref(ctx->packet);
return ret;
}

// 编码
ret = avcodec_send_frame(ctx->encoder_ctx, ctx->frame);
if (ret < 0) {
av_frame_unref(ctx->frame);
av_packet_unref(ctx->packet);
return ret;
}

while (ret >= 0) {
ret = avcodec_receive_packet(ctx->encoder_ctx, ctx->packet);
if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) {
break;
} else if (ret < 0) {
av_frame_unref(ctx->frame);
av_packet_unref(ctx->packet);
return ret;
}

// 写入输出文件
av_packet_rescale_ts(ctx->packet, ctx->encoder_ctx->time_base,
ctx->output_ctx->streams[0]->time_base);
ctx->packet->stream_index = 0;

ret = av_interleaved_write_frame(ctx->output_ctx, ctx->packet);
if (ret < 0) {
av_frame_unref(ctx->frame);
av_packet_unref(ctx->packet);
return ret;
}

av_packet_unref(ctx->packet);
}

av_frame_unref(ctx->frame);
}

av_packet_unref(ctx->packet);
return 0;
}

void cleanup_transcoder(TranscodeContext *ctx) {
if (ctx->output_ctx) {
av_write_trailer(ctx->output_ctx);
if (!(ctx->output_ctx->oformat->flags & AVFMT_NOFILE))
avio_closep(&ctx->output_ctx->pb);
avformat_free_context(ctx->output_ctx);
}

av_frame_free(&ctx->frame);
av_packet_free(&ctx->packet);
avcodec_free_context(&ctx->decoder_ctx);
avcodec_free_context(&ctx->encoder_ctx);
avformat_close_input(&ctx->input_ctx);
}

int main(int argc, char *argv[]) {
if (argc != 3) {
fprintf(stderr, "用法: %s <输入文件> <输出文件>\n", argv[0]);
return 1;
}

TranscodeContext ctx;
int ret;

ret = init_transcoder(&ctx, argv[1], argv[2]);
if (ret < 0) {
cleanup_transcoder(&ctx);
return 1;
}

printf("开始转码...\n");

while ((ret = transcode_frame(&ctx)) >= 0) {
// 转码进行中
}

if (ret == AVERROR_EOF) {
printf("转码完成\n");
ret = 0;
} else {
fprintf(stderr, "转码错误: %s\n", av_err2str(ret));
}

cleanup_transcoder(&ctx);
return ret;
}

5.2 音频重采样示例

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
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
#include <libswresample/swresample.h>
#include <libavutil/opt.h>
#include <libavutil/channel_layout.h>
#include <libavutil/samplefmt.h>

typedef struct AudioResampler {
SwrContext *swr_ctx;
AVFrame *input_frame;
AVFrame *output_frame;

// 输入参数
int input_sample_rate;
enum AVSampleFormat input_sample_fmt;
uint64_t input_channel_layout;
int input_channels;

// 输出参数
int output_sample_rate;
enum AVSampleFormat output_sample_fmt;
uint64_t output_channel_layout;
int output_channels;

// 缓冲区
uint8_t **input_data;
uint8_t **output_data;
int input_linesize;
int output_linesize;
int max_input_samples;
int max_output_samples;
} AudioResampler;

int init_audio_resampler(AudioResampler *resampler,
int input_sample_rate, enum AVSampleFormat input_sample_fmt, int input_channels,
int output_sample_rate, enum AVSampleFormat output_sample_fmt, int output_channels) {
int ret;

memset(resampler, 0, sizeof(*resampler));

// 设置参数
resampler->input_sample_rate = input_sample_rate;
resampler->input_sample_fmt = input_sample_fmt;
resampler->input_channels = input_channels;
resampler->input_channel_layout = av_get_default_channel_layout(input_channels);

resampler->output_sample_rate = output_sample_rate;
resampler->output_sample_fmt = output_sample_fmt;
resampler->output_channels = output_channels;
resampler->output_channel_layout = av_get_default_channel_layout(output_channels);

// 创建重采样上下文
resampler->swr_ctx = swr_alloc();
if (!resampler->swr_ctx) {
fprintf(stderr, "无法分配重采样上下文\n");
return AVERROR(ENOMEM);
}

// 设置重采样参数
av_opt_set_int(resampler->swr_ctx, "in_channel_layout", resampler->input_channel_layout, 0);
av_opt_set_int(resampler->swr_ctx, "in_sample_rate", resampler->input_sample_rate, 0);
av_opt_set_sample_fmt(resampler->swr_ctx, "in_sample_fmt", resampler->input_sample_fmt, 0);

av_opt_set_int(resampler->swr_ctx, "out_channel_layout", resampler->output_channel_layout, 0);
av_opt_set_int(resampler->swr_ctx, "out_sample_rate", resampler->output_sample_rate, 0);
av_opt_set_sample_fmt(resampler->swr_ctx, "out_sample_fmt", resampler->output_sample_fmt, 0);

// 初始化重采样上下文
ret = swr_init(resampler->swr_ctx);
if (ret < 0) {
fprintf(stderr, "无法初始化重采样上下文: %s\n", av_err2str(ret));
return ret;
}

// 分配输入输出缓冲区
resampler->max_input_samples = 1024;
resampler->max_output_samples = av_rescale_rnd(resampler->max_input_samples,
resampler->output_sample_rate,
resampler->input_sample_rate,
AV_ROUND_UP);

ret = av_samples_alloc_array_and_samples(&resampler->input_data, &resampler->input_linesize,
resampler->input_channels, resampler->max_input_samples,
resampler->input_sample_fmt, 0);
if (ret < 0) {
fprintf(stderr, "无法分配输入缓冲区: %s\n", av_err2str(ret));
return ret;
}

ret = av_samples_alloc_array_and_samples(&resampler->output_data, &resampler->output_linesize,
resampler->output_channels, resampler->max_output_samples,
resampler->output_sample_fmt, 0);
if (ret < 0) {
fprintf(stderr, "无法分配输出缓冲区: %s\n", av_err2str(ret));
return ret;
}

return 0;
}

int resample_audio(AudioResampler *resampler, const uint8_t **input_data, int input_samples,
uint8_t **output_data, int *output_samples) {
int ret;

// 计算输出样本数
int max_output_samples = av_rescale_rnd(swr_get_delay(resampler->swr_ctx, resampler->input_sample_rate) + input_samples,
resampler->output_sample_rate, resampler->input_sample_rate, AV_ROUND_UP);

if (max_output_samples > resampler->max_output_samples) {
// 重新分配输出缓冲区
av_freep(&resampler->output_data[0]);
av_freep(&resampler->output_data);

ret = av_samples_alloc_array_and_samples(&resampler->output_data, &resampler->output_linesize,
resampler->output_channels, max_output_samples,
resampler->output_sample_fmt, 0);
if (ret < 0) {
fprintf(stderr, "无法重新分配输出缓冲区: %s\n", av_err2str(ret));
return ret;
}

resampler->max_output_samples = max_output_samples;
}

// 执行重采样
ret = swr_convert(resampler->swr_ctx, resampler->output_data, max_output_samples,
input_data, input_samples);
if (ret < 0) {
fprintf(stderr, "重采样失败: %s\n", av_err2str(ret));
return ret;
}

*output_samples = ret;
*output_data = resampler->output_data[0];

return 0;
}

void cleanup_audio_resampler(AudioResampler *resampler) {
if (resampler->input_data) {
av_freep(&resampler->input_data[0]);
av_freep(&resampler->input_data);
}

if (resampler->output_data) {
av_freep(&resampler->output_data[0]);
av_freep(&resampler->output_data);
}

swr_free(&resampler->swr_ctx);
}

// 使用示例
int main() {
AudioResampler resampler;
int ret;

// 初始化重采样器:44.1kHz 立体声 -> 48kHz 单声道
ret = init_audio_resampler(&resampler,
44100, AV_SAMPLE_FMT_S16, 2, // 输入
48000, AV_SAMPLE_FMT_S16, 1); // 输出
if (ret < 0) {
return 1;
}

// 模拟输入数据
int input_samples = 1024;
int16_t *input_buffer = (int16_t*)resampler.input_data[0];

// 生成测试音频数据(正弦波)
for (int i = 0; i < input_samples * 2; i += 2) {
double t = (double)i / (44100.0 * 2);
int16_t sample = (int16_t)(sin(2 * M_PI * 440 * t) * 16384); // 440Hz正弦波
input_buffer[i] = sample; // 左声道
input_buffer[i + 1] = sample; // 右声道
}

// 执行重采样
uint8_t *output_data;
int output_samples;

ret = resample_audio(&resampler, (const uint8_t**)resampler.input_data, input_samples,
&output_data, &output_samples);
if (ret < 0) {
cleanup_audio_resampler(&resampler);
return 1;
}

printf("重采样完成: %d 输入样本 -> %d 输出样本\n", input_samples, output_samples);

cleanup_audio_resampler(&resampler);
return 0;
}

6. 性能优化与最佳实践

6.1 性能优化技巧

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
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
// 1. 内存池管理
typedef struct MemoryPool {
AVFrame **frames;
AVPacket **packets;
int frame_count;
int packet_count;
int frame_index;
int packet_index;
} MemoryPool;

MemoryPool* create_memory_pool(int frame_count, int packet_count) {
MemoryPool *pool = av_mallocz(sizeof(MemoryPool));
if (!pool) return NULL;

pool->frames = av_mallocz_array(frame_count, sizeof(AVFrame*));
pool->packets = av_mallocz_array(packet_count, sizeof(AVPacket*));

for (int i = 0; i < frame_count; i++) {
pool->frames[i] = av_frame_alloc();
}

for (int i = 0; i < packet_count; i++) {
pool->packets[i] = av_packet_alloc();
}

pool->frame_count = frame_count;
pool->packet_count = packet_count;

return pool;
}

AVFrame* get_frame_from_pool(MemoryPool *pool) {
if (pool->frame_index >= pool->frame_count) {
pool->frame_index = 0;
}

AVFrame *frame = pool->frames[pool->frame_index++];
av_frame_unref(frame);
return frame;
}

// 2. 多线程编码
typedef struct ThreadContext {
AVCodecContext *codec_ctx;
AVFrame *frame;
AVPacket *packet;
int thread_id;
pthread_mutex_t mutex;
pthread_cond_t cond;
int frame_ready;
int stop;
} ThreadContext;

void* encode_thread(void *arg) {
ThreadContext *ctx = (ThreadContext*)arg;

while (!ctx->stop) {
pthread_mutex_lock(&ctx->mutex);
while (!ctx->frame_ready && !ctx->stop) {
pthread_cond_wait(&ctx->cond, &ctx->mutex);
}

if (ctx->stop) {
pthread_mutex_unlock(&ctx->mutex);
break;
}

// 编码帧
int ret = avcodec_send_frame(ctx->codec_ctx, ctx->frame);
if (ret >= 0) {
while (ret >= 0) {
ret = avcodec_receive_packet(ctx->codec_ctx, ctx->packet);
if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) {
break;
}

if (ret >= 0) {
// 处理编码后的包
process_encoded_packet(ctx->packet);
av_packet_unref(ctx->packet);
}
}
}

ctx->frame_ready = 0;
pthread_mutex_unlock(&ctx->mutex);
}

return NULL;
}

// 3. 硬件加速
int setup_hw_decoder(AVCodecContext *ctx, enum AVHWDeviceType type) {
int ret;

// 创建硬件设备上下文
AVBufferRef *hw_device_ctx = NULL;
ret = av_hwdevice_ctx_create(&hw_device_ctx, type, NULL, NULL, 0);
if (ret < 0) {
fprintf(stderr, "创建硬件设备上下文失败: %s\n", av_err2str(ret));
return ret;
}

ctx->hw_device_ctx = av_buffer_ref(hw_device_ctx);
av_buffer_unref(&hw_device_ctx);

return 0;
}

int setup_hw_encoder(AVCodecContext *ctx, enum AVHWDeviceType type) {
int ret;

// 查找硬件像素格式
for (int i = 0;; i++) {
const AVCodecHWConfig *config = avcodec_get_hw_config(ctx->codec, i);
if (!config) {
fprintf(stderr, "编码器不支持硬件加速\n");
return AVERROR(ENOSYS);
}

if (config->methods & AV_CODEC_HW_CONFIG_METHOD_HW_DEVICE_CTX &&
config->device_type == type) {
ctx->pix_fmt = config->pix_fmt;
break;
}
}

return setup_hw_decoder(ctx, type);
}

6.2 错误处理与调试

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
// 错误处理宏
#define CHECK_RET(ret, msg) do { \
if (ret < 0) { \
fprintf(stderr, "%s: %s\n", msg, av_err2str(ret)); \
goto cleanup; \
} \
} while(0)

// 日志回调
void log_callback(void *ptr, int level, const char *fmt, va_list vl) {
static char line[1024];
static int print_prefix = 1;

av_log_format_line(ptr, level, fmt, vl, line, sizeof(line), &print_prefix);

// 根据级别输出到不同位置
switch (level) {
case AV_LOG_ERROR:
case AV_LOG_FATAL:
fprintf(stderr, "[ERROR] %s", line);
break;
case AV_LOG_WARNING:
fprintf(stderr, "[WARN] %s", line);
break;
case AV_LOG_INFO:
printf("[INFO] %s", line);
break;
case AV_LOG_DEBUG:
printf("[DEBUG] %s", line);
break;
}
}

// 性能监控
typedef struct PerformanceMonitor {
int64_t start_time;
int64_t total_frames;
int64_t total_bytes;
double avg_fps;
double avg_bitrate;
} PerformanceMonitor;

void init_performance_monitor(PerformanceMonitor *monitor) {
memset(monitor, 0, sizeof(*monitor));
monitor->start_time = av_gettime();
}

void update_performance_monitor(PerformanceMonitor *monitor, int frame_size) {
monitor->total_frames++;
monitor->total_bytes += frame_size;

int64_t elapsed = av_gettime() - monitor->start_time;
if (elapsed > 0) {
monitor->avg_fps = (double)monitor->total_frames * 1000000 / elapsed;
monitor->avg_bitrate = (double)monitor->total_bytes * 8 * 1000000 / elapsed;
}
}

void print_performance_stats(PerformanceMonitor *monitor) {
printf("性能统计:\n");
printf(" 总帧数: %ld\n", monitor->total_frames);
printf(" 总字节数: %ld\n", monitor->total_bytes);
printf(" 平均帧率: %.2f fps\n", monitor->avg_fps);
printf(" 平均比特率: %.2f bps\n", monitor->avg_bitrate);
}

总结

FFmpeg作为开源音视频处理领域的瑞士军刀,具有以下特点:

架构优势:

  • 模块化设计,各组件职责清晰
  • 丰富的API接口,支持各种应用场景
  • 插件化的编解码器和格式支持
  • 强大的滤镜系统,支持复杂的音视频处理

功能特性:

  • 支持几乎所有主流音视频格式和编解码器
  • 强大的命令行工具,满足日常音视频处理需求
  • 丰富的API接口,支持二次开发
  • 跨平台支持,在各种操作系统上运行稳定

应用场景:

  • 视频播放器开发(VLC、MPlayer等)
  • 视频转码服务(云端转码、格式转换)
  • 直播系统(推流、拉流、转码)
  • 音视频编辑软件(剪辑、特效处理)
  • 监控系统(录像、回放、格式转换)

开发建议:

  • 深入理解FFmpeg的架构和数据流
  • 合理使用内存管理,避免内存泄漏
  • 充分利用硬件加速能力
  • 注意多线程安全和性能优化
  • 及时更新版本,获得最新特性和安全修复

FFmpeg的强大功能和灵活性使其成为音视频开发的首选框架。通过深入学习其架构设计和API使用,可以构建出高性能、稳定可靠的音视频应用。在实际项目中,需要根据具体需求选择合适的组件和优化策略,充分发挥FFmpeg的潜力。

版权所有,如有侵权请联系我