FFmpeg 新编码 API 完全指南:avcodec_send_frame / avcodec_receive_packet 目录一、为什么要淘汰旧的 avcodec_encode_video2旧 API 的问题新 API 的设计哲学二、新 API 核心的4个函数三、核心语义Rule 1Rule 2Rule 3四、标准编码循环初始化编码主循环五、Flush旧写法错误新 API 正确 Flush六、send / receive 返回码速查表记忆口诀七、B 帧 / 延迟编码器为什么必须新 API八、和 HW EncoderNVENC / AMF / QSV一模一样NVENC 例子伪代码九、最容易踩的 5 个坑血泪版1. 忘了 av_packet_unref2. send NULL 后又 send frame3. receive 一次就停4. pts 没设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 1send 返回 0 ≠ 立刻有 packet编码器可能缓存 B 帧GOP 没结束Lookahead 没满Rule 2receive 返回 EAGAIN → 再送 framereceive 返回 EOF → 没数据了Rule 3frame 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 返回码速查表返回值含义你该干啥0OK继续AVERROR(EAGAIN)encoder 满了receive_packet再试AVERROR_EOF队列空了结束0 other真出错abort记忆口诀send 报 EAGAIN → receive 再收receive 报 EAGAIN → send 再喂七、B 帧 / 延迟编码器为什么必须新 API举个 H264bframes3send f0 send f1 send f2 send f3 → receive PTS0 send f4 → receive PTS1 flush → receive PTS2,3,4旧 API你得自己数 flush 次数x264 和 nvenc 行为不一致新 API编码器说了算FFmpeg 官方保证语义一致八、和 HW EncoderNVENC / 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 framesend(NULL) send(frame) // ❌ ENCODER_FLUSHED只能 reopen ctx3. receive 一次就停if (avcodec_receive_packet() 0) write(); // ❌ 可能还有 B 帧必须 while loop4. 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”只问“要不要继续收”。