[FFmpeg学习]从视频中获取图片

从视频中获取图片是一个比较直观的例子,这里从一个基础的例子来查看FFmpeg相关api的使用,从mp4文件中获取一帧图像,保存为jpeg格式图片,mp4文件比较好准备,一般手机录屏文件就是mp4格式。

原理还是比较清楚,得到一个AVFrame后,再使用jpeg的编码器来转换

cpp 复制代码
int getpic() {
    std::string filename = "test.mp4";     // 输入MP4文件名
    std::string outputFilename = "output.jpg";  // 输出图片文件名
    int targetSecond = 1;    // 目标秒数

    AVFormatContext* formatContext = nullptr;
    if (avformat_open_input(&formatContext, filename.c_str(), nullptr, nullptr) != 0) {
        std::cerr << "Error opening input file" << std::endl;
        return -1;
    }

    if (avformat_find_stream_info(formatContext, nullptr) < 0) {
        std::cerr << "Error finding stream information" << std::endl;
        avformat_close_input(&formatContext);
        return -1;
    }

    const AVCodec* codec = nullptr;
    int videoStreamIndex = -1;

    for (unsigned int i = 0; i < formatContext->nb_streams; i++) {
        if (formatContext->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) {
            videoStreamIndex = i;
            codec = avcodec_find_decoder(formatContext->streams[i]->codecpar->codec_id);
            break;
        }
    }

    if (videoStreamIndex == -1 || codec == nullptr) {
        std::cerr << "Error finding video stream or decoder" << std::endl;
        avformat_close_input(&formatContext);
        return -1;
    }

    AVCodecContext* codecContext = avcodec_alloc_context3(codec);
    if (codecContext == nullptr) {
        std::cerr << "Error allocating codec context" << std::endl;
        avformat_close_input(&formatContext);
        return -1;
    }

    if (avcodec_parameters_to_context(codecContext, formatContext->streams[videoStreamIndex]->codecpar) < 0) {
        std::cerr << "Error setting codec parameters" << std::endl;
        avcodec_free_context(&codecContext);
        avformat_close_input(&formatContext);
        return -1;
    }

    if (avcodec_open2(codecContext, codec, nullptr) < 0) {
        std::cerr << "Error opening codec" << std::endl;
        avcodec_free_context(&codecContext);
        avformat_close_input(&formatContext);
        return -1;
    }

    AVPacket packet;
    av_init_packet(&packet);

    // 计算目标时间戳
    int64_t targetTimestamp = targetSecond * AV_TIME_BASE;

    // 查找目标时间戳所对应的帧
    AVFrame* frame = av_frame_alloc();
    bool foundTargetFrame = false;
    int count = 0;
    while (av_read_frame(formatContext, &packet) >= 0) {
        if (packet.stream_index == videoStreamIndex) {
            int response = avcodec_send_packet(codecContext, &packet);
            if (response < 0) {
                std::cerr << "Error sending packet to decoder" << std::endl;
                break;
            }
            count++;

            response = avcodec_receive_frame(codecContext, frame);
            if (response == AVERROR(EAGAIN) || response == AVERROR_EOF) {
                continue;
            }
            else if (response < 0) {
                std::cerr << "Error receiving frame from decoder" << std::endl;
                break;
            }

            // 检查帧的时间戳是否接近目标时间戳
            /*
            if (frame->pts >= targetTimestamp - (AV_TIME_BASE / 2) && frame->pts <= targetTimestamp + (AV_TIME_BASE / 2)) {
                foundTargetFrame = true;
                break;
            }*/
            if (count == 20) {
                foundTargetFrame = true;
                char outname[] = "out.jpg";
//                savePicture(frame, outname);
                break;
            }
        }

        av_packet_unref(&packet);
    }

    if (!foundTargetFrame) {
        std::cerr << "Target frame not found" << std::endl;
        av_frame_free(&frame);
        avcodec_free_context(&codecContext);
        avformat_close_input(&formatContext);
        return -1;
    }

    // 将帧的数据保存为JPEG图片
    AVFrame* rgbFrame = av_frame_alloc();
    if (rgbFrame == nullptr) {
        std::cerr << "Error allocating RGB frame" << std::endl;
        av_frame_free(&frame);
        avcodec_free_context(&codecContext);
        avformat_close_input(&formatContext);
        return -1;
    }
    /*
    struct SwsContext* swsContext
        = sws_getContext(
            codecContext->width, codecContext->height, codecContext->pix_fmt,
            codecContext->width, codecContext->height, AV_PIX_FMT_RGB24,
            SWS_BILINEAR, nullptr, nullptr, nullptr
        );

    if (swsContext == nullptr) {
        std::cerr << "Error creating SwsContext" << std::endl;
        av_frame_free(&frame);
        av_frame_free(&rgbFrame);
        avcodec_free_context(&codecContext);
        avformat_close_input(&formatContext);
        return -1;
    }

    // 分配RGB帧的缓冲区
    int numBytes = av_image_get_buffer_size(AV_PIX_FMT_RGB24, codecContext->width, codecContext->height, 1);
    uint8_t* buffer = (uint8_t*)av_malloc(numBytes * sizeof(uint8_t));
    av_image_fill_arrays(rgbFrame->data, rgbFrame->linesize, buffer, AV_PIX_FMT_RGB24, codecContext->width, codecContext->height, 1);

    // 将解码后的帧转换为RGB格式
    sws_scale(swsContext, frame->data, frame->linesize, 0, codecContext->height, rgbFrame->data, rgbFrame->linesize);
    */

    // 保存RGB帧为JPEG图片
    const AVCodec* jpegCodec = avcodec_find_encoder(AV_CODEC_ID_MJPEG);
    if (jpegCodec == nullptr) {
        std::cerr << "Error finding JPEG encoder" << std::endl;
        av_frame_free(&frame);
        av_frame_free(&rgbFrame);
        avcodec_free_context(&codecContext);
        avformat_close_input(&formatContext);
        return -1;
    }

    AVCodecContext* jpegCodecContext = avcodec_alloc_context3(jpegCodec);
    if (jpegCodecContext == nullptr) {
        std::cerr << "Error allocating JPEG codec context" << std::endl;
        av_frame_free(&frame);
        av_frame_free(&rgbFrame);
        avcodec_free_context(&codecContext);
        avformat_close_input(&formatContext);
        return -1;
    }

    jpegCodecContext->pix_fmt = AV_PIX_FMT_YUVJ420P;
    jpegCodecContext->width = codecContext->width;
    jpegCodecContext->height = codecContext->height;

    // 设置编码器时间基
    jpegCodecContext->time_base = { 1, 25 };//formatContext->streams[videoStreamIndex]->time_base;

    if (avcodec_open2(jpegCodecContext, jpegCodec, nullptr) < 0) {
        std::cerr << "Error opening JPEG codec" << std::endl;
        av_frame_free(&frame);
        av_frame_free(&rgbFrame);
        avcodec_free_context(&codecContext);
        avcodec_free_context(&jpegCodecContext);
        avformat_close_input(&formatContext);
        return -1;
    }

    AVPacket jpegPacket;
    av_init_packet(&jpegPacket);
    jpegPacket.data = nullptr;
    jpegPacket.size = 0;

    if (avcodec_send_frame(jpegCodecContext, frame) < 0) {//rgbFrame
        std::cerr << "Error sending frame to JPEG encoder" << std::endl;
        av_frame_free(&frame);
        av_frame_free(&rgbFrame);
        avcodec_free_context(&codecContext);
        avcodec_free_context(&jpegCodecContext);
        avformat_close_input(&formatContext);
        return -1;
    }

    if (avcodec_receive_packet(jpegCodecContext, &jpegPacket) < 0) {
        std::cerr << "Error receiving packet from JPEG encoder" << std::endl;
        av_frame_free(&frame);
        av_frame_free(&rgbFrame);
        avcodec_free_context(&codecContext);
        avcodec_free_context(&jpegCodecContext);
        avformat_close_input(&formatContext);
        return -1;
    }

    // 保存JPEG图像到文件
    FILE* outputFile = fopen(outputFilename.c_str(), "wb");
    if (outputFile == nullptr) {
        std::cerr << "Error opening output file" << std::endl;
        av_frame_free(&frame);
        av_frame_free(&rgbFrame);
        avcodec_free_context(&codecContext);
        avcodec_free_context(&jpegCodecContext);
        avformat_close_input(&formatContext);
        return -1;
    }

    fwrite(jpegPacket.data, 1, jpegPacket.size, outputFile);
    fclose(outputFile);

    // 清理资源
    av_frame_free(&frame);
    av_frame_free(&rgbFrame);
    av_packet_unref(&packet);
    av_packet_unref(&jpegPacket);
    avcodec_free_context(&codecContext);
    return 1;
}

获取的图片看上去不是太清晰,字有些糊掉了

从AVFrame保存为jpg图片的处理可以有另外的一个方式,有些差异,

cpp 复制代码
int savePicture(AVFrame* pFrame, char* out_name) {//编码保存图片

    int width = pFrame->width;
    int height = pFrame->height;
    AVCodecContext* pCodeCtx = NULL;


    AVFormatContext* pFormatCtx = avformat_alloc_context();
    // 设置输出文件格式
    pFormatCtx->oformat = av_guess_format("mjpeg", NULL, NULL);

    // 创建并初始化输出AVIOContext
    if (avio_open(&pFormatCtx->pb, out_name, AVIO_FLAG_READ_WRITE) < 0) {
        printf("Couldn't open output file.");
        return -1;
    }

    // 构建一个新stream
    AVStream* pAVStream = avformat_new_stream(pFormatCtx, 0);
    if (pAVStream == NULL) {
        return -1;
    }

    AVCodecParameters* parameters = pAVStream->codecpar;
    parameters->codec_id = pFormatCtx->oformat->video_codec;
    parameters->codec_type = AVMEDIA_TYPE_VIDEO;
    parameters->format = AV_PIX_FMT_YUVJ420P;
    parameters->width = pFrame->width;
    parameters->height = pFrame->height;

    const AVCodec* pCodec = avcodec_find_encoder(pAVStream->codecpar->codec_id);  //查找编码器

    if (!pCodec) {
        printf("Could not find encoder\n");
        return -1;
    }

    pCodeCtx = avcodec_alloc_context3(pCodec);   //为AVCodecContext分配内存
    if (!pCodeCtx) {
        fprintf(stderr, "Could not allocate video codec context\n");
        exit(1);
    }

    if ((avcodec_parameters_to_context(pCodeCtx, pAVStream->codecpar)) < 0) {
        fprintf(stderr, "Failed to copy %s codec parameters to decoder context\n",
            av_get_media_type_string(AVMEDIA_TYPE_VIDEO));
        return -1;
    }
  //  AVRational tmp = { 1, 25 };
    pCodeCtx->time_base = { 1, 25 };

    if (avcodec_open2(pCodeCtx, pCodec, NULL) < 0) {   //打开编码器
        printf("Could not open codec.");
        return -1;
    }

    int ret = avformat_write_header(pFormatCtx, NULL);
    if (ret < 0) {
        printf("write_header fail\n");
        return -1;
    }

    int y_size = width * height;

    //Encode
    // 给AVPacket分配足够大的空间
    AVPacket pkt;
    av_new_packet(&pkt, y_size * 3);

    // 编码数据
    ret = avcodec_send_frame(pCodeCtx, pFrame);
    if (ret < 0) {
        printf("Could not avcodec_send_frame.");
        return -1;
    }

    // 得到编码后数据
    ret = avcodec_receive_packet(pCodeCtx, &pkt);
    if (ret < 0) {
        printf("Could not avcodec_receive_packet");
        return -1;
    }

    ret = av_write_frame(pFormatCtx, &pkt);

    if (ret < 0) {
        printf("Could not av_write_frame");
        return -1;
    }

    av_packet_unref(&pkt);

    //Write Trailer
    av_write_trailer(pFormatCtx);


    avcodec_close(pCodeCtx);
    avio_close(pFormatCtx->pb);
    avformat_free_context(pFormatCtx);

    return 0;
}

参考资料

FFmpeg将视频转换成一帧一帧的jpeg图片(代码实现)_ffmpeg把视频转为一帧帧图片-CSDN博客

相关推荐
西***63473 小时前
声画合一 智控全场 —— 高清数字会议系统重构现代会议新生态
音视频·会议系统
REDcker4 小时前
RTSP 直播技术详解
linux·服务器·网络·音视频·实时音视频·直播·rtsp
微尘hjx4 小时前
【Gstreamer 应用程序开发手册 01】关于GSTREAMER
linux·音视频·媒体
石去皿5 小时前
轻量级 Web 应用 —— 把一堆图片按指定频率直接拼成视频,零特效、零依赖、零命令行
前端·音视频
runner365.git5 小时前
做一个基于ffmpeg的AI Agent智能体
人工智能·ffmpeg·大模型
进击的小头6 小时前
FIR滤波器实战:音频信号降噪
c语言·python·算法·音视频
Black蜡笔小新7 小时前
终结“监控盲区”:EasyGBS视频质量诊断技术多场景应用设计
人工智能·音视频·视频质量诊断
彷徨而立8 小时前
【FFmpeg】理解 av_packet_from_data 和 av_packet_unref 接口
ffmpeg
liliangcsdn10 小时前
视频嵌入表示生成方案的探索
数据库·人工智能·音视频
查无此人byebye10 小时前
深度解析:当前AI视频生成为何普遍“短小精悍”?
人工智能·pytorch·python·深度学习·音视频·transformer