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

如何使用C/C++从视频文件提取特定帧(无法直接用FFmpeg命令行)

当然可以!你完全可以通过FFmpeg的C/C++ API来实现特定帧的提取——本质上就是把你那条命令行的逻辑用代码复现出来。我给你详细讲实现步骤和核心代码:

1. 准备开发环境

首先你需要确保已经安装了FFmpeg的开发库(包含头文件和静态/动态库),编译代码时要链接以下几个核心库:

  • libavformat:负责媒体文件的格式解析与解复用
  • libavcodec:负责视频流的解码
  • libavutil:提供通用工具函数(比如内存分配、格式转换)
  • libswscale:负责像素格式转换(解码后的帧通常是YUV格式,需要转成PNG支持的RGB格式)

以GCC为例,编译命令大概是这样:

gcc extract_frame.c -o extract_frame -lavformat -lavcodec -lavutil -lswscale

2. 核心代码实现

下面是完整的示例代码,功能和你那条命令ffmpeg -i in_video.avi -vf "select=gte(n\,100)" -vframes 1 out_img.png完全一致——提取第100帧(注意FFmpeg的帧索引n从0开始,对应代码里的目标帧索引为100)并保存为PNG:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include <libavformat/avformat.h>
#include <libavcodec/avcodec.h>
#include <libswscale/swscale.h>
#include <libavutil/imgutils.h>

// 将RGB帧保存为PNG文件
int save_frame_as_png(AVFrame *rgb_frame, const char *output_path) {
    const AVCodec *png_codec = avcodec_find_encoder(AV_CODEC_ID_PNG);
    if (!png_codec) {
        fprintf(stderr, "Error: 找不到PNG编码器\n");
        return -1;
    }

    AVCodecContext *png_ctx = avcodec_alloc_context3(png_codec);
    if (!png_ctx) {
        fprintf(stderr, "Error: 无法分配PNG编码器上下文\n");
        return -1;
    }

    // 配置PNG编码器参数
    png_ctx->width = rgb_frame->width;
    png_ctx->height = rgb_frame->height;
    png_ctx->pix_fmt = AV_PIX_FMT_RGB24;
    png_ctx->time_base = (AVRational){1, 1}; // PNG不需要时间基

    if (avcodec_open2(png_ctx, png_codec, NULL) < 0) {
        fprintf(stderr, "Error: 无法打开PNG编码器\n");
        avcodec_free_context(&png_ctx);
        return -1;
    }

    FILE *fp = fopen(output_path, "wb");
    if (!fp) {
        fprintf(stderr, "Error: 无法打开输出文件%s\n", output_path);
        avcodec_free_context(&png_ctx);
        return -1;
    }

    AVPacket pkt = {0};
    av_init_packet(&pkt);

    // 发送RGB帧到编码器
    if (avcodec_send_frame(png_ctx, rgb_frame) < 0) {
        fprintf(stderr, "Error: 发送帧到PNG编码器失败\n");
        fclose(fp);
        avcodec_free_context(&png_ctx);
        return -1;
    }

    // 接收编码后的数据包并写入文件
    while (avcodec_receive_packet(png_ctx, &pkt) == 0) {
        fwrite(pkt.data, 1, pkt.size, fp);
        av_packet_unref(&pkt);
    }

    fclose(fp);
    avcodec_free_context(&png_ctx);
    return 0;
}

int main(int argc, char *argv[]) {
    if (argc != 4) {
        fprintf(stderr, "用法: %s <输入视频文件> <目标帧索引> <输出PNG路径>\n", argv[0]);
        return 1;
    }

    const char *input_file = argv[1];
    int target_frame_idx = atoi(argv[2]);
    const char *output_file = argv[3];

    // 初始化FFmpeg组件
    av_register_all();
    avformat_network_init();

    AVFormatContext *fmt_ctx = NULL;
    if (avformat_open_input(&fmt_ctx, input_file, NULL, NULL) < 0) {
        fprintf(stderr, "Error: 无法打开输入视频%s\n", input_file);
        return 1;
    }

    if (avformat_find_stream_info(fmt_ctx, NULL) < 0) {
        fprintf(stderr, "Error: 无法获取视频流信息\n");
        avformat_close_input(&fmt_ctx);
        return 1;
    }

    // 查找视频流索引
    int video_stream_idx = -1;
    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;
            break;
        }
    }
    if (video_stream_idx == -1) {
        fprintf(stderr, "Error: 未找到视频流\n");
        avformat_close_input(&fmt_ctx);
        return 1;
    }

    // 初始化视频解码器
    AVCodecParameters *codec_par = fmt_ctx->streams[video_stream_idx]->codecpar;
    const AVCodec *video_codec = avcodec_find_decoder(codec_par->codec_id);
    if (!video_codec) {
        fprintf(stderr, "Error: 找不到对应视频解码器\n");
        avformat_close_input(&fmt_ctx);
        return 1;
    }

    AVCodecContext *codec_ctx = avcodec_alloc_context3(video_codec);
    if (!codec_ctx) {
        fprintf(stderr, "Error: 无法分配解码器上下文\n");
        avformat_close_input(&fmt_ctx);
        return 1;
    }

    if (avcodec_parameters_to_context(codec_ctx, codec_par) < 0) {
        fprintf(stderr, "Error: 无法复制解码器参数\n");
        avcodec_free_context(&codec_ctx);
        avformat_close_input(&fmt_ctx);
        return 1;
    }

    if (avcodec_open2(codec_ctx, video_codec, NULL) < 0) {
        fprintf(stderr, "Error: 无法打开视频解码器\n");
        avcodec_free_context(&codec_ctx);
        avformat_close_input(&fmt_ctx);
        return 1;
    }

    // 分配帧内存
    AVFrame *yuv_frame = av_frame_alloc();
    AVFrame *rgb_frame = av_frame_alloc();
    if (!yuv_frame || !rgb_frame) {
        fprintf(stderr, "Error: 无法分配帧内存\n");
        avcodec_free_context(&codec_ctx);
        avformat_close_input(&fmt_ctx);
        return 1;
    }

    // 初始化像素格式转换器(YUV转RGB24)
    struct SwsContext *sws_ctx = sws_getContext(
        codec_ctx->width, codec_ctx->height, codec_ctx->pix_fmt,
        codec_ctx->width, codec_ctx->height, AV_PIX_FMT_RGB24,
        SWS_BILINEAR, NULL, NULL, NULL
    );
    if (!sws_ctx) {
        fprintf(stderr, "Error: 无法初始化像素格式转换器\n");
        av_frame_free(&yuv_frame);
        av_frame_free(&rgb_frame);
        avcodec_free_context(&codec_ctx);
        avformat_close_input(&fmt_ctx);
        return 1;
    }

    // 为RGB帧分配缓冲区
    int rgb_buffer_size = av_image_get_buffer_size(AV_PIX_FMT_RGB24, codec_ctx->width, codec_ctx->height, 1);
    uint8_t *rgb_buffer = (uint8_t *)av_malloc(rgb_buffer_size);
    av_image_fill_arrays(rgb_frame->data, rgb_frame->linesize, rgb_buffer, AV_PIX_FMT_RGB24,
                         codec_ctx->width, codec_ctx->height, 1);

    AVPacket pkt = {0};
    int current_frame_idx = 0;
    int target_found = 0;

    // 读取并解码视频帧,直到找到目标帧
    while (av_read_frame(fmt_ctx, &pkt) >= 0) {
        if (pkt.stream_index != video_stream_idx) {
            av_packet_unref(&pkt);
            continue;
        }

        if (avcodec_send_packet(codec_ctx, &pkt) < 0) {
            fprintf(stderr, "Error: 发送数据包到解码器失败\n");
            av_packet_unref(&pkt);
            break;
        }

        while (avcodec_receive_frame(codec_ctx, yuv_frame) == 0) {
            if (current_frame_idx == target_frame_idx) {
                // 转换为RGB格式
                sws_scale(sws_ctx, (const uint8_t *const *)yuv_frame->data, yuv_frame->linesize,
                          0, codec_ctx->height, rgb_frame->data, rgb_frame->linesize);

                // 保存为PNG
                if (save_frame_as_png(rgb_frame, output_file) == 0) {
                    printf("成功提取第%d帧到%s\n", target_frame_idx, output_file);
                } else {
                    fprintf(stderr, "Error: 保存PNG文件失败\n");
                }

                target_found = 1;
                goto cleanup; // 找到目标帧后直接清理资源退出
            }
            current_frame_idx++;
        }

        av_packet_unref(&pkt);
    }

    if (!target_found) {
        fprintf(stderr, "Error: 未找到第%d帧,视频总帧数为%d\n", target_frame_idx, current_frame_idx);
    }

cleanup:
    // 释放所有资源
    av_free(rgb_buffer);
    sws_freeContext(sws_ctx);
    av_frame_free(&rgb_frame);
    av_frame_free(&yuv_frame);
    avcodec_free_context(&codec_ctx);
    avformat_close_input(&fmt_ctx);
    avformat_network_deinit();

    return 0;
}

3. 代码说明

  • 初始化流程:注册FFmpeg组件、打开输入视频、解析流信息、找到视频流并初始化对应的解码器。
  • 帧解码与计数:循环读取视频数据包,解码得到原始YUV帧,同时计数帧索引,直到匹配目标帧。
  • 格式转换:用sws_scale将YUV帧转换为PNG支持的RGB24格式。
  • 保存PNG:调用FFmpeg的PNG编码器,将RGB帧编码并写入文件。

4. 使用方法

编译后运行以下命令即可(对应你原来的需求):

./extract_frame in_video.avi 100 out_img.png

内容的提问来源于stack exchange,提问作者CoXier

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.25 03:34:32