8.2 核心开发流程:API 层面的解码与编码


8.2 核心开发流程:API 层面的解码与编码

本节摘要:SDK 开发就是把命令行翻译成 API 调用。本节给出「打开文件→找流→解码→编码→写出」的最小程序骨架,逐段讲解 avformat_open_input、avcodec_send_packet、avcodec_receive_frame 等核心 API,并对照命令行建立一一对应。

学习目标

阅读完本节,你应当能够:

  1. 说出转码程序的完整调用序列与每个环节的核心 API
  2. 理解「send/receive」推拉模型(先喂后取),说出它与命令行管线的对应
  3. 识别资源释放、缓冲区、错误码三类常见坑

一、场景代入:把第 3 章那条命令写进 C

第 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 章的「流描述」在这里变成实体。

第三步:解码(send/receive 推拉模型)

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 层找到对应的工位。

三类必踩的坑

  1. 资源释放:每个 av_packet_alloc/av_frame_alloc 都要配 free,漏一个就是泄漏;av_packet_unrefav_packet_free 是两回事,前者释放引用后者释放对象
  2. send/receive 返回码AVERROR(EAGAIN) 是「还没就绪,再试」的正常信号,不等于错误;用返回值决定循环逻辑,别只判断负数
  3. 时间戳对齐:第 1 章的理论在这里兑现——编码前确保 frame 的 pts 正确递增,否则输出音画不同步

数据流一图流

数据流一图流

图说明:一条可插入的管线

上图画出的调用序列,与第 3 章那条命令行在数据流上完全同构。差别在于:SDK 版在每个环节之间都留了「插槽」——拿 frame 之后你想干什么都行。这就是二次开发的意义:不是换一种写法,而是获得插入权。

⚠️ 常见坑:avformat_find_stream_info 忘记调用,后面拿到的流参数可能是空的;av_packet_unrefav_packet_free 混用导致 double free;解码后直接把 frame 塞给编码器,忘了重设 pts,输出时间轴错乱——这三条占了 SDK 新手报错的大半。

💡 关键直觉:把 SDK 程序想成「把命令行一条条拆开,用代码重新连起来,并在每个接头处留口子」。第 2 章的 AVPacket/AVFrame 在这里就是真实的变量,不再是概念。

要点回顾

  • 调用序列:open_input → find_stream → 解码器 → send/receive 循环 → 编码器 → write_frame
  • 推拉模型:send 喂数据、receive 取数据,EAGAIN 是正常信号
  • 对应命令行:-i 对应 avformat_open_input,解码编码对应 send/receive,copy 对应 write_frame
  • 三类坑:资源释放(alloc/free 配对)、返回码处理(EAGAIN)、时间戳对齐(pts 递增)
  • SDK 的意义:每个环节可插入自定义逻辑,这是命令行给不了的

下一节,如果 FFmpeg 没有你要的滤镜怎么办——自定义扩展的机制与取舍。


作者与出处
原作者: 灏天文库
来源:灏天文库
整理: 灏天文库整理
由灏天文库平台收录,内容或由平台用户上传,仅供学习交流
发布者: 作者: 灏天文库 转发
评论区 (0)
U