使用FFmpeg+GDI截屏时avcodec_receive_packet返回AVERROR(EAGAIN)求助
avcodec_receive_packet返回AVERROR(EAGAIN)的问题 嘿,我来帮你理清这个问题——其实AVERROR(EAGAIN)根本不是“错误”,它是编码器在给你发信号:我现在缓冲区里没现成的数据包给你,要么继续发帧,要么让我收尾把剩下的包吐出来。你的代码核心问题是没做编码器的收尾工作,导致缓冲区里的数据包没被取出,下面一步步给你解决:
1. 先搞懂EAGAIN到底是什么
编码器内部有缓存机制,比如H.264这类编码需要攒够几帧(比如I帧、P帧的依赖关系)才会输出数据包,或者你的GOP设置比较大,前期都会出现这个状态。你的原代码里遇到EAGAIN就直接返回,这本身没问题,但你没在所有帧发送完后告诉编码器“我没帧了,把剩下的包都给我”,所以最后缓冲区里的内容没被写入文件,虽然注释错误处理后文件能读,但那是程序结束时编码器自动清理的侥幸行为,不是正确做法。
2. 给编码流程加个收尾步骤
在你循环发送完所有屏幕捕获的帧之后,必须给编码器发一个NULL帧,触发它输出剩余的缓存数据包。修改你的主循环:
// 原有的循环:发送所有捕获的帧 for (int i = 0; i < fps*seconds; i++) { fflush(stdout); /* 确保帧数据可写 */ ret = av_frame_make_writable(frame); if (ret < 0) exit(1); gdi->MakeScreenshoot(); OutFrame->pts = i; int ret = av_image_fill_arrays(GDIFrame->data, GDIFrame->linesize, gdi->m_bufferGDIBits, AV_PIX_FMT_BGRA, c->width, c->height, 1); ret = av_image_fill_arrays(OutFrame->data, OutFrame->linesize, outbuffer, c->pix_fmt, c->width, c->height, 1); GDIFrame->data[0] += GDIFrame->linesize[0] * (c->height - 1); // 翻转帧 GDIFrame->linesize[0] *= -1; int hslice = sws_scale(convertContext, GDIFrame->data, GDIFrame->linesize, 0, c->height,OutFrame->data, OutFrame->linesize); /* 编码当前帧 */ encode(c, OutFrame, pkt, f); } // 关键步骤:发送NULL帧,让编码器输出剩余缓存的数据包 encode(c, NULL, pkt, f);
3. 优化encode函数的错误处理逻辑
你的原encode函数的逻辑其实没问题,但可以调整得更健壮,比如不要直接exit(1),而是返回错误码让上层处理,同时明确EAGAIN的含义。修改后的encode函数:
static int encode(AVCodecContext *enc_ctx, AVFrame *frame, AVPacket *pkt, FILE *outfile) { int ret = avcodec_send_frame(enc_ctx, frame); if (ret < 0) { fprintf(stderr, "发送帧到编码器失败: %s\n", av_err2str(ret)); return ret; } while (ret >= 0) { ret = avcodec_receive_packet(enc_ctx, pkt); if (ret == AVERROR_EOF) { // 编码器已经输出所有数据包,收尾完成 return 0; } else if (ret == AVERROR(EAGAIN)) { // 当前没有可用数据包,后续继续调用即可,不是错误 return 0; } else if (ret < 0) { fprintf(stderr, "接收编码数据包失败: %s\n", av_err2str(ret)); return ret; } // 写入数据包到文件 fwrite(pkt->data, 1, pkt->size, outfile); av_packet_unref(pkt); } return 0; }
4. 检查帧和编码器的参数匹配
确保你的OutFrame的参数和编码器上下文完全一致:
OutFrame->width、OutFrame->height要和enc_ctx->width、enc_ctx->height相同OutFrame->pix_fmt必须等于enc_ctx->pix_fmt- 时间戳
OutFrame->pts要符合编码器的time_base。比如如果你的帧率是fps,那么编码器的时间基应该设为:
此时enc_ctx->time_base = av_make_q(1, fps);OutFrame->pts = i;是正确的,因为每帧间隔1/fps秒。
5. 关于编码器缓存的额外说明
如果你的编码器(比如x264)设置了较大的gop_size或者rc_lookahead,前期发送帧时可能会多次返回EAGAIN,这是正常的——编码器在攒帧准备生成I帧或者做码率控制。只要你最后发送NULL帧收尾,就能把所有缓存的数据包都取出来。
按照上面的步骤修改后,你不用注释掉错误处理代码,AVERROR(EAGAIN)的情况会被正确处理,生成的文件也会完整无问题。
内容的提问来源于stack exchange,提问作者saqef

