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

FFmpeg获取视频关键帧并保存成jpg图像

1、命令行方式

1秒取1帧 r:rate

ffmpeg -i input.mp4 -f image2 -r 1  dstPath/image-%03d.jpg

提取I帧

ffmpeg -i input.mp4 -an -vf select='eq(pict_type\,I)' -vsync 2 -s 720*480 -f image2  dstPath/image-%03d.jpg

本文福利, 免费领取C++音视频学习资料包+学习路线大纲、技术视频/代码,内容包括(音视频开发,面试题,FFmpeg ,webRTC ,rtmp ,hls ,rtsp ,ffplay ,编解码,推拉流,srs)↓↓↓↓↓↓见下面↓↓文章底部点击免费领取↓↓

2、代码方式

提取I帧

//source: keyframe.cpp
#include <iostream>
#include <cstdio>
#include <cstring>#define __STDC_CONSTANT_MACROSextern "C"
{
#include <libavutil/imgutils.h>
#include <libavutil/samplefmt.h>
#include <libavutil/timestamp.h>
#include <libavutil/opt.h>
#include <libavcodec/avcodec.h>
#include <libavutil/channel_layout.h>
#include <libavutil/common.h>
#include <libavutil/imgutils.h>
#include <libavutil/mathematics.h>
#include <libavutil/samplefmt.h>
#include <libavutil/pixfmt.h>
#include <libavformat/avformat.h>
#include <libswscale/swscale.h>
#include <jpeglib.h>
}using namespace std;char errbuf[256];
char timebuf[256];
static AVFormatContext *fmt_ctx = NULL;
static AVCodecContext *video_dec_ctx = NULL;
static int width, height;
static enum AVPixelFormat pix_fmt;
static AVStream *video_stream = NULL;
static const char *src_filename = NULL;
static const char *output_dir = NULL;
static int video_stream_idx = -1;
static AVFrame *frame = NULL;
static AVFrame *pFrameRGB = NULL;
static AVPacket pkt;
static struct SwsContext *pSWSCtx = NULL;
static int video_frame_count = 0;/* Enable or disable frame reference counting. You are not supposed to support* both paths in your application but pick the one most appropriate to your* needs. Look for the use of refcount in this example to see what are the* differences of API usage between them. */
static int refcount = 0;
static void jpg_save(uint8_t *pRGBBuffer, int iFrame, int width, int height);static int decode_packet(int *got_frame, int cached)
{int ret = 0;int decoded = pkt.size;*got_frame = 0;if (pkt.stream_index == video_stream_idx){/* decode video frame */ret = avcodec_decode_video2(video_dec_ctx, frame, got_frame, &pkt);if (ret < 0){fprintf(stderr, "Error decoding video frame (%s)\n", av_make_error_string(errbuf, sizeof(errbuf), ret));return ret;}if (*got_frame){if (frame->width != width || frame->height != height ||frame->format != pix_fmt){/* To handle this change, one could call av_image_alloc again and* decode the following frames into another rawvideo file. */fprintf(stderr, "Error: Width, height and pixel format have to be ""constant in a rawvideo file, but the width, height or ""pixel format of the input video changed:\n""old: width = %d, height = %d, format = %s\n""new: width = %d, height = %d, format = %s\n",width, height, av_get_pix_fmt_name(pix_fmt),frame->width, frame->height,av_get_pix_fmt_name(frame->format));return -1;}video_frame_count++;static int iFrame = 0;if (frame->key_frame == 1) //如果是关键帧{sws_scale(pSWSCtx, frame->data, frame->linesize, 0,video_dec_ctx->height,pFrameRGB->data, pFrameRGB->linesize);// 保存到磁盘iFrame++;jpg_save(pFrameRGB->data[0], iFrame, width, height);}}}/* If we use frame reference counting, we own the data and need* to de-reference it when we don't use it anymore */if (*got_frame && refcount)av_frame_unref(frame);return decoded;
}static int open_codec_context(int *stream_idx,AVCodecContext **dec_ctx, AVFormatContext *fmt_ctx, enum AVMediaType type)
{int ret, stream_index;AVStream *st;AVCodec *dec = NULL;AVDictionary *opts = NULL;ret = av_find_best_stream(fmt_ctx, type, -1, -1, NULL, 0);if (ret < 0){fprintf(stderr, "Could not find %s stream in input file '%s'\n",av_get_media_type_string(type), src_filename);return ret;}else{stream_index = ret;st = fmt_ctx->streams[stream_index];/* find decoder for the stream */dec = avcodec_find_decoder(st->codecpar->codec_id);if (!dec){fprintf(stderr, "Failed to find %s codec\n",av_get_media_type_string(type));return AVERROR(EINVAL);}/* Allocate a codec context for the decoder */*dec_ctx = avcodec_alloc_context3(dec);if (!*dec_ctx){fprintf(stderr, "Failed to allocate the %s codec context\n",av_get_media_type_string(type));return AVERROR(ENOMEM);}/* Copy codec parameters from input stream to output codec context */if ((ret = avcodec_parameters_to_context(*dec_ctx, st->codecpar)) < 0){fprintf(stderr, "Failed to copy %s codec parameters to decoder context\n",av_get_media_type_string(type));return ret;}/* Init the decoders, with or without reference counting */av_dict_set(&opts, "refcounted_frames", refcount ? "1" : "0", 0);if ((ret = avcodec_open2(*dec_ctx, dec, &opts)) < 0){fprintf(stderr, "Failed to open %s codec\n",av_get_media_type_string(type));return ret;}*stream_idx = stream_index;}return 0;
}static int get_format_from_sample_fmt(const char **fmt, enum AVSampleFormat sample_fmt)
{int i;struct sample_fmt_entry{enum AVSampleFormat sample_fmt;const char *fmt_be, *fmt_le;} sample_fmt_entries[] = {{AV_SAMPLE_FMT_U8, "u8", "u8"},{AV_SAMPLE_FMT_S16, "s16be", "s16le"},{AV_SAMPLE_FMT_S32, "s32be", "s32le"},{AV_SAMPLE_FMT_FLT, "f32be", "f32le"},{AV_SAMPLE_FMT_DBL, "f64be", "f64le"},};*fmt = NULL;for (i = 0; i < FF_ARRAY_ELEMS(sample_fmt_entries); i++){struct sample_fmt_entry *entry = &sample_fmt_entries[i];if (sample_fmt == entry->sample_fmt){*fmt = AV_NE(entry->fmt_be, entry->fmt_le);return 0;}}fprintf(stderr,"sample format %s is not supported as output format\n",av_get_sample_fmt_name(sample_fmt));return -1;
}int main(int argc, char **argv)
{int ret = 0, got_frame;int numBytes = 0;uint8_t *buffer;if (argc != 3 && argc != 4){fprintf(stderr, "usage: %s [-refcount] input_file ouput_dir\n""API example program to show how to read frames from an input file.\n""This program reads frames from a file, decodes them, and writes bmp keyframes\n""If the -refcount option is specified, the program use the\n""reference counting frame system which allows keeping a copy of\n""the data for longer than one decode call.\n""\n",argv[0]);exit(1);}if (argc == 4 && !strcmp(argv[1], "-refcount")){refcount = 1;argv++;}src_filename = argv[1];output_dir = argv[2];/* open input file, and allocate format context */if (avformat_open_input(&fmt_ctx, src_filename, NULL, NULL) < 0){fprintf(stderr, "Could not open source file %s\n", src_filename);exit(1);}/* retrieve stream information */if (avformat_find_stream_info(fmt_ctx, NULL) < 0){fprintf(stderr, "Could not find stream information\n");exit(1);}if (open_codec_context(&video_stream_idx, &video_dec_ctx, fmt_ctx, AVMEDIA_TYPE_VIDEO) >= 0){video_stream = fmt_ctx->streams[video_stream_idx];/* allocate image where the decoded image will be put */width = video_dec_ctx->width;height = video_dec_ctx->height;pix_fmt = video_dec_ctx->pix_fmt;}else{goto end;}/* dump input information to stderr */av_dump_format(fmt_ctx, 0, src_filename, 0);if (!video_stream){fprintf(stderr, "Could not find video stream in the input, aborting\n");ret = 1;goto end;}pFrameRGB = av_frame_alloc();numBytes = avpicture_get_size(AV_PIX_FMT_BGR24, width, height);buffer = av_malloc(numBytes);avpicture_fill((AVPicture *)pFrameRGB, buffer, AV_PIX_FMT_BGR24, width, height);pSWSCtx = sws_getContext(width, height, pix_fmt, width, height, AV_PIX_FMT_RGB24, SWS_BICUBIC, NULL, NULL, NULL);frame = av_frame_alloc();if (!frame){fprintf(stderr, "Could not allocate frame\n");ret = AVERROR(ENOMEM);goto end;}/* initialize packet, set data to NULL, let the demuxer fill it */av_init_packet(&pkt);pkt.data = NULL;pkt.size = 0;if (video_stream)printf("Demuxing video from file '%s' to dir: %s\n", src_filename, output_dir);/* read frames from the file */while (av_read_frame(fmt_ctx, &pkt) >= 0){AVPacket orig_pkt = pkt;do{ret = decode_packet(&got_frame, 0);if (ret < 0)break;pkt.data += ret;pkt.size -= ret;} while (pkt.size > 0);av_packet_unref(&orig_pkt);}/* flush cached frames */pkt.data = NULL;pkt.size = 0;end:if (video_dec_ctx)avcodec_free_context(&video_dec_ctx);if (fmt_ctx)avformat_close_input(&fmt_ctx);if (buffer)av_free(buffer);if (pFrameRGB)av_frame_free(&pFrameRGB);if (frame)av_frame_free(&frame);return ret < 0;
}static void jpg_save(uint8_t *pRGBBuffer, int iFrame, int width, int height)
{struct jpeg_compress_struct cinfo;struct jpeg_error_mgr jerr;char szFilename[1024];int row_stride;FILE *fp;JSAMPROW row_pointer[1]; // 一行位图cinfo.err = jpeg_std_error(&jerr);jpeg_create_compress(&cinfo);sprintf(szFilename, "%s/image-%03d.jpg", output_dir, iFrame); //图片名字为视频名+号码fp = fopen(szFilename, "wb");if (fp == NULL)return;jpeg_stdio_dest(&cinfo, fp);cinfo.image_width = width; // 为图的宽和高,单位为像素cinfo.image_height = height;cinfo.input_components = 3;     // 在此为1,表示灰度图, 如果是彩色位图,则为3cinfo.in_color_space = JCS_RGB; //JCS_GRAYSCALE表示灰度图,JCS_RGB表示彩色图像jpeg_set_defaults(&cinfo);jpeg_set_quality(&cinfo, 80, 1);jpeg_start_compress(&cinfo, TRUE);row_stride = cinfo.image_width * 3; //每一行的字节数,如果不是索引图,此处需要乘以3// 对每一行进行压缩while (cinfo.next_scanline < cinfo.image_height){row_pointer[0] = &(pRGBBuffer[cinfo.next_scanline * row_stride]);jpeg_write_scanlines(&cinfo, row_pointer, 1);}jpeg_finish_compress(&cinfo);jpeg_destroy_compress(&cinfo);fclose(fp);
}
cat Makefile
keyframe:keyframe.cppg++ $< -o $@ `pkg-config --libs libavcodec libavformat libswscale libavutil` -ljpeg -fpermissive

本文福利, 免费领取C++音视频学习资料包+学习路线大纲、技术视频/代码,内容包括(音视频开发,面试题,FFmpeg ,webRTC ,rtmp ,hls ,rtsp ,ffplay ,编解码,推拉流,srs)↓↓↓↓↓↓见下面↓↓文章底部点击免费领取↓↓

相关文章:

  • Centos, RockyLinux 常用软件安装汇总
  • 使用数据泵的注意事项
  • k8s自定义Endpoint实现内部pod访问外部应用
  • 307.区域和检索
  • Idea 编译SpringBoot项目Kotlin报错/Idea重新编译
  • YOLOv8-Seg改进:卷积变体系列篇 | DCNv3可形变卷积基于DCNv2优化 | CVPR2023
  • 2001-2022年全国平均气温数据,逐月数据均有
  • 鼎捷PLM:引领国产替代,创造极致体验,探索数字化研发可行之路
  • 【图论实战】 Boost学习 03:dijkstra_shortest_paths
  • 数据库MHA高可用
  • 浪潮服务器安装操作系统
  • 【iOS】JSONModel的基本使用
  • PyCharm鼠标控制字体缩放
  • 阿里云centos7.9乱码问题
  • 【C++】——运算符重载
  • CNN 在图像分割中的简史:从 R-CNN 到 Mask R-CNN
  • CSS中外联样式表代表的含义
  • gitlab-ci配置详解(一)
  • input实现文字超出省略号功能
  • laravel5.5 视图共享数据
  • PHP 使用 Swoole - TaskWorker 实现异步操作 Mysql
  • Redux系列x:源码分析
  • uva 10370 Above Average
  • windows下使用nginx调试简介
  • 大型网站性能监测、分析与优化常见问题QA
  • 动态魔术使用DBMS_SQL
  • 前端相关框架总和
  • 如何实现 font-size 的响应式
  • 我与Jetbrains的这些年
  • 小试R空间处理新库sf
  • 新版博客前端前瞻
  • AI又要和人类“对打”,Deepmind宣布《星战Ⅱ》即将开始 ...
  • TPG领衔财团投资轻奢珠宝品牌APM Monaco
  • 组复制官方翻译九、Group Replication Technical Details
  • #QT(串口助手-界面)
  • #WEB前端(HTML属性)
  • (3)nginx 配置(nginx.conf)
  • (70min)字节暑假实习二面(已挂)
  • (delphi11最新学习资料) Object Pascal 学习笔记---第5章第5节(delphi中的指针)
  • (PWM呼吸灯)合泰开发板HT66F2390-----点灯大师
  • (pytorch进阶之路)扩散概率模型
  • (ZT)北大教授朱青生给学生的一封信:大学,更是一个科学的保证
  • (安全基本功)磁盘MBR,分区表,活动分区,引导扇区。。。详解与区别
  • (二)JAVA使用POI操作excel
  • (附源码)springboot码头作业管理系统 毕业设计 341654
  • (六)c52学习之旅-独立按键
  • (学习日记)2024.01.09
  • (原創) 博客園正式支援VHDL語法著色功能 (SOC) (VHDL)
  • (转)程序员疫苗:代码注入
  • (转载)VS2010/MFC编程入门之三十四(菜单:VS2010菜单资源详解)
  • *ST京蓝入股力合节能 着力绿色智慧城市服务
  • ./configure,make,make install的作用(转)
  • .bashrc在哪里,alias妙用
  • .net framework profiles /.net framework 配置
  • .net framwork4.6操作MySQL报错Character set ‘utf8mb3‘ is not supported 解决方法