FFmpeg send_packet / receive_frame EAGAIN状态拆解1. 整体数据流解码主链路可以先压缩成下面这张图user packet - avcodec_send_packet() - buffer_pkt - BSF - in_pkt - decoder - buffer_frame - avcodec_receive_frame() - decoder user frame这里几个关键对象的职责是buffer_pktAPI 输入暂存槽send_packet()先把用户传入的AVPacket放这里BSFBitstream Filter负责把容器侧码流整理成 decoder 更适合消费的压缩包in_pkt真正喂给 decode 路径的当前工作包buffer_frame已经解出、但还没被用户receive_frame()取走的帧缓存2. AVERROR(EAGAIN)先看 FFmpeg 自己在avcodec.h里的约束可以连续avcodec_send_packet(...)但内部缓冲满后会返回AVERROR(EAGAIN)一旦send返回EAGAIN必须先receive3.avcodec_send_packet(...)顶层源码拆解intattribute_align_argavcodec_send_packet(AVCodecContext*avctx,constAVPacket*avpkt){AVCodecInternal*avciavctx-internal;DecodeContext*dcdecode_ctx(avci);intret;if(!avcodec_is_open(avctx)||!av_codec_is_decoder(avctx-codec))returnAVERROR(EINVAL);if(dc-draining_started)returnAVERROR_EOF;if(avpkt!avpkt-sizeavpkt-data)returnAVERROR(EINVAL);if(avpkt(avpkt-data||avpkt-side_data_elems)){if(!AVPACKET_IS_EMPTY(avci-buffer_pkt))returnAVERROR(EAGAIN);retav_packet_ref(avci-buffer_pkt,avpkt);if(ret0)returnret;}elsedc-draining_started1;if(!avci-buffer_frame-buf[0]!dc-draining_started){retdecode_receive_frame_internal(avctx,avci-buffer_frame);if(ret0ret!AVERROR(EAGAIN)ret!AVERROR_EOF)returnret;}return0;}看上面ffmpeg源码可以知道send packet返回again的判断十分简单就是buffer_pkt非空直接返回eagain如果为空就把外部传进来的packet ref给它。关键在后面!avci-buffer_frame-buf[0]就会调用一次内部解码接口decode_receive_frame_internal(avctx, avci-buffer_frame);做一次解码。decode_receive_frame_internal的源码就详细展开因为展开反而不好理解简单来说这个接口会调用receive_frame(avctx, frame);获取一个解码帧到buffer_frame中。现在具体看receive_frame内部的主要执行步骤decode_get_packet(avctx, pkt);decode这里简单写成两步涉及got framepkt consumed相关逻辑暂时不展开说。这里可以主要看decode_get_packet这个接口中ffmpeg会从bsf中获取pkt给到in_pkt这个in_pkt才是给到decode步骤用来解码的pkt。这一步如果从bsf中获取pkt失败才会把前面提到的avci-buffer_pkt给到bsf经bsf后给到in_pkt。具体源码可以看下面decode_get_packet就是从bsf中获取pkt av_bsf_send_packet就是往bsf中放入pkt。把这套流程搞清楚后就很清楚为什么buffer pkt只是单槽但是send却可以连续send。假设中间完全不receive。情况 A第 1 个 packet 就出 frame可能序列是send1 - packet 进 buffer_pkt - 立刻推进 - 产出 1 帧到 buffer_frame send2 - 新 packet 可以先进 buffer_pkt但不会继续推进 send3 - buffer_pkt 还没空返回 EAGAIN情况 B要攒 3 个 packet 才出第 1 帧可能序列是send1 - 吃掉输入不出帧 send2 - 吃掉输入不出帧 send3 - 这次才出第 1 帧 send4 - 还能先接收下一个 packet send5 - 这时才 EAGAIN所以通用结论是只要buffer_frame还是空的send就还有机会继续推进解码一旦buffer_frame非空而上层又不receive通常最多还能再成功send1 次再往后就容易EAGAIN4. 为什么BSF会暂时吐不出in_pkt这通常是码流整理层自己的原因不是 decoder 算不出来。常见原因一个 packet 还不够组成完整 access unit还需要继续重组码流header / 参数集 / 边界信息还没凑齐bsf.h也明确写了av_bsf_receive_packet()返回EAGAIN表示还需要继续av_bsf_send_packet(...)5. 为什么会出现“packet 被吃掉但不出 frame”这是 FFmpeg 明确支持的正常行为不是异常。函数前面的注释直接写了some decoders consume partial packets without returning any output来源decode.c:394-400https://ffmpeg.org/doxygen/6.1/decode_8c_source.html#l00394从编解码角度看常见原因是视频需要更多参考帧存在 B 帧重排序一帧被拆在多个压缩包里音频需要累积更多 sample当前产出的东西被 discard / trim 掉了所以要把这两个动作分开理解consume in_pktoutput frame它们不是一一对应的。6.avcodec_receive_frame(...)顶层源码拆解receive_frame顶层比send_packet更简单。写成伪代码intavcodec_receive_frame(avctx,frame){if(buffer_frame has cached frame){move buffer_frame to user frame;return0;}retdecode_receive_frame_internal(avctx,frame);returnret;}receive_frame()返回EAGAIN时更准确的语义是当前没有可交付帧它不等价于这一轮什么都没发生因为内部完全可能已经发生了这些事情decoder 吃掉了输入内部状态推进了但还没到能输出 frame 的时机7.draining_started / avci-draining / draining_done三个状态怎么区分7.1draining_started含义上层已经送了空包明确声明后面不会再有正常输入这是 API 侧的“意图标记”。此时内部仍然可能还有buffer_pktBSF缓存输出in_pktdecoder 内部延迟帧7.2avci-draining含义输入链路已经真的走到尽头经过buffer_pkt - BSF之后再也拿不到新的压缩包了触发位置在decode_get_packet()if(retAVERROR_EOF)avci-draining1;7.3draining_done含义输入链路已经完全耗尽decoder 内部延迟输出也已经吐完这是 drain 的最终结束态。这时继续receive_frame()就应该得到EOF。
FFmpeg send_packet / receive_frame EAGAIN状态拆解
发布时间:2026/7/3 5:53:32
FFmpeg send_packet / receive_frame EAGAIN状态拆解1. 整体数据流解码主链路可以先压缩成下面这张图user packet - avcodec_send_packet() - buffer_pkt - BSF - in_pkt - decoder - buffer_frame - avcodec_receive_frame() - decoder user frame这里几个关键对象的职责是buffer_pktAPI 输入暂存槽send_packet()先把用户传入的AVPacket放这里BSFBitstream Filter负责把容器侧码流整理成 decoder 更适合消费的压缩包in_pkt真正喂给 decode 路径的当前工作包buffer_frame已经解出、但还没被用户receive_frame()取走的帧缓存2. AVERROR(EAGAIN)先看 FFmpeg 自己在avcodec.h里的约束可以连续avcodec_send_packet(...)但内部缓冲满后会返回AVERROR(EAGAIN)一旦send返回EAGAIN必须先receive3.avcodec_send_packet(...)顶层源码拆解intattribute_align_argavcodec_send_packet(AVCodecContext*avctx,constAVPacket*avpkt){AVCodecInternal*avciavctx-internal;DecodeContext*dcdecode_ctx(avci);intret;if(!avcodec_is_open(avctx)||!av_codec_is_decoder(avctx-codec))returnAVERROR(EINVAL);if(dc-draining_started)returnAVERROR_EOF;if(avpkt!avpkt-sizeavpkt-data)returnAVERROR(EINVAL);if(avpkt(avpkt-data||avpkt-side_data_elems)){if(!AVPACKET_IS_EMPTY(avci-buffer_pkt))returnAVERROR(EAGAIN);retav_packet_ref(avci-buffer_pkt,avpkt);if(ret0)returnret;}elsedc-draining_started1;if(!avci-buffer_frame-buf[0]!dc-draining_started){retdecode_receive_frame_internal(avctx,avci-buffer_frame);if(ret0ret!AVERROR(EAGAIN)ret!AVERROR_EOF)returnret;}return0;}看上面ffmpeg源码可以知道send packet返回again的判断十分简单就是buffer_pkt非空直接返回eagain如果为空就把外部传进来的packet ref给它。关键在后面!avci-buffer_frame-buf[0]就会调用一次内部解码接口decode_receive_frame_internal(avctx, avci-buffer_frame);做一次解码。decode_receive_frame_internal的源码就详细展开因为展开反而不好理解简单来说这个接口会调用receive_frame(avctx, frame);获取一个解码帧到buffer_frame中。现在具体看receive_frame内部的主要执行步骤decode_get_packet(avctx, pkt);decode这里简单写成两步涉及got framepkt consumed相关逻辑暂时不展开说。这里可以主要看decode_get_packet这个接口中ffmpeg会从bsf中获取pkt给到in_pkt这个in_pkt才是给到decode步骤用来解码的pkt。这一步如果从bsf中获取pkt失败才会把前面提到的avci-buffer_pkt给到bsf经bsf后给到in_pkt。具体源码可以看下面decode_get_packet就是从bsf中获取pkt av_bsf_send_packet就是往bsf中放入pkt。把这套流程搞清楚后就很清楚为什么buffer pkt只是单槽但是send却可以连续send。假设中间完全不receive。情况 A第 1 个 packet 就出 frame可能序列是send1 - packet 进 buffer_pkt - 立刻推进 - 产出 1 帧到 buffer_frame send2 - 新 packet 可以先进 buffer_pkt但不会继续推进 send3 - buffer_pkt 还没空返回 EAGAIN情况 B要攒 3 个 packet 才出第 1 帧可能序列是send1 - 吃掉输入不出帧 send2 - 吃掉输入不出帧 send3 - 这次才出第 1 帧 send4 - 还能先接收下一个 packet send5 - 这时才 EAGAIN所以通用结论是只要buffer_frame还是空的send就还有机会继续推进解码一旦buffer_frame非空而上层又不receive通常最多还能再成功send1 次再往后就容易EAGAIN4. 为什么BSF会暂时吐不出in_pkt这通常是码流整理层自己的原因不是 decoder 算不出来。常见原因一个 packet 还不够组成完整 access unit还需要继续重组码流header / 参数集 / 边界信息还没凑齐bsf.h也明确写了av_bsf_receive_packet()返回EAGAIN表示还需要继续av_bsf_send_packet(...)5. 为什么会出现“packet 被吃掉但不出 frame”这是 FFmpeg 明确支持的正常行为不是异常。函数前面的注释直接写了some decoders consume partial packets without returning any output来源decode.c:394-400https://ffmpeg.org/doxygen/6.1/decode_8c_source.html#l00394从编解码角度看常见原因是视频需要更多参考帧存在 B 帧重排序一帧被拆在多个压缩包里音频需要累积更多 sample当前产出的东西被 discard / trim 掉了所以要把这两个动作分开理解consume in_pktoutput frame它们不是一一对应的。6.avcodec_receive_frame(...)顶层源码拆解receive_frame顶层比send_packet更简单。写成伪代码intavcodec_receive_frame(avctx,frame){if(buffer_frame has cached frame){move buffer_frame to user frame;return0;}retdecode_receive_frame_internal(avctx,frame);returnret;}receive_frame()返回EAGAIN时更准确的语义是当前没有可交付帧它不等价于这一轮什么都没发生因为内部完全可能已经发生了这些事情decoder 吃掉了输入内部状态推进了但还没到能输出 frame 的时机7.draining_started / avci-draining / draining_done三个状态怎么区分7.1draining_started含义上层已经送了空包明确声明后面不会再有正常输入这是 API 侧的“意图标记”。此时内部仍然可能还有buffer_pktBSF缓存输出in_pktdecoder 内部延迟帧7.2avci-draining含义输入链路已经真的走到尽头经过buffer_pkt - BSF之后再也拿不到新的压缩包了触发位置在decode_get_packet()if(retAVERROR_EOF)avci-draining1;7.3draining_done含义输入链路已经完全耗尽decoder 内部延迟输出也已经吐完这是 drain 的最终结束态。这时继续receive_frame()就应该得到EOF。