FFmpeg 是一个功能强大的开源音视频处理工具集,其核心代码以 C 语言实现。下面从源码角度分析 FFmpeg 如何实现转码、压缩、提取、截取、拼接、合并和录屏等功能:
FFmpeg 的源码结构围绕以下核心组件展开:
关键数据结构包括:
转码是将输入媒体流解码后重新编码为另一种格式的过程。核心流程在ffmpeg.c的transcode()函数中:
// ffmpeg.c: transcode() 简化版流程
static int transcode(void) {
// 1. 打开输入文件并读取流信息
if (open_input_file(ifile) < 0) exit_program(1);
// 2. 打开输出文件并创建输出流
if (open_output_file(ofile) < 0) exit_program(1);
// 3. 主循环:读取输入包 -> 解码 -> 编码 -> 写入输出
while (!received_sigterm) {
// 从输入文件读取一个AVPacket
ret = get_input_packet(ifile, &pkt);
// 找到对应的解码器并解码为AVFrame
ret = decode(ist->dec_ctx, ist->frame, &got_frame, &pkt);
// 处理解码后的AVFrame(可能需要滤镜处理)
if (got_frame) {
// 转换帧格式(如像素格式、采样率等)
filter_frame(ist, ist->filter_frame);
// 找到对应的编码器并编码为AVPacket
ret = encode(ost->enc_ctx, &pkt_out, frame, &got_packet);
// 将编码后的AVPacket写入输出文件
if (got_packet) write_packet(ofile, &pkt_out, ost);
}
}
// 4. 清理资源
close_input_file(ifile);
close_output_file(ofile);
return 0;
}
压缩本质是通过编码器控制比特率。在ffmpeg.c中,编码器参数通过AVCodecContext设置:
// ffmpeg.c: open_output_file() 中设置编码器参数
static AVCodecContext *init_output_stream_encode(OutputStream *ost) {
AVCodecContext *avctx = ost->enc_ctx;
// 设置视频编码器参数
if (avctx->codec_type == AVMEDIA_TYPE_VIDEO) {
avctx->bit_rate = ost->bitrate; // 设置目标比特率
avctx->width = ost->source_width; // 分辨率
avctx->height = ost->source_height;
avctx->time_base = ost->frame_rate; // 帧率
avctx->gop_size = ost->gop_size; // I帧间隔
// ...其他参数
}
// 设置音频编码器参数
else if (avctx->codec_type == AVMEDIA_TYPE_AUDIO) {
avctx->bit_rate = ost->bitrate; // 音频比特率
avctx->sample_rate = ost->sample_rate; // 采样率
avctx->channels = avctx->codec->channels; // 声道数
// ...其他参数
}
// 打开编码器
ret = avcodec_open2(avctx, codec, &opts);
return avctx;
}
提取特定流(如仅提取音频或视频)通过禁用不需要的流实现:
// ffmpeg.c: open_input_file() 中设置流选择
static int open_input_file(InputFile *f) {
// 打开输入文件
ret = avformat_open_input(&f->ctx, filename, fmt, &format_opts);
// 读取流信息
ret = avformat_find_stream_info(f->ctx, NULL);
// 选择需要的流(如只选视频流)
for (i = 0; i < f->ctx->nb_streams; i++) {
AVStream *st = f->ctx->streams[i];
if (st->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) {
f->streams[i]->discard = 0; // 保留视频流
} else {
f->streams[i]->discard = AVDISCARD_ALL; // 丢弃其他流
}
}
return 0;
}
截取通过设置输入文件的开始时间(-ss)和持续时间(-t)实现:
// ffmpeg.c: transcode_init() 中处理时间选项
static int transcode_init(void) {
// 处理输入文件的起始时间(-ss)
if (ifile->start_time != AV_NOPTS_VALUE) {
if (ifile->ctx->start_time != AV_NOPTS_VALUE) {
ifile->ts_offset = ifile->ctx->start_time;
}
avformat_seek_file(ifile->ctx, -1, INT64_MIN,
ifile->start_time, INT64_MAX, 0);
}
// 处理持续时间(-t)
if (ofile->recording_time != INT64_MAX) {
ost->max_frames = av_rescale_q(ofile->recording_time,
AV_TIME_BASE_Q,
ost->st->time_base);
}
return 0;
}
拼接多个媒体文件通过concat协议或滤镜实现。核心是使用libavfilter的concat滤镜:
// ffmpeg_filter.c: configure_filtergraph() 中配置concat滤镜
static int configure_filtergraph(FilterGraph *fg) {
// 创建concat滤镜上下文
AVFilter *filter = avfilter_get_by_name("concat");
AVFilterContext *concat_ctx;
// 设置滤镜参数(输入流数量、是否同步等)
avfilter_graph_create_filter(&concat_ctx, filter, "concat",
options, NULL, fg->graph);
// 将多个输入流连接到concat滤镜
for (i = 0; i < nb_inputs; i++) {
avfilter_link(inputs[i]->filter, 0, concat_ctx, i);
}
// 配置滤镜图并生效
return avfilter_graph_config(fg->graph, NULL);
}
合并多个流到一个容器(如音视频合并)通过创建多个输出流实现:
// ffmpeg.c: open_output_file() 中创建输出流
static int open_output_file(OutputFile *of) {
// 创建输出格式上下文
ret = avformat_alloc_output_context2(&of->ctx, NULL, fmt, filename);
// 为每种媒体类型创建输出流
for (i = 0; i < nb_output_streams; i++) {
OutputStream *ost = output_streams[i];
AVStream *st = avformat_new_stream(of->ctx, ost->enc);
ost->st = st;
// 复制流参数(如编解码器参数)
ret = avcodec_parameters_from_context(st->codecpar, ost->enc_ctx);
}
// 打开输出文件
if (!(of->ctx->oformat->flags & AVFMT_NOFILE)) {
ret = avio_open(&of->ctx->pb, filename, AVIO_FLAG_WRITE);
}
// 写入文件头
ret = avformat_write_header(of->ctx, &opts);
return 0;
}
录屏通过libavdevice访问系统设备实现。以 Linux 的 X11 录屏为例:
// ffmpeg.c: open_input_file() 中使用avdevice打开录屏设备
static int open_input_file(InputFile *f) {
// 注册设备
avdevice_register_all();
// 打开X11录屏设备
AVInputFormat *iformat = av_find_input_format("x11grab");
ret = avformat_open_input(&f->ctx, display_name, iformat, &format_opts);
// 设置录屏参数(帧率、分辨率等)
if (f->framerate.num) {
av_dict_set(&format_opts, "framerate", av_get_time_base_q(), 0);
}
if (f->video_size) {
av_dict_set(&format_opts, "video_size", f->video_size, 0);
}
// 读取流信息
ret = avformat_find_stream_info(f->ctx, NULL);
return 0;
}
FFmpeg 使用复杂的时间戳转换机制,确保音视频同步:
libavfilter实现了强大的滤镜链处理:
FFmpeg 支持编解码多线程:
FFmpeg 通过模块化设计实现了强大的音视频处理能力:
源码中最核心的逻辑位于ffmpeg.c的transcode()函数,它串联了整个处理流程。理解 FFmpeg 的架构和数据结构是深入掌握其功能的关键。