当前位置: 首页 > news >正文

[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) {//rgbFramestd::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);// 创建并初始化输出AVIOContextif (avio_open(&pFormatCtx->pb, out_name, AVIO_FLAG_READ_WRITE) < 0) {printf("Couldn't open output file.");return -1;}// 构建一个新streamAVStream* 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 Trailerav_write_trailer(pFormatCtx);avcodec_close(pCodeCtx);avio_close(pFormatCtx->pb);avformat_free_context(pFormatCtx);return 0;
}

参考资料

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

相关文章:

  • 机器学习:Pooling层作用及反向传播
  • 【前端高频面试题--git篇】
  • python从入门到精通(十六):python爬虫的BeautifulSoup4
  • 自动化专业英语词汇积累【第一期】
  • npm install 安装依赖如何加速
  • 软件测试-测试用例研究-如何编写一份优秀的测试用例
  • 力扣[面试题 01.02. 判定是否互为字符重排(哈希表,位图)
  • java中事务的使用
  • JVM(2)实战篇
  • Redis相关介绍
  • 【PyTorch】改变张量(Tensor)形状操作
  • 2. Maven 继承与聚合
  • 小游戏和GUI编程(4) | 基于 SFML 的黑客帝国字符雨
  • 机器学习3----决策树
  • Android java基础_多态性
  • [LeetCode] Wiggle Sort
  • 《Java8实战》-第四章读书笔记(引入流Stream)
  • 【面试系列】之二:关于js原型
  • 2017前端实习生面试总结
  • GraphQL学习过程应该是这样的
  • Iterator 和 for...of 循环
  • leetcode-27. Remove Element
  • leetcode讲解--894. All Possible Full Binary Trees
  • Less 日常用法
  • Linux编程学习笔记 | Linux IO学习[1] - 文件IO
  • miniui datagrid 的客户端分页解决方案 - CS结合
  • Python中eval与exec的使用及区别
  • Vue官网教程学习过程中值得记录的一些事情
  • 对象引论
  • 精益 React 学习指南 (Lean React)- 1.5 React 与 DOM
  • 无服务器化是企业 IT 架构的未来吗?
  • 用jquery写贪吃蛇
  • 鱼骨图 - 如何绘制?
  • 这几个编码小技巧将令你 PHP 代码更加简洁
  • elasticsearch-head插件安装
  • # 再次尝试 连接失败_无线WiFi无法连接到网络怎么办【解决方法】
  • ###STL(标准模板库)
  • #vue3 实现前端下载excel文件模板功能
  • (11)工业界推荐系统-小红书推荐场景及内部实践【粗排三塔模型】
  • (翻译)Quartz官方教程——第一课:Quartz入门
  • (附源码)springboot猪场管理系统 毕业设计 160901
  • (机器学习-深度学习快速入门)第一章第一节:Python环境和数据分析
  • (六)c52学习之旅-独立按键
  • (四)linux文件内容查看
  • (学习日记)2024.04.10:UCOSIII第三十八节:事件实验
  • (已更新)关于Visual Studio 2019安装时VS installer无法下载文件,进度条为0,显示网络有问题的解决办法
  • *++p:p先自+,然后*p,最终为3 ++*p:先*p,即arr[0]=1,然后再++,最终为2 *p++:值为arr[0],即1,该语句执行完毕后,p指向arr[1]
  • .Net 8.0 新的变化
  • .net core使用RPC方式进行高效的HTTP服务访问
  • .NET企业级应用架构设计系列之技术选型
  • .net最好用的JSON类Newtonsoft.Json获取多级数据SelectToken
  • .secret勒索病毒数据恢复|金蝶、用友、管家婆、OA、速达、ERP等软件数据库恢复
  • @JsonSerialize注解的使用
  • [100天算法】-目标和(day 79)
  • [AIR] NativeExtension在IOS下的开发实例 --- IOS项目的创建 (一)