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

如何通过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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.15 07:15:41