FFmpeg 新编码 API 完全指南:avcodec_send_frame / avcodec_receive_packet

目录

[一、为什么要淘汰旧的 avcodec_encode_video2](#一、为什么要淘汰旧的 avcodec_encode_video2)

[旧 API 的问题](#旧 API 的问题)

[新 API 的设计哲学](#新 API 的设计哲学)

[二、新 API 核心的4个函数](#二、新 API 核心的4个函数)

三、核心语义

[Rule 1](#Rule 1)

[Rule 2](#Rule 2)

[Rule 3](#Rule 3)

四、标准编码循环

初始化

编码主循环

五、Flush

旧写法(错误)

[新 API 正确 Flush](#新 API 正确 Flush)

[六、send / receive 返回码速查表](#六、send / receive 返回码速查表)

记忆口诀

[七、B 帧 / 延迟编码器为什么必须新 API](#七、B 帧 / 延迟编码器为什么必须新 API)

[八、和 HW Encoder(NVENC / AMF / QSV)一模一样](#八、和 HW Encoder(NVENC / AMF / QSV)一模一样)

[NVENC 例子(伪代码)](#NVENC 例子(伪代码))

[九、最容易踩的 5 个坑(血泪版)](#九、最容易踩的 5 个坑(血泪版))

[1. 忘了 av_packet_unref](#1. 忘了 av_packet_unref)

[2. send NULL 后又 send frame](#2. send NULL 后又 send frame)

[3. receive 一次就停](#3. receive 一次就停)

[4. pts 没设](#4. pts 没设)

[5. time_base 乱写](#5. time_base 乱写)

十、最小完整可编译骨架

十一、总结


这两个函数是 FFmpeg 中用于‌视频编码 ‌的核心 API,与解码流程(avcodec_send_packet / avcodec_receive_frame)正好相反。它们采用了异步推拉模型,将编码过程分为"发送原始帧"和"接收压缩包"两个步骤。

一、为什么要淘汰旧的 avcodec_encode_video2

如果你搜过 FFmpeg 编码例子,大概率见过这句:

复制代码
avcodec_encode_video2(codec_ctx, pkt, frame, &got_packet);

这是历史包袱

旧 API 的问题

问题 说明
同步语义混乱 got_packet 是 int 指针
不支持 B 帧延迟 最后一帧 flush 很丑
线程不安全 内部状态不可控
不支持 AV1 / Vulkan / HW enc 新编码器根本不兼容

新 API 的设计哲学

"生产者 / 消费者"模型

复制代码
send_frame  →  [Encoder Queue]  →  receive_packet

像 Qt 的 enqueue()/ dequeue()

编码器想缓存几帧就缓存几帧

flush / drain 语义清晰


二、新 API 核心的4个函数

复制代码
int avcodec_send_frame(AVCodecContext *ctx, const AVFrame *frame);
int avcodec_receive_packet(AVCodecContext *ctx, AVPacket *pkt);
int av_interleaved_write_frame(AVFormatContext *s, AVPacket *pkt);
int avcodec_flush_buffers(AVCodecContext *ctx);   // 辅助

三、核心语义

Rule 1

send 返回 0 ≠ 立刻有 packet

编码器可能:

  • 缓存 B 帧

  • GOP 没结束

  • Lookahead 没满


Rule 2

receive 返回 EAGAIN → 再送 frame

receive 返回 EOF → 没数据了


Rule 3

frame = NULL → 告诉编码器"我完了"


四、标准编码循环

初始化

复制代码
AVCodec *codec = avcodec_find_encoder(AV_CODEC_ID_H264);
AVCodecContext *cctx = avcodec_alloc_context3(codec);

cctx->width  = 1280;
cctx->height = 720;
cctx->time_base = (AVRational){1, 25};
cctx->framerate = (AVRational){25, 1};
cctx->gop_size = 25;
cctx->max_b_frames = 3;
cctx->pix_fmt = AV_PIX_FMT_YUV420P;

avcodec_open2(cctx, codec, NULL);

编码主循环

复制代码
AVFrame *frame = av_frame_alloc();
AVPacket *pkt = av_packet_alloc();

while (have_frame_to_encode()) {

    fill_yuv_frame(frame);   // 自己写:sws_scale / hw map

    /* ---------- send ---------- */
    ret = avcodec_send_frame(cctx, frame);
    if (ret < 0) {
        fprintf(stderr, "send error: %s\n", av_err2str(ret));
        break;
    }

    /* ---------- receive ---------- */
    while (ret >= 0) {
        ret = avcodec_receive_packet(cctx, pkt);
        if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF)
            break;
        else if (ret < 0)
            goto end;

        printf("got packet pts=%lld size=%d\n", pkt->pts, pkt->size);

        write_output_file(pkt);
        av_packet_unref(pkt);
    }
}

av_frame_free(&frame);

五、Flush

旧写法(错误)

复制代码
while (got_packet)
    encode_video2(...);

新 API 正确 Flush

复制代码
/* 告诉编码器:没 frame 了 */
avcodec_send_frame(cctx, NULL);

while (avcodec_receive_packet(cctx, pkt) == 0) {
    pkt->pts = av_rescale_q(pkt->pts, cctx->time_base, out_timebase);
    write_output_file(pkt);
    av_packet_unref(pkt);
}

NULL frame = drain encoder queue


六、send / receive 返回码速查表

返回值 含义 你该干啥
0 OK 继续
AVERROR(EAGAIN) encoder 满了 receive_packet再试
AVERROR_EOF 队列空了 结束
<0 other 真出错 abort

记忆口诀

send 报 EAGAIN → receive 再收

receive 报 EAGAIN → send 再喂


七、B 帧 / 延迟编码器为什么必须新 API

举个 H264 bframes=3

复制代码
send f0
send f1
send f2
send f3  → receive PTS=0
send f4  → receive PTS=1
flush     → receive PTS=2,3,4

旧 API:

  • 你得自己数 flush 次数

  • x264 和 nvenc 行为不一致

新 API:

  • 编码器说了算

  • FFmpeg 官方保证语义一致


八、和 HW Encoder(NVENC / AMF / QSV)一模一样

NVENC 例子(伪代码)

复制代码
AVCodec *enc = avcodec_find_encoder_by_name("h264_nvenc");
avcodec_open2(cctx, enc, NULL);

avcodec_send_frame(cctx, frame);
while (avcodec_receive_packet(cctx, pkt) >= 0) {
    // same code
}

软件 / GPU API 完全一致

不用学两套


九、最容易踩的 5 个坑(血泪版)

1. 忘了 av_packet_unref

内存泄漏第一名


2. send NULL 后又 send frame

复制代码
send(NULL)
send(frame) // ❌ ENCODER_FLUSHED

只能 reopen ctx


3. receive 一次就停

复制代码
if (avcodec_receive_packet() == 0)
    write();
// ❌ 可能还有 B 帧

必须 while loop


4. pts 没设

复制代码
frame->pts = frame_index++; // ✅ 必须

否则 x264 直接丢帧


5. time_base 乱写

复制代码
cctx->time_base = {1, 1000}; // ❌ 某些 enc 不接受

推荐 {1, 25}/ {1, 90000}(TS)


十、最小完整可编译骨架

复制代码
while (get_next_frame(&frm)) {
    frm->pts = pts++;

    if (avcodec_send_frame(cc, frm) < 0) continue;

    while (avcodec_receive_packet(cc, pkt) == 0) {
        pkt->stream_index = st->index;
        av_interleaved_write_frame(fmt, pkt);
        av_packet_unref(pkt);
    }
}

avcodec_send_frame(cc, NULL);
while (avcodec_receive_packet(cc, pkt) == 0) {
    write_flush_packet(pkt);
    av_packet_unref(pkt);
}

十一、总结

FFmpeg 编码新 API = 不再问"我有没有 packet",只问"要不要继续收"。

相关推荐
纽格立科技12 小时前
纽格立参展IBC 2026——DRM端到端全链路方案,展位Hall 8 F71
车载系统·音视频·信息与通信·传媒
linux_cfan14 小时前
videojs v10 源代码系列解读:10 · `DestroyMixin`:双重 rAF 延迟销毁
前端·javascript·音视频
云上先途1 天前
标签化服务适合哪些人?常见适用场景一次讲清
大数据·人工智能·算法·音视频
2601_962380761 天前
MoneyPrinterTurbo 本地部署与出片全流程:从环境配置到一键生成短视频
音视频
可乐鸡翅yeah_1 天前
HLS视频编码兼容性全解,H.264/H.265流媒体浏览器适配排错实战
音视频·firefox·safari·m3u8
FlightYe1 天前
音视频修炼之编码器(一):AVC、HEVC编码器内部
android·linux·c++·音视频
昨日之日20061 天前
AuK:多任务语音生成编辑,支持克隆、改词、换情绪与分离,内容编辑与增强全覆盖
人工智能·音视频
林澈在路上2 天前
能做DJ的AI写歌软件推荐:MELO音乐对比Suno v6
大数据·人工智能·github·音视频·音频
YWamy2 天前
政企项目 RTC SDK 评估思路:从技术指标到落地选型
音视频·实时音视频
安河桥。2 天前
座舱显示接口笔记:DP、eDP、无线投屏到底怎么分工?
嵌入式硬件·车载系统·音视频·视频