I帧转图片(BMP、PPM、JPG)

192 篇文章 ¥19.90 ¥99.00
这段代码展示了如何使用ffmpeg库获取视频的第一个I帧,并将其转换为BMP、PPM和JPG三种格式的图片。首先,代码初始化并打开输入视频文件,找到视频流,然后解码首个I帧。接着,通过swscale库将帧数据转换为RGB格式,最后保存为指定格式的图片文件。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

// 使用ffmpeg取得视频的首个I帧,并转换为图片
// 图片格式可以为BMP、PPM、JPG

// 头文件CGetFirstIFrameToPic.h

[cpp]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. #if !defined (_C_GET_FIRST_IFRAME_TO_PIC_H_)  
  2. #define _C_GET_FIRST_IFRAME_TO_PIC_H_  
  3.   
  4. extern "C"  
  5. {  
  6. #include "libavcodec/avcodec.h"  
  7. #include "libavformat/avformat.h"  
  8. #include "libswscale/swscale.h"  
  9. #include "jpeglib.h"  
  10. }  
  11.   
  12.   
  13. // 取得视频的首个I帧,并转换为图片  
  14. // 图片格式可以为BMP、PPM、JPG  
  15. class CGetFirstIFrameToPic  
  16. {  
  17. public:  
  18.     CGetFirstIFrameToPic(){};  
  19.     virtual ~CGetFirstIFrameToPic(){};  
  20.   
  21.     // 取得视频的首个I帧,并转换为图片  
  22.     virtual int GetFirstIFrameAndConvertToPic(char *pVideoFile, char *pPicFile);  
  23.     // 保存帧数据为BMP图片  
  24.     virtual int SaveFrameToBMP(char *pPicFile, uint8_t *pRGBBuffer, int nWidth, int nHeight, int nBitCount);  
  25.     // 保存帧数据为PPM图片  
  26.     virtual int SaveFrameToPPM(char *pPicFile, AVFrame *pFrame, int nWidth, int nHeight);  
  27.     // 保存帧数据为JPG图片  
  28.     virtual int SaveFrameToJPG(char *pPicFile, uint8_t *pRGBBuffer, int nWidth, int nHeight);  
  29. };  
  30.   
  31. #endif //!defined(_C_GET_FIRST_IFRAME_TO_PIC_H_)  


// 取得视频的首个I帧,并转换为图片

[cpp]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. #include "stdafx.h"  
  2. #include "CGetFirstIFrameToPic.h"  
  3.   
  4. // 取得视频的首个I帧,并转换为图片  
  5. int CGetFirstIFrameToPic::GetFirstIFrameAndConvertToPic(char *pVideoFile, char *pPicFile)  
  6. {  
  7.     int nRet = 0;  
  8.   
  9.     if((NULL == pVideoFile) || (NULL == pPicFile))  
  10.     {  
  11.         return -1;  
  12.     }  
  13.   
  14.     // 注册所有的文件格式和编解码器的库  
  15.     av_register_all();  
  16.   
  17.     AVFormatContext *pFormatCtxDec = NULL; // 视频流的格式内容  
  18.     // 读取文件的头部并且把信息保存到我们给的AVFormatContext结构  
  19.     nRet = avformat_open_input(&pFormatCtxDec, pVideoFile, NULL, NULL);  
  20.     if(nRet != 0)  
  21.     {  
  22.         printf("Couldn't open file %s.\n", pVideoFile);  
  23.         return -1;  
  24.     }  
  25.   
  26.     // 检查在文件中的流的信息  
  27.     nRet = avformat_find_stream_info(pFormatCtxDec, NULL);  
  28.     if(nRet < 0)  
  29.     {  
  30.         printf("Couldn't find stream information.\n");  
  31.         return -1;  
  32.     }  
  33.   
  34.     // 找到第一个视频流  
  35.     int nVideoStream = -1;  
  36.     for(unsigned int i = 0; i < pFormatCtxDec->nb_streams; i++)   
  37.     {  
  38.         if(pFormatCtxDec->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO)  
  39.         {  
  40.             nVideoStream = i;  
  41.             break;  
  42.         }  
  43.     }  
  44.     if(nVideoStream == -1)  
  45.     {  
  46.         printf("Didn't find a video stream.\n");  
  47.         return -1;  
  48.     }  
  49.     printf("nVideoStream:%d.\n", nVideoStream);  
  50.   
  51.     // 流中关于编解码器的信息就是被我们叫做"codec context"(编解码器上下文)的东西。  
  52.     // 这里面包含了流中所使用的关于编解码器的所有信息,现在我们有了一个指向他的指针。  
  53.     // 但是我们必需要找到真正的编解码器并且打开它  
  54.     AVCodecContext *pCodecCtxDec = NULL;  // 编解码器上下文  
  55.     // Get a pointer to the codec context for the video stream  
  56.     pCodecCtxDec = pFormatCtxDec->streams[nVideoStream]->codec;  
  57.   
  58.     AVCodec *pCodecDec = NULL;   
  59.     // Find the decoder for the video stream  
  60.     pCodecDec = avcodec_find_decoder(pCodecCtxDec->codec_id);  
  61.     if(pCodecDec==NULL)  
  62.     {  
  63.         printf("Codec not found.\n");  
  64.         return -1;  
  65.     }  
  66.   
  67.     // Open codec  
  68.     nRet = avcodec_open2(pCodecCtxDec, pCodecDec, NULL);  
  69.     if(nRet < 0)  
  70.     {  
  71.         printf("Could not open codec.\n");  
  72.         return -1;  
  73.     }  
  74.   
  75.     AVFrame *pFrameDec = NULL;  
  76.     // Allocate video frame  
  77.     pFrameDec = avcodec_alloc_frame();  
  78.     if(pFrameDec == NULL)  
  79.     {  
  80.         printf("Allocate video frame error.\n");  
  81.         return -1;  
  82.     }  
  83.   
  84.     // 因为我们准备输出保存24位RGB色的PPM文件,我们必需把帧的格式从原来的转换为RGB。  
  85.     // FFMPEG将为我们做这些转换。  
  86.     // 在大多数项目中(包括我们的这个)我们都想把原始的帧转换成一个特定的格式。  
  87.     // 让我们先为转换来申请一帧的内存  
  88.     AVFrame *pFrameRGB = NULL;  
  89.     // Allocate an AVFrame structure  
  90.     pFrameRGB = avcodec_alloc_frame();  
  91.     if(pFrameRGB == NULL)  
  92.     {  
  93.         printf("Allocate an AVFrame structure error.\n");  
  94.         return -1;  
  95.     }  
  96.   
  97.     // 即使我们申请了一帧的内存,当转换的时候,我们仍然需要一个地方来放置原始的数据。  
  98.     // 我们使用avpicture_get_size来获得我们需要的大小,然后手工申请内存空间:  
  99.     uint8_t *pBuffer;  
  100.     int numBytes;  
  101.     // Determine required buffer size and allocate buffer AV_PIX_FMT_RGB24  
  102.     numBytes = avpicture_get_size(AV_PIX_FMT_RGB24, pCodecCtxDec->width, pCodecCtxDec->height);  
  103.     pBuffer=(uint8_t *)av_malloc(numBytes * sizeof(uint8_t));  
  104.   
  105.     // 现在我们使用avpicture_fill来把帧和我们新申请的内存来结合。  
  106.     // 关于AVPicture的结成:AVPicture结构体是AVFrame结构体的子集  
  107.     // ――AVFrame结构体的开始部分与AVPicture结构体是一样的。  
  108.     // Assign appropriate parts of buffer to image planes in pFrameRGB  
  109.     // Note that pFrameRGB is an AVFrame, but AVFrame is a superset of AVPicture  
  110.     avpicture_fill((AVPicture *)pFrameRGB, pBuffer, AV_PIX_FMT_RGB24,   
  111.         pCodecCtxDec->width, pCodecCtxDec->height);  
  112.   
  113.     // 最后,我们已经准备好来从流中读取数据了。  
  114.     // 读取数据  
  115.     // 我们将要做的是通过读取包来读取整个视频流,  
  116.     // 然后把它解码成帧,最好后转换格式并且保存。  
  117.     int frameFinished = 0;  
  118.     AVPacket packet;  
  119.   
  120.     static struct SwsContext *img_convert_ctx;  
  121.     while(true)   
  122.     {  
  123.         av_init_packet(&packet);  
  124.         nRet = av_read_frame(pFormatCtxDec, &packet);  
  125.         if(nRet < 0)  
  126.         {  
  127.             break;  
  128.         }  
  129.         // Is this a packet from the video stream?  
  130.         if(packet.stream_index == nVideoStream)   
  131.         {  
  132.             // Decode video frame  
  133.             nRet = avcodec_decode_video2(pCodecCtxDec, pFrameDec, &frameFinished, &packet);  
  134.   
  135.             // Did we get a video frame?  
  136.             if(frameFinished)   
  137.             {  
  138.                 // 关键帧  
  139.                 if(pFrameDec->key_frame == 1)  
  140.                 {  
  141.                     char szPicFile[256];  
  142.                     // ------------frame to bmp start------------  
  143.                     memset(szPicFile, 0, sizeof(szPicFile));  
  144.                     strncpy(szPicFile, pPicFile, sizeof(szPicFile));  
  145.                     strncat(szPicFile, ".bmp"sizeof(szPicFile));  
  146.                     // Convert the image from its native format to RGB  
  147.                     img_convert_ctx = sws_getContext(  
  148.                         pCodecCtxDec->width, pCodecCtxDec->height, pCodecCtxDec->pix_fmt,  
  149.                         pCodecCtxDec->width, pCodecCtxDec->height,   
  150.                         AV_PIX_FMT_RGB24, SWS_BILINEAR, NULL, NULL, NULL);  
  151.                     sws_scale(img_convert_ctx, pFrameDec->data, pFrameDec->linesize, 0,   
  152.                         pCodecCtxDec->height, pFrameRGB->data, pFrameRGB->linesize);  
  153.                     SaveFrameToBMP(szPicFile, pFrameRGB->data[0],   
  154.                         pCodecCtxDec->width, pCodecCtxDec->height, 24);  
  155.                     sws_freeContext(img_convert_ctx);  
  156.                     // ------------frame to bmp end------------  
  157.                     // ------------frame to ppm start------------  
  158.                     memset(szPicFile, 0, sizeof(szPicFile));  
  159.                     strncpy(szPicFile, pPicFile, sizeof(szPicFile));  
  160.                     strncat(szPicFile, ".ppm"sizeof(szPicFile));  
  161.                     // Convert the image from its native format to RGB  
  162.                     img_convert_ctx = sws_getContext(  
  163.                         pCodecCtxDec->width, pCodecCtxDec->height, pCodecCtxDec->pix_fmt,  
  164.                         pCodecCtxDec->width, pCodecCtxDec->height,   
  165.                         AV_PIX_FMT_RGB24, SWS_FAST_BILINEAR, NULL, NULL, NULL);  
  166.                     sws_scale(img_convert_ctx, pFrameDec->data, pFrameDec->linesize, 0,   
  167.                         pCodecCtxDec->height, pFrameRGB->data, pFrameRGB->linesize);  
  168.                     SaveFrameToPPM(szPicFile, pFrameRGB,   
  169.                         pCodecCtxDec->width, pCodecCtxDec->height);  
  170.                     sws_freeContext(img_convert_ctx);  
  171.                     // ------------frame to ppm end------------  
  172.                     // ------------frame to jpg start------------  
  173.                     memset(szPicFile, 0, sizeof(szPicFile));  
  174.                     strncpy(szPicFile, pPicFile, sizeof(szPicFile));  
  175.                     strncat(szPicFile, ".jpg"sizeof(szPicFile));  
  176.                     // Convert the image from its native format to RGB  
  177.                     img_convert_ctx = sws_getContext(  
  178.                         pCodecCtxDec->width, pCodecCtxDec->height, pCodecCtxDec->pix_fmt,  
  179.                         pCodecCtxDec->width, pCodecCtxDec->height,   
  180.                         AV_PIX_FMT_RGB24, SWS_FAST_BILINEAR, NULL, NULL, NULL);  
  181.                     sws_scale(img_convert_ctx, pFrameDec->data, pFrameDec->linesize, 0,   
  182.                         pCodecCtxDec->height, pFrameRGB->data, pFrameRGB->linesize);  
  183.                     // Save the frame to disk  
  184.                     SaveFrameToJPG(szPicFile, pFrameRGB->data[0],   
  185.                         pCodecCtxDec->width, pCodecCtxDec->height);  
  186.                     sws_freeContext(img_convert_ctx);  
  187.                     // ------------frame to jpg end------------  
  188.                     break;  
  189.                 }  
  190.             }  
  191.         }  
  192.         // Free the packet that was allocated by av_read_frame  
  193.         av_free_packet(&packet);  
  194.     }  
  195.     av_free(pFrameRGB);  
  196.     av_free(pBuffer);  
  197.     av_free(pFrameDec);  
  198.     return 0;  
  199. }  


//typedef struct tagBITMAPFILEHEADER
//{
// unsigned short bfType;      //2 位图文件的类型,必须为“BM”
// unsigned long bfSize;       //4 位图文件的大小,以字节为单位
// unsigned short bfReserved1; //2 位图文件保留字,必须为0
// unsigned short bfReserved2; //2 位图文件保留字,必须为0
// unsigned long bfOffBits;    //4 位图数据的起始位置,以相对于位图文件头的偏移量表示,以字节为单位
//} BITMAPFILEHEADER;           //该结构占据14个字节。

//typedef struct tagBITMAPINFOHEADER{
// unsigned long biSize;       //4 本结构所占用字节数
// long biWidth;               //4 位图的宽度,以像素为单位
// long biHeight;              //4 位图的高度,以像素为单位
// unsigned short biPlanes;    //2 目标设备的平面数不清,必须为1
// unsigned short biBitCount;  //2 每个像素所需的位数,必须是1(双色), 4(16色),8(256色)或24(真彩色)之一
// unsigned long biCompression;//4 位图压缩类型,必须是 0(不压缩),1(BI_RLE8压缩类型)或2(BI_RLE4压缩类型)之一
// unsigned long biSizeImage;  //4 位图的大小,以字节为单位
// long biXPelsPerMeter;       //4 位图水平分辨率,每米像素数
// long biYPelsPerMeter;       //4 位图垂直分辨率,每米像素数
// unsigned long biClrUsed;    //4 位图实际使用的颜色表中的颜色数
// unsigned long biClrImportant;//4 位图显示过程中重要的颜色数
//} BITMAPINFOHEADER;           //该结构占据40个字节。
// 保存帧数据为BMP图片

[cpp]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. // 保存帧数据为BMP图片  
  2. int CGetFirstIFrameToPic::SaveFrameToBMP(char *pPicFile, uint8_t *pRGBBuffer, int nWidth, int nHeight, int nBitCount)  
  3. {  
  4.     BITMAPFILEHEADER bmpheader;  
  5.     BITMAPINFOHEADER bmpinfo;  
  6.     memset(&bmpheader, 0, sizeof(BITMAPFILEHEADER));  
  7.     memset(&bmpinfo, 0, sizeof(BITMAPINFOHEADER));  
  8.   
  9.     FILE *fp = NULL;  
  10.     fp = fopen(pPicFile, "wb");  
  11.     if(NULL == fp)  
  12.     {  
  13.         printf("file open error %s\n", pPicFile);  
  14.         return -1;  
  15.     }  
  16.     // set BITMAPFILEHEADER value  
  17.     bmpheader.bfType = ('M' << 8) | 'B';  
  18.     bmpheader.bfReserved1 = 0;  
  19.     bmpheader.bfReserved2 = 0;  
  20.     bmpheader.bfOffBits = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER);  
  21.     bmpheader.bfSize = bmpheader.bfOffBits + nWidth * nHeight * nBitCount / 8;  
  22.     // set BITMAPINFOHEADER value  
  23.     bmpinfo.biSize = sizeof(BITMAPINFOHEADER);  
  24.     bmpinfo.biWidth = nWidth;  
  25.     bmpinfo.biHeight = 0 - nHeight;  
  26.     bmpinfo.biPlanes = 1;  
  27.     bmpinfo.biBitCount = nBitCount;  
  28.     bmpinfo.biCompression = BI_RGB;  
  29.     bmpinfo.biSizeImage = 0;  
  30.     bmpinfo.biXPelsPerMeter = 100;  
  31.     bmpinfo.biYPelsPerMeter = 100;  
  32.     bmpinfo.biClrUsed = 0;  
  33.     bmpinfo.biClrImportant = 0;  
  34.     // write pic file  
  35.     fwrite(&bmpheader, sizeof(BITMAPFILEHEADER), 1, fp);  
  36.     fwrite(&bmpinfo, sizeof(BITMAPINFOHEADER), 1, fp);  
  37.     fwrite(pRGBBuffer, nWidth * nHeight * nBitCount / 8, 1, fp);  
  38.     fclose(fp);  
  39.     return 0;  
  40. }  



// 我们做了一些标准的文件打开动作,然后写入RGB数据。
// 我们一次向文件写入一行数据。
// PPM格式文件的是一种包含一长串的RGB数据的文件。
// 如果你了解 HTML色彩表示的方式,那么它就类似于把每个像素的颜色头对头的展开,
// 就像#ff0000#ff0000....就表示了了个红色的屏幕。
//(它被保存成二进制方式并且没有分隔符,但是你自己是知道如何分隔的)。
// 文件的头部表示了图像的宽度和高度以及最大的RGB值的大小。
// 保存帧数据为PPM图片

[cpp]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. int CGetFirstIFrameToPic::SaveFrameToPPM(char *pPicFile, AVFrame *pFrame, int nWidth, int nHeight)  
  2. {  
  3.     FILE *fp = fopen(pPicFile, "wb");  
  4.     if(NULL == fp)  
  5.     {  
  6.         printf("file open error %s\n", pPicFile);  
  7.         return -1;  
  8.     }  
  9.     // write header  
  10.     fprintf(fp, "P6\n%d %d\n255\n", nWidth, nHeight);  
  11.   
  12.     // write pixel data  
  13.     for(int y = 0; y < nHeight; y++)  
  14.     {  
  15.         fwrite(pFrame->data[0] + y * pFrame->linesize[0], 1, nWidth * 3, fp);  
  16.     }  
  17.     fclose(fp);  
  18.     return 0;  
  19. }  

// 保存帧数据为JPG图片
// 该函数使用了jpeglib库进行图片压缩

[cpp]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. // 保存帧数据为JPG图片  
  2. // 该函数使用了jpeglib库进行图片压缩  
  3. int CGetFirstIFrameToPic::SaveFrameToJPG(char *pPicFile, uint8_t *pRGBBuffer, int nWidth, int nHeight)  
  4. {  
  5.     FILE *fp = fopen(pPicFile, "wb");  
  6.     if(fp == NULL)  
  7.     {  
  8.         printf("file open error %s\n", pPicFile);  
  9.         return -1;  
  10.     }  
  11.   
  12.     struct jpeg_compress_struct jcs;  
  13.     // 声明错误处理器,并赋值给jcs.err域  
  14.     struct jpeg_error_mgr jem;  
  15.     jcs.err = jpeg_std_error(&jem);  
  16.   
  17.     jpeg_create_compress(&jcs);  
  18.   
  19.     jpeg_stdio_dest(&jcs, fp);  
  20.   
  21.     // 为图的宽和高,单位为像素  
  22.     jcs.image_width = nWidth;  
  23.     jcs.image_height = nHeight;  
  24.     // 在此为1,表示灰度图, 如果是彩色位图,则为3  
  25.     jcs.input_components = 3;  
  26.     //JCS_GRAYSCALE表示灰度图,JCS_RGB表示彩色图像  
  27.     jcs.in_color_space = JCS_RGB;   
  28.   
  29.     jpeg_set_defaults(&jcs);   
  30.     jpeg_set_quality (&jcs, 80, TRUE);  
  31.     jpeg_start_compress(&jcs, TRUE);  
  32.   
  33.     // 一行位图  
  34.     JSAMPROW row_pointer[1];  
  35.     //每一行的字节数,如果不是索引图,此处需要乘以3  
  36.     int row_stride = jcs.image_width * 3;  
  37.   
  38.     // 对每一行进行压缩  
  39.     while (jcs.next_scanline < jcs.image_height)   
  40.     {  
  41.         row_pointer[0] = &(pRGBBuffer[jcs.next_scanline * row_stride]);  
  42.         jpeg_write_scanlines(&jcs, row_pointer, 1);  
  43.     }  
  44.     jpeg_finish_compress(&jcs);  
  45.     jpeg_destroy_compress(&jcs);  
  46.   
  47.     fclose(fp);  
  48.     return 0;  
  49. }  


http://blog.youkuaiyun.com/dgyanyong/article/details/22971921

http://blog.youkuaiyun.com/dgyanyong/article/details/22971921
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值