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

从视频中获取图片是一个比较直观的例子,这里从一个基础的例子来查看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.mfbz.cn/a/385723.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【杂谈】扣子(Coze) 初体验

扣子(Coze)是什么 官方原文如下&#xff1a; 扣子&#xff08;coze.cn&#xff09;是一款用来开发新一代 AI Chat Bot 的应用编辑平台&#xff0c;无论你是否有编程基础&#xff0c;都可以通过这个平台来快速创建各种类型的 Chat Bot&#xff0c;并将其发布到各类社交平台和通…

B2084 质因数分解

题目描述 已知正整数 n 是两个不同的质数的乘积&#xff0c;试求出较大的那个质数。 输入格式 输入只有一行&#xff0c;包含一个正整数 n&#xff08;6<n<&#xff09;。 输出格式 输出只有一行&#xff0c;包含一个正整数 p&#xff0c;即较大的那个质数。 输入输…

Java 基于 SpringBoot+Vue 的社区医院系统

博主介绍&#xff1a;✌程序员徐师兄、7年大厂程序员经历。全网粉丝12w、csdn博客专家、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战✌ &#x1f345;文末获取源码联系&#x1f345; &#x1f447;&#x1f3fb; 精彩专栏推荐订阅&#x1f447;…

《Java 简易速速上手小册》第8章:Java 性能优化(2024 最新版)

文章目录 8.1 性能评估工具 - 你的性能探测仪8.1.1 基础知识8.1.2 重点案例&#xff1a;使用 VisualVM 监控应用性能8.1.3 拓展案例 1&#xff1a;使用 JProfiler 分析内存泄漏8.1.4 拓展案例 2&#xff1a;使用 Gatling 进行 Web 应用压力测试 8.2 JVM 调优 - 魔法引擎的调校8…

根据Ruoyi做二开

Ruoyi二开 前言菜单代码生成总结 前言 之前写过一篇文章&#xff0c;若依微服务版本搭建&#xff0c;超详细&#xff0c;就介绍了怎么搭建若依微服务版本&#xff0c;我们使用若依就是为了简化我们的开发&#xff0c;减少开发周期的&#xff0c;这篇文章就会介绍怎么使用若依进…

大型社区门口适合开什么店?商业趋势与消费需求分析

作为一名资深的鲜奶吧创业者&#xff0c;我在这个行业已经摸爬滚打了五年。期间&#xff0c;我见证了社区商业的蓬勃发展&#xff0c;也深刻体会到了选址对于店铺经营的重要性。 这篇文章&#xff0c;我和大家分享一下我的见解&#xff0c;探讨一下大型社区门口适合开什么店&a…

软件实例分享,茶楼收银软件管理系统,支持计时计费商品销售会员管理定时语音提醒功能

软件实例分享&#xff0c;茶楼收银软件管理系统&#xff0c;支持计时计费商品销售会员管理定时语音提醒功能 一、前言 以下软件教程以 佳易王茶社计时计费管理系统软件V18.0为例说明 软件文件下载可以点击最下方官网卡片——软件下载——试用版软件下载 问&#xff1a;这个软…

AI绘画作品的展示和变现-2

4.7 制作红包封面 中国的节日和传统文化元素仍然可以成为创作者们的创作灵感&#xff0c;创造出更多的变现机会。比如元宵节&#xff0c;可以制作大型元宵图案&#xff0c;进行引流并卖出元宵。 而春分、谷雨等节气也可以成为创作的灵感来源&#xff0c;创作出与之相关的图案&…

Java实现音乐平台 JAVA+Vue+SpringBoot+MySQL

目录 一、摘要1.1 项目介绍1.2 项目录屏 二、功能模块三、系统展示 四、核心代码4.1 查询单首音乐4.2 新增音乐4.3 新增音乐订单4.4 查询音乐订单4.5 新增音乐收藏 五、免责说明 一、摘要 1.1 项目介绍 基于微信小程序JAVAVueSpringBootMySQL的音乐平台&#xff0c;包含了音乐…

Go+:一种简单而强大的编程语言

Go是一种简单而强大的编程语言&#xff0c;它是在Go语言之上构建的&#xff0c;旨在提供更加强大、灵活和易于使用的编程体验。Go与Go语言共享大部分语法和语义&#xff0c;因此Go开发人员可以很快上手Go&#xff0c;同时也可以使用Go来编写更加简洁和高效的代码。在本文中&…

蓝桥杯嵌入式第11届真题(完成) STM32G431

蓝桥杯嵌入式第11届真题(完成) STM32G431 题目 代码 程序和之前的大同小异&#xff0c;不过多解释 main.c /* USER CODE BEGIN Header */ /********************************************************************************* file : main.c* brief :…

Windows搭建docker+k8s

安装Docker Desktop 从官网下载&#xff0c;然后直接安装即可&#xff0c;过程很简单&#xff0c;一直Next就行。 有一点需要注意就是要看好对应的版本&#xff0c;因为后边涉及到版本的问题。 https://www.docker.com/products/docker-desktop 安装完成&#xff0c;双击图…

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

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

【深度学习】S2 数学基础 P1 线性代数(上)

目录 基本数学对象标量与变量向量矩阵张量降维求和非降维求和累计求和 点积与向量积点积矩阵-向量积矩阵-矩阵乘法 深度学习的三大数学基础 —— 线性代数、微积分、概率论&#xff1b; 自本篇博文以下几遍博文&#xff0c;将对这三大数学基础进行重点提炼。 本节博文将介绍线…

涛哥聊Python | pymunk,一个强大的 Python 库!

本文来源公众号“涛哥聊Python”&#xff0c;仅用于学术分享&#xff0c;侵权删&#xff0c;干货满满。 原文链接&#xff1a;pymunk&#xff0c;一个强大的 Python 库&#xff01; 大家好&#xff0c;今天为大家分享一个强大的 Python 库 - pymunk。 Github地址&#xff1a;…

计算机二级C语言的注意事项及相应真题-4-程序修改

目录&#xff1a; 31.逐个比较p、q所指两个字符串对应位置中的字符&#xff0c;把ASCII值大或相等的字符依次存放到c所指数组中&#xff0c;形成一个新的字符串32.求矩阵&#xff08;二维数组)a[N][N]中每行的最小值&#xff0c;结果存放到数组b中33.将一个十进制整数转换成r(二…

Vulnhub靶场 DC-9

目录 一、环境搭建 二、信息收集 1、主机发现 2、指纹识别 三、漏洞复现 1、dirsearch目录探测 2、sqlmap注入测试 3、文件包含漏洞 4、Knockd敲门服务 5、ssh爆破 ​​​​​​​6、提权 四、提取flag 一、环境搭建 Vulnhub靶机下载&#xff1a; 官网地址&#xff1a;https://…

Spring Boot 笔记 007 创建接口_登录

1.1 登录接口需求 1.2 JWT令牌 1.2.1 JWT原理 1.2.2 引入JWT坐标 1.2.3 单元测试 1.2.3.1 引入springboot单元测试坐标 1.2.3.2 在单元测试文件夹中创建测试类 1.2.3.3 运行测试类中的生成和解析方法 package com.geji;import com.auth0.jwt.JWT; import com.auth0.jwt.JWTV…

2.第一个Electron程序

目录 一、前言二、基本运行结构三、代码详解四、打包 一、前言 原文以及系列文章后续请参考&#xff1a;第一个Electron程序 上一章我们完成了Electron的环境搭建&#xff0c;本章就开始详解如何使用Electron开发一个完整的Electron桌面端程序。 注意开发环境&#xff0c;个…

可视化工具:将多种数据格式转化为交互式图形展示的利器

引言 在数据驱动的时代&#xff0c;数据的分析和理解对于决策过程至关重要。然而&#xff0c;不同的数据格式和结构使得数据的解读变得复杂和困难。为了解决这个问题&#xff0c;一种强大的可视化工具应运而生。这个工具具有将多种数据格式&#xff08;包括JSON、YAML、XML、C…
最新文章