本节摘要:SDK 开发就是把命令行翻译成 API 调用。本节给出「打开文件→找流→解码→编码→写出」的最小程序骨架,逐段讲解 avformat_open_input、avcodec_send_packet、avcodec_receive_frame 等核心 API,并对照命令行建立一一对应。
阅读完本节,你应当能够:
第 3 章我们写过一条转码命令:
ffmpeg -i input.mp4 -c:v libx264 -c:a aac output.mp4
这条命令背后,是几十个 API 调用按顺序串成的一条管线。SDK 开发的第一步就是意识到:你写的不是「另一种写法」,而是把 FFmpeg 内部那条装配线亲手搭一遍。 好处是你可以在任意环节插入自己的逻辑——比如把解码出来的帧先送进 AI 模型再编码,这是命令行永远做不到的。
AVFormatContext *fmt_ctx = NULL; if (avformat_open_input(&fmt_ctx, "input.mp4", NULL, NULL) < 0) { // 打开失败 } avformat_find_stream_info(fmt_ctx, NULL); // 读取流信息
对应命令行:-i input.mp4 加 ffprobe 的侦察。
AVCodec *decoder = NULL; int video_index = av_find_best_stream(fmt_ctx, AVMEDIA_TYPE_VIDEO, -1, -1, &decoder, 0); AVStream *stream = fmt_ctx->streams[video_index]; AVCodecContext *dec_ctx = avcodec_alloc_context3(decoder); avcodec_parameters_to_context(dec_ctx, stream->codecpar); avcodec_open2(dec_ctx, decoder, NULL);
核心动作:从容器里挑出视频流,为它建一个解码器上下文并打开。codecpar 是容器里记录的编码参数,第 2 章的「流描述」在这里变成实体。
AVPacket *pkt = av_packet_alloc(); AVFrame *frame = av_frame_alloc(); while (av_read_frame(fmt_ctx, pkt) >= 0) { if (pkt->stream_index == video_index) { // 把压缩包喂给解码器 avcodec_send_packet(dec_ctx, pkt); // 从解码器取出原始帧(可能一包出多帧) while (avcodec_receive_frame(dec_ctx, frame) >= 0) { // 这里拿到 frame,可以送滤镜、送 AI、或直接送编码器 } } av_packet_unref(pkt); }
send/receive 模型是本节最需要吃透的概念:解码器是一个有状态的机器,你「喂」压缩包(send_packet),它吐出原始帧(receive_frame)。一次 send 可能触发多次 receive(一包出多帧),也可能暂时吐不出来(数据不够)。所以必须 while 循环把能吐的都取干净。
编码是解码的逆过程,同样用 send/receive:
AVCodecContext *enc_ctx = ...; // 配置编码器(编码器、CRF 等价参数) while (avcodec_send_frame(enc_ctx, frame) >= 0) { while (avcodec_receive_packet(enc_ctx, pkt) >= 0) { // 把编码后的包写进输出文件 av_interleaved_write_frame(out_fmt_ctx, pkt); av_packet_unref(pkt); } }
av_frame_free(&frame); av_packet_free(&pkt); avcodec_free_context(&dec_ctx); avformat_close_input(&fmt_ctx);
内存泄漏是 SDK 开发第一杀手,每个 alloc 都要配一个 free。
| 命令行 | SDK API | 干什么 |
|---|---|---|
-i input.mp4 |
avformat_open_input | 打开容器 |
| 流侦察 | avformat_find_stream_info | 读流信息 |
| 选择流 | av_find_best_stream | 挑出目标流 |
| 解码 | avcodec_send_packet / receive_frame | 压缩包变原始帧 |
| 滤镜 | 滤镜图 API | 加工帧(第 4 章) |
| 编码 | avcodec_send_frame / receive_packet | 原始帧变压缩包 |
-c copy |
av_interleaved_write_frame | 写出 |
这张表是「命令行功底」到「SDK 能力」的转换桥:你熟的命令,都能在 API 层找到对应的工位。
av_packet_unref 与 av_packet_free 是两回事,前者释放引用后者释放对象AVERROR(EAGAIN) 是「还没就绪,再试」的正常信号,不等于错误;用返回值决定循环逻辑,别只判断负数
上图画出的调用序列,与第 3 章那条命令行在数据流上完全同构。差别在于:SDK 版在每个环节之间都留了「插槽」——拿 frame 之后你想干什么都行。这就是二次开发的意义:不是换一种写法,而是获得插入权。
⚠️ 常见坑:
avformat_find_stream_info忘记调用,后面拿到的流参数可能是空的;av_packet_unref和av_packet_free混用导致 double free;解码后直接把 frame 塞给编码器,忘了重设 pts,输出时间轴错乱——这三条占了 SDK 新手报错的大半。
💡 关键直觉:把 SDK 程序想成「把命令行一条条拆开,用代码重新连起来,并在每个接头处留口子」。第 2 章的 AVPacket/AVFrame 在这里就是真实的变量,不再是概念。
下一节,如果 FFmpeg 没有你要的滤镜怎么办——自定义扩展的机制与取舍。