You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用FFmpeg+GDI截屏时avcodec_receive_packet返回AVERROR(EAGAIN)求助

解决FFmpeg屏幕捕获中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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.28 06:40:13