FFmpeg8.0.1 源代码的深入分析

执行命令 -i input.mp4 -vframes 1 -f image2 -y output.jpg

🎯 完整调用链概览

main()

├── 初始化阶段

│ ├── init_dynload()

│ ├── parse_loglevel()

│ ├── avdevice_register_all()

│ ├── avformat_network_init()

│ ├── show_banner()

│ └── sch_alloc()

├── 参数解析阶段 [ffmpeg_parse_options()]

│ ├── split_commandline() - 分割命令行参数

│ ├── parse_optgroup() - 解析全局选项

│ ├── term_init() - 初始化终端

│ ├── open_files(..., ifile_open) - 打开输入文件

│ └── open_files(..., of_open) - 打开输出文件

└── 转码执行阶段 [transcode()]

├── sch_start() - 启动调度器

├── sch_wait() - 等待处理完成

├── sch_stop() - 停止调度器

└── of_write_trailer() - 写入尾部

📋 分阶段详细调用链

  1. 程序入口 (fftools/ffmpeg.c:964)

    main(int argc, char **argv)

    ├── init_dynload() // 初始化动态加载

    ├── setvbuf(stderr, ...) // 设置stderr缓冲

    ├── av_log_set_flags(AV_LOG_SKIP_REPEATED)

    ├── parse_loglevel(argc, argv, options) // 解析日志级别

    ├── avdevice_register_all() // 注册所有设备 [CONFIG_AVDEVICE]

    ├── avformat_network_init() // 初始化网络

    ├── show_banner(argc, argv, options) // 显示版本信息 Banner

    └── sch_alloc() // 分配调度器

  2. 参数解析阶段 (fftools/ffmpeg_opt.c:1380)

    ffmpeg_parse_options(argc, argv, sch)

    ├── split_commandline(&octx, argc, argv, ...)

    │ // 将命令行参数分割为内部表示

    │ // 识别: -i input.mp4 -vframes 1 -f image2 -y output.jpg

    ├── parse_optgroup(&go, &octx.global_opts, options)

    │ // 解析全局选项 (如 -y 覆盖输出)

    ├── term_init()

    │ // 配置终端和信号处理

    ├── open_files(&octx.groups[GROUP_INFILE], "input", sch, ifile_open)

    │ // 打开所有输入文件 ↓↓↓

    └── open_files(&octx.groups[GROUP_OUTFILE], "output", sch, of_open)

    // 打开所有输出文件 ↓↓↓

  3. 输入文件打开 (fftools/ffmpeg_demux.c:1656)

    ifile_open(o, filename="/Users/.../input.mp4", sch)

    ├── demux_alloc() // 分配解复用器

    ├── sch_add_demux(sch, input_thread, d) // 添加解复用线程

    ├── av_find_input_format(o->format) // 查找输入格式 (NULL时自动检测)

    ├── avformat_alloc_context() // 分配格式上下文

    │ └── ic = AVFormatContext* // 创建输入上下文

    ├── avformat_open_input(&ic, filename, file_iformat, &format_opts)

    │ // ★★★ 打开输入文件,探测格式 ★★★

    │ │

    │ └── [libavformat/demux.c] avformat_open_input()

    │ ├── init_input() // 初始化输入

    │ │ ├── av_probe_input_format() // 探测文件格式

    │ │ └── avio_open2() // 打开IO

    │ ├── s->iformat->read_header() // 读取文件头 (mov_read_header for MP4)

    │ └── avformat_find_stream_info() // 查找流信息

    ├── avformat_find_stream_info(ic, ...)

    │ // ★★★ 分析流信息,读取若干帧以确定编码参数 ★★★

    │ │

    │ └── [libavformat/demux.c]

    │ ├── av_read_frame() // 读取帧

    │ ├── try_decode_frame() // 尝试解码帧

    │ └── estimate_timings() // 估算时长

    └── ist_add() // 添加输入流

    └── dec_create() // 创建解码器

    └── dec_open() // 打开解码器

    └── avcodec_open2() // 打开编解码器

关键 libavformat 函数:

avformat_open_input() (libavformat/demux.c) - 打开文件并探测格式

avformat_find_stream_info() - 分析流参数

格式特定函数如 mov_read_header() (libavformat/mov.c) - 读取 MP4 文件头

  1. 输出文件打开 (fftools/ffmpeg_mux_init.c:3262)

of_open(o, filename="/Users/.../xx.jpg", sch)

├── mux_alloc() // 分配复用器

├── avformat_alloc_output_context2(&oc, NULL, o->format="image2", filename)

│ // ★★★ 分配输出上下文,根据 -f image2 和文件名确定格式 ★★★

│ │

│ └── [libavformat/mux.c]

│ ├── av_guess_format("image2", filename, NULL)

│ │ // 根据格式名或文件扩展名猜测输出格式

│ └── avformat_alloc_context()

├── sch_add_mux(sch, muxer_thread, ...)

│ // 添加复用线程到调度器

├── create_streams(mux, o) // 创建输出流

│ └── new_output_stream(mux, o, AVMEDIA_TYPE_VIDEO, ...)

│ ├── ost_add() // 添加输出流

│ └── enc_open() // 打开编码器

│ ├── avcodec_find_encoder() // 查找编码器 (mjpeg)

│ ├── avcodec_alloc_context3()// 分配编码器上下文

│ └── avcodec_open2() // 打开编码器

│ └── [libavcodec/encode.c] ff_encode_open()

└── avio_open2(&oc->pb, filename, AVIO_FLAG_WRITE, ...)

// ★★★ 打开输出文件进行写入 ★★★

关键编码器:

查找到的编码器: mjpeg (Motion JPEG)

编码器实现: libavcodec/mjpegenc.c

  1. 转码主循环 (fftools/ffmpeg.c:870)

transcode(sch)

├── print_stream_maps() // 打印流映射信息

├── sch_start(sch) // ★★★ 启动调度器,开始所有线程 ★★★

│ │

│ ├── 启动 input_thread() 线程 ↓ // 解复用线程

│ ├── 启动 decoder_thread() 线程 ↓ // 解码线程

│ ├── 启动 encoder_thread() 线程 ↓ // 编码线程

│ └── 启动 muxer_thread() 线程 ↓ // 复用线程

├── while (!sch_wait(sch, ...))

│ ├── check_keyboard_interaction() // 检查键盘输入 (q退出)

│ └── print_report() // 打印进度报告

├── sch_stop(sch, &transcode_ts) // 停止调度器

└── of_write_trailer() // 写入文件尾部

└── av_write_trailer(oc)

  1. 解复用线程 (fftools/ffmpeg_demux.c:717)

    input_thread(void *arg) // 在独立线程中运行

    ├── demux_thread_init(&dt) // 初始化线程上下文

    ├── while (1) { // 主循环

    │ │

    │ ├── av_read_frame(f->ctx, dt.pkt_demux)

    │ │ // ★★★ 从输入文件读取一个数据包 ★★★

    │ │ │

    │ │ └── [libavformat/demux.c] av_read_frame()

    │ │ ├── ff_read_packet() // 读取原始包

    │ │ │ └── s->iformat->read_packet() // 格式特定读取 (mov)

    │ │ └── parse_packet() // 解析数据包

    │ │

    │ ├── input_packet_process(d, pkt, ...)

    │ │ // 处理输入包 (时间戳调整等)

    │ │

    │ └── demux_send(d, &dt, ds, pkt, ...)

    │ └── sch_demux_send() // 发送包到调度器

    │ }

    └── demux_thread_uninit(&dt)

  2. 解码流程 (fftools/ffmpeg_dec.c)

    decoder_thread(void *arg) // 在独立线程中运行

    ├── while (接收数据包) {

    │ │

    │ ├── sch_dec_receive() // 从调度器接收包

    │ │

    │ ├── dec_packet(...)

    │ │ ├── packet_decode(d, pkt, frame)

    │ │ │ │

    │ │ │ ├── avcodec_send_packet(dec, pkt)

    │ │ │ │ // ★★★ 发送压缩包给解码器 ★★★

    │ │ │ │ │

    │ │ │ │ └── [libavcodec/decode.c] avcodec_send_packet()

    │ │ │ │ └── ff_decode_preinit()

    │ │ │ │

    │ │ │ └── avcodec_receive_frame(dec, frame)

    │ │ │ // ★★★ 从解码器接收解码后的帧 ★★★

    │ │ │ │

    │ │ │ └── [libavcodec/decode.c] avcodec_receive_frame()

    │ │ │ ├── decode_simple_internal()

    │ │ │ │ └── codec->cb.decode() // H.264解码器

    │ │ │ │ └── [libavcodec/h264dec.c] h264_decode_frame()

    │ │ │ └── av_frame_ref()

    │ │ │

    │ │ └── sch_dec_send() // 发送解码后的帧

    │ }

    └── 对于 -vframes 1: 解码第1帧后退出

关键解码器:

H.264 解码器: libavcodec/h264dec.c

核心函数: h264_decode_frame()

  1. 编码流程 (fftools/ffmpeg_enc.c)

encoder_thread(void *arg) // 在独立线程中运行

├── while (接收帧) {

│ │

│ ├── sch_enc_receive() // 从调度器接收帧

│ │

│ ├── enc_frame(enc, frame)

│ │ ├── do_video_out(e, frame, ...)

│ │ │ │

│ │ │ ├── avcodec_send_frame(enc, frame)

│ │ │ │ // ★★★ 发送原始帧给编码器 ★★★

│ │ │ │ │

│ │ │ │ └── [libavcodec/encode.c] avcodec_send_frame()

│ │ │ │ └── ff_encode_preinit()

│ │ │ │

│ │ │ └── avcodec_receive_packet(enc, pkt)

│ │ │ // ★★★ 从编码器接收编码后的包 ★★★

│ │ │ │

│ │ │ └── [libavcodec/encode.c] avcodec_receive_packet()

│ │ │ ├── encode_simple_internal()

│ │ │ │ └── codec->cb.encode() // MJPEG编码器

│ │ │ │ └── [libavcodec/mjpegenc.c] ff_mpv_encode_picture()

│ │ │ └── av_packet_ref()

│ │ │

│ │ └── sch_enc_send() // 发送编码后的包

│ }

└── 对于 -vframes 1: 编码1帧后发送EOF

关键编码器:

MJPEG 编码器: libavcodec/mjpegenc.c

核心函数: ff_mpv_encode_picture()

  1. 复用线程 (fftools/ffmpeg_mux.c:407)

muxer_thread(void *arg) // 在独立线程中运行

├── mux_thread_init(&mt) // 初始化复用线程

├── while (1) {

│ │

│ ├── sch_mux_receive(mux->sch, ...) // 从调度器接收编码后的包

│ │

│ ├── mux_packet_filter(mux, &mt, ost, pkt, ...)

│ │ ├── of_streamcopy() // 流复制处理

│ │ └── write_packet(of, pkt)

│ │ ├── of_check_init(of)

│ │ │ └── avformat_write_header(s, ...)

│ │ │ // ★★★ 写入文件头 (仅首次调用) ★★★

│ │ │ │

│ │ │ └── [libavformat/mux.c] avformat_write_header()

│ │ │ └── s->oformat->write_header()

│ │ │ └── [libavformat/img2enc.c] write_header()

│ │ │

│ │ └── av_interleaved_write_frame(s, pkt)

│ │ // ★★★ 写入数据帧 ★★★

│ │ │

│ │ └── [libavformat/mux.c] av_interleaved_write_frame()

│ │ ├── interleaved_write_packet()

│ │ │ └── s->oformat->write_packet()

│ │ │ └── [libavformat/img2enc.c] write_packet()

│ │ └── avio_write() // 实际IO写入

│ }

└── mux_thread_uninit(&mt)

关键复用器:

image2 复用器: libavformat/img2enc.c

核心函数: write_packet() - 将JPEG数据写入文件

  1. 清理和退出

transcode() 返回后

├── ffmpeg_cleanup(ret) // 清理资源

│ ├── 关闭所有输入文件

│ ├── 关闭所有输出文件

│ ├── 释放编解码器上下文

│ └── 释放格式上下文

├── sch_free(&sch) // 释放调度器

└── return ret // 退出程序

🔑 核心库函数调用链

libavformat (格式处理)

输入: avformat_open_input() → avformat_find_stream_info() → av_read_frame()

输出: avformat_alloc_output_context2() → avformat_write_header() → av_interleaved_write_frame() → av_write_trailer()

libavcodec (编解码)

解码: avcodec_find_decoder() → avcodec_open2() → avcodec_send_packet() → avcodec_receive_frame()

编码: avcodec_find_encoder() → avcodec_open2() → avcodec_send_frame() → avcodec_receive_packet()

libavutil (工具函数)

内存: av_malloc(), av_free(), av_packet_alloc(), av_frame_alloc()

日志: av_log()

时间: av_gettime_relative(), av_rescale_q()

📊 数据流向

输入文件 (MP4)

↓ av_read_frame()

H.264 编码包

↓ avcodec_send_packet() / avcodec_receive_frame()

YUV420P 原始帧 (960x1280)

↓ avcodec_send_frame() / avcodec_receive_packet()

MJPEG 编码包

↓ av_interleaved_write_frame()

输出文件 (JPEG)

⚙️ 线程模型

FFmpeg 使用调度器 (Scheduler) 管理多个工作线程:

input_thread - 读取输入文件并解复用

decoder_thread - 解码 H.264 → YUV

encoder_thread - 编码 YUV → MJPEG

muxer_thread - 写入输出文件

线程间通过调度器队列传递数据包和帧

相关推荐
aqi005 小时前
FFmpeg开发笔记(九十八)基于FFmpeg的跨平台图形用户界面LosslessCut
android·ffmpeg·kotlin·音视频·直播·流媒体
带土11 天前
2. Linux下FFmpeg C++音视频解码+推流开发
linux·c++·ffmpeg
aqi001 天前
FFmpeg开发笔记(九十七)国产的开源视频剪辑工具AndroidVideoEditor
android·ffmpeg·音视频·直播·流媒体
Sleepless_斑马1 天前
RTMP/RTSP流媒体服务器搭建、ffmpeg推流桌面、vlc拉流
ffmpeg·rtmp·rtsp
炼金术1 天前
SkyPlayer v1.1.0 - 在线视频播放功能更新
android·ffmpeg
喜欢吃豆1 天前
深度解析:FFmpeg 远程流式解复用原理与工程实践
人工智能·架构·ffmpeg·大模型·音视频·多模态
带土11 天前
1. FFmpeg入门
ffmpeg
Lueeee.1 天前
1.广告机项目-----ffmpeg播放准备
ffmpeg
心动啊1212 天前
FFMPeg在Python中的使用
ffmpeg
aqi002 天前
FFmpeg开发笔记(一百)国产的Android开源视频压缩工具VideoSlimmer
android·ffmpeg·音视频·直播·流媒体