如何通过FFmpeg内存提取视频帧并做直方图分析筛选闪电帧?
实现FFmpeg内存帧提取+闪电帧筛选保存流程
核心思路
跳过FFmpeg将帧写入磁盘的步骤,直接通过FFmpeg的libavformat/libavcodec库从视频流解码帧到内存缓冲区,将内存帧数据传给你已有的直方图分析逻辑,判断为闪电帧时再调用FFmpeg编码接口将帧写入磁盘。
具体实现步骤
1. FFmpeg初始化与内存帧准备
首先完成FFmpeg的基础初始化,打开输入视频、找到视频流、初始化解码器,并分配内存帧缓冲区(无需创建磁盘文件):
// 初始化FFmpeg核心组件 av_register_all(); avformat_network_init(); AVFormatContext *fmt_ctx = NULL; // 打开输入视频文件 if (avformat_open_input(&fmt_ctx, "input_video.mp4", NULL, NULL) != 0) { // 错误处理:输入文件打开失败 return -1; } // 读取流信息 if (avformat_find_stream_info(fmt_ctx, NULL) < 0) { avformat_close_input(&fmt_ctx); return -1; } // 定位视频流 int video_stream_idx = -1; AVCodecParameters *codec_par = NULL; for (int i = 0; i < fmt_ctx->nb_streams; i++) { if (fmt_ctx->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) { video_stream_idx = i; codec_par = fmt_ctx->streams[i]->codecpar; break; } } // 初始化解码器上下文 AVCodec *codec = avcodec_find_decoder(codec_par->codec_id); AVCodecContext *codec_ctx = avcodec_alloc_context3(codec); avcodec_parameters_to_context(codec_ctx, codec_par); if (avcodec_open2(codec_ctx, codec, NULL) < 0) { avcodec_free_context(&codec_ctx); avformat_close_input(&fmt_ctx); return -1; } // 分配内存帧缓冲区(用于存储解码后的帧数据) AVFrame *frame = av_frame_alloc(); AVPacket pkt; av_init_packet(&pkt);
2. 逐帧解码到内存
循环读取视频数据包,解码得到内存中的AVFrame,跳过磁盘写入步骤:
while (av_read_frame(fmt_ctx, &pkt) >= 0) { if (pkt.stream_index != video_stream_idx) { av_packet_unref(&pkt); continue; } // 发送数据包到解码器 int ret = avcodec_send_packet(codec_ctx, &pkt); if (ret < 0) { av_packet_unref(&pkt); continue; } // 接收解码后的内存帧 while (ret >= 0) { ret = avcodec_receive_frame(codec_ctx, frame); if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) break; if (ret < 0) { // 错误处理:解码失败 break; } // 处理当前内存帧:分析+筛选+保存 process_lightning_frame(frame); av_frame_unref(frame); // 重置帧缓冲区,准备接收下一个帧 } av_packet_unref(&pkt); }
3. 对接直方图分析逻辑
将FFmpeg解码出的YUV格式内存帧,转换为你分析函数兼容的格式(比如RGB24),再调用已有分析逻辑判断是否存在闪电:
void process_lightning_frame(AVFrame *frame) { // 初始化像素格式转换器:将FFmpeg默认的YUV420P转为RGB24(适配你的分析函数) struct SwsContext *sws_ctx = sws_getContext( frame->width, frame->height, frame->format, frame->width, frame->height, AV_PIX_FMT_RGB24, SWS_BILINEAR, NULL, NULL, NULL ); if (!sws_ctx) return; // 分配RGB格式内存缓冲区 uint8_t *rgb_data[1]; int rgb_linesize[1]; av_image_alloc(rgb_data, rgb_linesize, frame->width, frame->height, AV_PIX_FMT_RGB24, 1); // 执行格式转换:YUV -> RGB sws_scale(sws_ctx, frame->data, frame->linesize, 0, frame->height, rgb_data, rgb_linesize); // 调用你已有的直方图分析函数,传入内存中的RGB数据 int has_lightning = your_histogram_analysis( rgb_data[0], // RGB内存缓冲区首地址 frame->width, // 帧宽度 frame->height, // 帧高度 rgb_linesize[0] // RGB行跨度(每行字节数) ); // 如果检测到闪电,保存当前帧到磁盘 if (has_lightning) { save_frame_to_disk(frame, "lightning_frame_%lld.jpg", frame->pts); } // 释放资源 av_freep(&rgb_data[0]); sws_freeContext(sws_ctx); }
4. 闪电帧写入磁盘
将内存中的AVFrame编码为图片文件(比如JPG)写入磁盘:
void save_frame_to_disk(AVFrame *frame, const char *filename_pattern, int64_t pts) { char filename[256]; snprintf(filename, sizeof(filename), filename_pattern, pts); // 用帧的pts作为唯一标识命名 // 初始化JPEG编码器 AVCodec *jpg_codec = avcodec_find_encoder(AV_CODEC_ID_MJPEG); AVCodecContext *jpg_ctx = avcodec_alloc_context3(jpg_codec); jpg_ctx->width = frame->width; jpg_ctx->height = frame->height; jpg_ctx->pix_fmt = AV_PIX_FMT_YUVJ420P; // MJPEG标准格式 jpg_ctx->bit_rate = 1000000; // 编码比特率 if (avcodec_open2(jpg_ctx, jpg_codec, NULL) < 0) { avcodec_free_context(&jpg_ctx); return; } // 转换帧格式到编码器兼容的YUVJ420P AVFrame *jpg_frame = av_frame_alloc(); jpg_frame->format = jpg_ctx->pix_fmt; jpg_frame->width = frame->width; jpg_frame->height = frame->height; av_frame_get_buffer(jpg_frame, 0); struct SwsContext *sws_ctx = sws_getContext( frame->width, frame->height, frame->format, jpg_frame->width, jpg_frame->height, jpg_frame->format, SWS_BILINEAR, NULL, NULL, NULL ); sws_scale(sws_ctx, frame->data, frame->linesize, 0, frame->height, jpg_frame->data, jpg_frame->linesize); sws_freeContext(sws_ctx); // 编码并写入文件 AVPacket pkt; av_init_packet(&pkt); pkt.data = NULL; pkt.size = 0; int ret = avcodec_send_frame(jpg_ctx, jpg_frame); if (ret >= 0) { ret = avcodec_receive_packet(jpg_ctx, &pkt); if (ret >= 0) { FILE *fp = fopen(filename, "wb"); if (fp) { fwrite(pkt.data, 1, pkt.size, fp); fclose(fp); } av_packet_unref(&pkt); } } // 释放资源 av_frame_free(&jpg_frame); avcodec_free_context(&jpg_ctx); }
关键注意事项
- 内存管理:必须调用
av_frame_unref、av_packet_unref、av_freep等函数释放内存,避免内存泄漏。 - 格式适配:FFmpeg解码帧默认是YUV格式,需根据你分析函数的输入格式(如RGB、BGR)做格式转换。
- 错误处理:实际代码中要完善所有FFmpeg API的返回值判断,避免崩溃。
- 帧标识:用
frame->pts(显示时间戳)作为帧的唯一命名标识,避免文件覆盖。
内容的提问来源于stack exchange,提问作者Daniel Lip
相关产品推荐
相关产品推荐

