关键帧抓取必须先解码,不能只依赖AVPacket->flags;需过滤视频流、提交关键帧包至解码器,调用avcodec_receive_frame()后检查AVFrame->key_frame字段,并正确处理EAGAIN及动态分辨率下的sws_scale转码。

关键帧抓取必须先解码,不能只靠AVPacket->flags
RTSP流里的关键帧(I帧)在传输层确实会打上 AV_PKT_FLAG_KEY 标志,但直接依赖这个标志抓帧极不可靠——很多RTSP服务器(尤其是海康、大华等厂商设备)会在SPS/PPS包或B帧前插入伪造的KEY标志,或者干脆不设。真正稳妥的方式是解码后检查 AVFrame->key_frame 字段。这意味着你得走完整解码流程:接收 AVPacket → 提交到解码器 → 调用 avcodec_receive_frame() → 检查输出帧。
av_read_frame() 后要过滤视频流,且必须跳过非关键帧的解码提交
RTSP流常含多路流(音频、视频、数据),av_read_frame() 返回的包可能属于任意流。若不显式过滤,你会把音频包送进视频解码器,触发 Invalid data found when processing input 错误。同时,为减少CPU开销,非关键帧的 AVPacket 可以直接 av_packet_unref() 丢弃,不必送入解码器:
while (av_read_frame(fmt_ctx, &pkt) >= 0) {
if (pkt.stream_index != video_stream_idx) {
av_packet_unref(&pkt);
continue;
}
// 只有 key_frame 的 packet 才提交解码
if (!(pkt.flags & AV_PKT_FLAG_KEY)) {
av_packet_unref(&pkt);
continue;
}
avcodec_send_packet(dec_ctx, &pkt);
while (avcodec_receive_frame(dec_ctx, frame) == 0) {
if (frame->key_frame) {
// 此时 frame 是真正的关键帧,可保存或处理
}
}
av_packet_unref(&pkt);
}
avcodec_receive_frame() 返回 EAGAIN 时别慌,这是正常流控信号
FFmpeg 解码器内部有缓冲,avcodec_send_packet() 成功后,avcodec_receive_frame() 可能立刻返回 AVERROR(EAGAIN),表示“暂无输出帧,稍后再试”。这不是错误,而是提示你继续读取新包、再尝试接收。常见误操作是遇到 EAGAIN 就 break 或报错退出,结果漏掉后续关键帧。正确做法是:
- 只对
AVERROR(EAGAIN)和AVERROR_EOF)做特殊处理(前者继续循环读包,后者结束) - 其他负值(如
AVERROR_INVALIDDATA)才需记录日志并跳过当前 packet - 每次调用
avcodec_receive_frame()前确保frame已用av_frame_unref()清空,否则可能复用旧数据
保存关键帧图像要用 sws_scale 转格式,RGB24 最通用但别硬编码尺寸
解码输出的 AVFrame 通常是 AV_PIX_FMT_YUV420P,直接写文件无法被普通查看器识别。必须用 sws_getContext() + sws_scale() 转成 AV_PIX_FMT_RGB24。注意两点:
立即学习“C++免费学习笔记(深入)”;
-
sws_getContext()的宽高必须用frame->width/frame->height,不能用初始化时的 codec context 宽高——RTSP流可能动态改变分辨率(如主码流切辅码流) - 转码后 RGB 数据需按行对齐(
frame_rgb->linesize[0]),写 BMP 或 PNG 时务必用该值计算每行字节数,否则图像左右错位 - 若只需保存 JPEG,推荐用
av_image_alloc()分配 buffer,再调用avcodec_encode_video2()(新版用avcodec_send_frame()+avcodec_receive_packet()),比先转RGB再用 libjpeg 更省内存


















