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

news2024/11/28 20:56:57

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

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

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图片的处理可以有另外的一个方式,有些差异,

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博客

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1448184.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

六、Redis之数据持久化及高频面试题

6.1 数据持久化 官网文档地址&#xff1a;https://redis.io/docs/manual/persistence/ Redis提供了主要提供了 2 种不同形式的持久化方式&#xff1a; RDB&#xff08;Redis数据库&#xff09;&#xff1a;RDB 持久性以指定的时间间隔执行数据集的时间点快照。AOF&#xff0…

车载诊断协议DoIP系列 —— DoIP会话模式(安全与非安全)

车载诊断协议DoIP系列 —— DoIP会话模式(安全与非安全) 我是穿拖鞋的汉子,魔都中坚持长期主义的汽车电子工程师(Wechat:gongkenan2013)。 老规矩,分享一段喜欢的文字,避免自己成为高知识低文化的工程师: 本就是小人物,输了就是输了,不要在意别人怎么看自己。江湖…

LLVM的中间表示

概括 选择编译器IR的决策很重要&#xff0c;它决定了优化过程将拥有多少信息来使代码运行得更快。 一方面非常高层级的IR允许优化器轻松地提取原始源代码的相关信息。 另一方面&#xff0c;低层的IR更加贴近目标机器&#xff0c;这样编译器更容易为特定的硬件生成相应的代码…

MyBatis篇----第三篇

系列文章目录 文章目录 系列文章目录前言一、模糊查询 like 语句该怎么写?二、如何获取自动生成的(主)键值三、在 mapper 中如何传递多个参数?前言 前些天发现了一个巨牛的人工智能学习网站,通俗易懂,风趣幽默,忍不住分享一下给大家。点击跳转到网站,这篇文章男女通用,…

Golang中的fmt包:格式化输入输出的利器

Golang中的fmt包&#xff1a;格式化输入输出的利器 在软件开发的世界里&#xff0c;fmt包就像是一位忠实的伙伴&#xff0c;始终陪伴着开发人员。它简化了格式化输入输出的过程&#xff0c;让打印和扫描数据变得轻松自如。无论是向控制台输出简单的消息&#xff0c;还是处理复杂…

零基础学Python(10)— 序列通用操作

前言&#xff1a;Hello大家好&#xff0c;我是小哥谈。本节课就带大家认识下Python语言中常见的序列通用操作&#xff01;~&#x1f308; 目录 &#x1f680;1.索引 &#x1f680;2.切片 &#x1f680;3.序列加法 &#x1f680;4.序列乘法 &#x1f680;5.检查某个元素是…

React18原理: 时间分片技术选择

渲染1w个节点的不同方式 1 &#xff09;案例1&#xff1a;一次渲染1w个节点 <div idroot><div><script type"text/javascript">function randomHexColor() {return "#" ("0000" (Math.random() * 0x1000000 << 0).toS…

在Docker中安装ubuntu镜像并安装Vim,将镜像命名

1、运行环境&#xff0c;腾讯云Ubuntu Server 22.04 LTS 64bit。默认用户名ubuntu&#xff0c;密码自己设置的啥写啥呗。 2、在docker中安装。 sudo docker pull ubuntu 3、查看docker中的镜像。 sudo docker images 4、 以交互模式进入容器。 sudo docker run -it ubuntu…

幻兽帕鲁服务器配置参数说明(Palworld官方汉化)

创建幻兽帕鲁服务器配置参数说明&#xff0c;Palworld服务器配置参数与解释&#xff0c;阿腾云atengyun.com分享&#xff1a; 自建幻兽帕鲁服务器教程&#xff1a; 阿里云教程 https://t.aliyun.com/U/bLynLC腾讯云教程 https://curl.qcloud.com/oRMoSucP 幻兽帕鲁服务器 幻…

Git中Idea操作git及Git Flow

目录 一、Idea中使用Git 1.idea配置Git和Gitee 2.实践操作 1.将本地项目推送到远程 2.从远程库克隆项目到本地 二、Git Flow 1.什么是Git Flow 2.工作流程 3.实践操作 一、Idea中使用Git 1.idea配置Git和Gitee 第一步&#xff1a;设置git.exe的安装路径 在设置中的…

预处理详解(上)

1. 预定义符号 C语言设置了一些预定义符号&#xff0c;可以直接使用&#xff0c;预定义符号也是在预处理期间处理的。 __FILE__ //进行编译的源文件 __LINE__ //文件当前的行号 __DATE__ //文件被编译的日期 __TIME__ //文件被编译的时间 __STDC__ //如果编译器遵循ANSIC&…

Linux--目录结构

目录 一、Linux的目录结构二、常用的目录介绍 一、Linux的目录结构 Linux的目录结构是一个树型结构。 Windos 系统可以拥有多个盘符&#xff0c;如C盘&#xff0c;D盘,E盘。 Linux 木有盘符这个概念&#xff0c;只有一个根目录 /&#xff08;相当于文件夹&#xff09;&#xf…

掌握C语言文件操作:从入门到精通的完整指南!

✨✨ 欢迎大家来到贝蒂大讲堂✨✨ &#x1f388;&#x1f388;养成好习惯&#xff0c;先赞后看哦~&#x1f388;&#x1f388; 所属专栏&#xff1a;C语言学习 贝蒂的主页&#xff1a;Betty‘s blog 1. 什么是文件 文件其实是指一组相关数据的有序集合。这个数据集有一个名称&a…

MPLAB V8.92 printf

Compile error “A heap is required, but has not been specified” Set printf function #if 0 //for UART1 int fputc(int ch, FILE *f) { IFS1bits.U2TXIF 0; // if (runConfig.printOn 1) { // usart_data_transmit(USART0, (uint8_t)ch); U2TXREG ch; // while (RESE…

二分查找算法及其简单应用(C语言实现)

目录 二分查找算法介绍 二分查找算法的简单应用 二分查找算法介绍 二分查找算法&#xff0c;指在一组有序的数组内查找数值&#xff0c;查找的数值与查找范围内的中间数值进行比较&#xff0c;如果比中间数值小&#xff0c;则在原范围内的左侧范围内重复与该范围内的中间数值…

RK3568笔记十四:yolov8pose部署

若该文为原创文章&#xff0c;转载请注明原文出处。 本篇参考山水无移大佬文章&#xff0c;并成功部署了yolov8pose在RK3568板子上&#xff0c;这里记录下全过程。 在此特感谢所有分享的大佬&#xff0c;底部附大佬的链接。 一、环境 1、平台&#xff1a;rk3568 2、开发板: …

微信小程序 民宿预订租赁系统uniApp

通过山青水磨APP办理租房相关业务&#xff0c;线上解决预定、退订的业务&#xff0c;旅客在使用时更加灵活&#xff0c;实现了快速找房&#xff0c;在线沟通、便捷租赁等操作&#xff0c;除此以外&#xff0c;还能帮助旅客获取周边资讯、当地特色活动服务&#xff0c;提升旅客的…

C++,stl,函数对象,谓词,内建函数对象

目录 1.函数对象使用 2.谓词 1.一元谓词 2.二元谓词 3.内建函数对象 1.算术仿函数 2.关系仿函数 3.逻辑仿函数 1.函数对象使用 #include<bits/stdc.h> using namespace std;class add { public:int operator()(int v1,int v2){return v1 v2;} };class print { p…

origin技巧

origin技巧 1.去掉白边2.曲线平滑3.合并多层图例3.图例换方向 1.去掉白边 ctrlu 2.曲线平滑 3.合并多层图例 3.图例换方向 图例右键 “图例” 水平排布修改图例字&#xff1a;双击图例修改 https://blog.csdn.net/m0_47746156/article/details/121295151 https://blog.csdn.…

【网站项目】023实验室耗材管理系统

&#x1f64a;作者简介&#xff1a;拥有多年开发工作经验&#xff0c;分享技术代码帮助学生学习&#xff0c;独立完成自己的项目或者毕业设计。 代码可以私聊博主获取。&#x1f339;赠送计算机毕业设计600个选题excel文件&#xff0c;帮助大学选题。赠送开题报告模板&#xff…