目录
[一、为什么要淘汰旧的 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)
[新 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",只问"要不要继续收"。