// 使用ffmpeg取得视频的首个I帧,并转换为图片
// 图片格式可以为BMP、PPM、JPG
// 头文件CGetFirstIFrameToPic.h
- #if !defined (_C_GET_FIRST_IFRAME_TO_PIC_H_)
- #define _C_GET_FIRST_IFRAME_TO_PIC_H_
- extern "C"
- {
- #include "libavcodec/avcodec.h"
- #include "libavformat/avformat.h"
- #include "libswscale/swscale.h"
- #include "jpeglib.h"
- }
- // 取得视频的首个I帧,并转换为图片
- // 图片格式可以为BMP、PPM、JPG
- class CGetFirstIFrameToPic
- {
- public:
- CGetFirstIFrameToPic(){};
- virtual ~CGetFirstIFrameToPic(){};
- // 取得视频的首个I帧,并转换为图片
- virtual int GetFirstIFrameAndConvertToPic(char *pVideoFile, char *pPicFile);
- // 保存帧数据为BMP图片
- virtual int SaveFrameToBMP(char *pPicFile, uint8_t *pRGBBuffer, int nWidth, int nHeight, int nBitCount);
- // 保存帧数据为PPM图片
- virtual int SaveFrameToPPM(char *pPicFile, AVFrame *pFrame, int nWidth, int nHeight);
- // 保存帧数据为JPG图片
- virtual int SaveFrameToJPG(char *pPicFile, uint8_t *pRGBBuffer, int nWidth, int nHeight);
- };
- #endif //!defined(_C_GET_FIRST_IFRAME_TO_PIC_H_)
// 取得视频的首个I帧,并转换为图片
- #include "stdafx.h"
- #include "CGetFirstIFrameToPic.h"
- // 取得视频的首个I帧,并转换为图片
- int CGetFirstIFrameToPic::GetFirstIFrameAndConvertToPic(char *pVideoFile, char *pPicFile)
- {
- int nRet = 0;
- if((NULL == pVideoFile) || (NULL == pPicFile))
- {
- return -1;
- }
- // 注册所有的文件格式和编解码器的库
- av_register_all();
- AVFormatContext *pFormatCtxDec = NULL; // 视频流的格式内容
- // 读取文件的头部并且把信息保存到我们给的AVFormatContext结构
- nRet = avformat_open_input(&pFormatCtxDec, pVideoFile, NULL, NULL);
- if(nRet != 0)
- {
- printf("Couldn't open file %s.\n", pVideoFile);
- return -1;
- }
- // 检查在文件中的流的信息
- nRet = avformat_find_stream_info(pFormatCtxDec, NULL);
- if(nRet < 0)
- {
- printf("Couldn't find stream information.\n");
- return -1;
- }
- // 找到第一个视频流
- int nVideoStream = -1;
- for(unsigned int i = 0; i < pFormatCtxDec->nb_streams; i++)
- {
- if(pFormatCtxDec->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO)
- {
- nVideoStream = i;
- break;
- }
- }
- if(nVideoStream == -1)
- {
- printf("Didn't find a video stream.\n");
- return -1;
- }
- printf("nVideoStream:%d.\n", nVideoStream);
- // 流中关于编解码器的信息就是被我们叫做"codec context"(编解码器上下文)的东西。
- // 这里面包含了流中所使用的关于编解码器的所有信息,现在我们有了一个指向他的指针。
- // 但是我们必需要找到真正的编解码器并且打开它
- AVCodecContext *pCodecCtxDec = NULL; // 编解码器上下文
- // Get a pointer to the codec context for the video stream
- pCodecCtxDec = pFormatCtxDec->streams[nVideoStream]->codec;
- AVCodec *pCodecDec = NULL;
- // Find the decoder for the video stream
- pCodecDec = avcodec_find_decoder(pCodecCtxDec->codec_id);
- if(pCodecDec==NULL)
- {
- printf("Codec not found.\n");
- return -1;
- }
- // Open codec
- nRet = avcodec_open2(pCodecCtxDec, pCodecDec, NULL);
- if(nRet < 0)
- {
- printf("Could not open codec.\n");
- return -1;
- }
- AVFrame *pFrameDec = NULL;
- // Allocate video frame
- pFrameDec = avcodec_alloc_frame();
- if(pFrameDec == NULL)
- {
- printf("Allocate video frame error.\n");
- return -1;
- }
- // 因为我们准备输出保存24位RGB色的PPM文件,我们必需把帧的格式从原来的转换为RGB。
- // FFMPEG将为我们做这些转换。
- // 在大多数项目中(包括我们的这个)我们都想把原始的帧转换成一个特定的格式。
- // 让我们先为转换来申请一帧的内存
- AVFrame *pFrameRGB = NULL;
- // Allocate an AVFrame structure
- pFrameRGB = avcodec_alloc_frame();
- if(pFrameRGB == NULL)
- {
- printf("Allocate an AVFrame structure error.\n");
- return -1;
- }
- // 即使我们申请了一帧的内存,当转换的时候,我们仍然需要一个地方来放置原始的数据。
- // 我们使用avpicture_get_size来获得我们需要的大小,然后手工申请内存空间:
- uint8_t *pBuffer;
- int numBytes;
- // Determine required buffer size and allocate buffer AV_PIX_FMT_RGB24
- numBytes = avpicture_get_size(AV_PIX_FMT_RGB24, pCodecCtxDec->width, pCodecCtxDec->height);
- pBuffer=(uint8_t *)av_malloc(numBytes * sizeof(uint8_t));
- // 现在我们使用avpicture_fill来把帧和我们新申请的内存来结合。
- // 关于AVPicture的结成:AVPicture结构体是AVFrame结构体的子集
- // ――AVFrame结构体的开始部分与AVPicture结构体是一样的。
- // Assign appropriate parts of buffer to image planes in pFrameRGB
- // Note that pFrameRGB is an AVFrame, but AVFrame is a superset of AVPicture
- avpicture_fill((AVPicture *)pFrameRGB, pBuffer, AV_PIX_FMT_RGB24,
- pCodecCtxDec->width, pCodecCtxDec->height);
- // 最后,我们已经准备好来从流中读取数据了。
- // 读取数据
- // 我们将要做的是通过读取包来读取整个视频流,
- // 然后把它解码成帧,最好后转换格式并且保存。
- int frameFinished = 0;
- AVPacket packet;
- static struct SwsContext *img_convert_ctx;
- while(true)
- {
- av_init_packet(&packet);
- nRet = av_read_frame(pFormatCtxDec, &packet);
- if(nRet < 0)
- {
- break;
- }
- // Is this a packet from the video stream?
- if(packet.stream_index == nVideoStream)
- {
- // Decode video frame
- nRet = avcodec_decode_video2(pCodecCtxDec, pFrameDec, &frameFinished, &packet);
- // Did we get a video frame?
- if(frameFinished)
- {
- // 关键帧
- if(pFrameDec->key_frame == 1)
- {
- char szPicFile[256];
- // ------------frame to bmp start------------
- memset(szPicFile, 0, sizeof(szPicFile));
- strncpy(szPicFile, pPicFile, sizeof(szPicFile));
- strncat(szPicFile, ".bmp", sizeof(szPicFile));
- // Convert the image from its native format to RGB
- img_convert_ctx = sws_getContext(
- pCodecCtxDec->width, pCodecCtxDec->height, pCodecCtxDec->pix_fmt,
- pCodecCtxDec->width, pCodecCtxDec->height,
- AV_PIX_FMT_RGB24, SWS_BILINEAR, NULL, NULL, NULL);
- sws_scale(img_convert_ctx, pFrameDec->data, pFrameDec->linesize, 0,
- pCodecCtxDec->height, pFrameRGB->data, pFrameRGB->linesize);
- SaveFrameToBMP(szPicFile, pFrameRGB->data[0],
- pCodecCtxDec->width, pCodecCtxDec->height, 24);
- sws_freeContext(img_convert_ctx);
- // ------------frame to bmp end------------
- // ------------frame to ppm start------------
- memset(szPicFile, 0, sizeof(szPicFile));
- strncpy(szPicFile, pPicFile, sizeof(szPicFile));
- strncat(szPicFile, ".ppm", sizeof(szPicFile));
- // Convert the image from its native format to RGB
- img_convert_ctx = sws_getContext(
- pCodecCtxDec->width, pCodecCtxDec->height, pCodecCtxDec->pix_fmt,
- pCodecCtxDec->width, pCodecCtxDec->height,
- AV_PIX_FMT_RGB24, SWS_FAST_BILINEAR, NULL, NULL, NULL);
- sws_scale(img_convert_ctx, pFrameDec->data, pFrameDec->linesize, 0,
- pCodecCtxDec->height, pFrameRGB->data, pFrameRGB->linesize);
- SaveFrameToPPM(szPicFile, pFrameRGB,
- pCodecCtxDec->width, pCodecCtxDec->height);
- sws_freeContext(img_convert_ctx);
- // ------------frame to ppm end------------
- // ------------frame to jpg start------------
- memset(szPicFile, 0, sizeof(szPicFile));
- strncpy(szPicFile, pPicFile, sizeof(szPicFile));
- strncat(szPicFile, ".jpg", sizeof(szPicFile));
- // Convert the image from its native format to RGB
- img_convert_ctx = sws_getContext(
- pCodecCtxDec->width, pCodecCtxDec->height, pCodecCtxDec->pix_fmt,
- pCodecCtxDec->width, pCodecCtxDec->height,
- AV_PIX_FMT_RGB24, SWS_FAST_BILINEAR, NULL, NULL, NULL);
- sws_scale(img_convert_ctx, pFrameDec->data, pFrameDec->linesize, 0,
- pCodecCtxDec->height, pFrameRGB->data, pFrameRGB->linesize);
- // Save the frame to disk
- SaveFrameToJPG(szPicFile, pFrameRGB->data[0],
- pCodecCtxDec->width, pCodecCtxDec->height);
- sws_freeContext(img_convert_ctx);
- // ------------frame to jpg end------------
- break;
- }
- }
- }
- // Free the packet that was allocated by av_read_frame
- av_free_packet(&packet);
- }
- av_free(pFrameRGB);
- av_free(pBuffer);
- av_free(pFrameDec);
- return 0;
- }
//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图片
- // 保存帧数据为BMP图片
- int CGetFirstIFrameToPic::SaveFrameToBMP(char *pPicFile, uint8_t *pRGBBuffer, int nWidth, int nHeight, int nBitCount)
- {
- BITMAPFILEHEADER bmpheader;
- BITMAPINFOHEADER bmpinfo;
- memset(&bmpheader, 0, sizeof(BITMAPFILEHEADER));
- memset(&bmpinfo, 0, sizeof(BITMAPINFOHEADER));
- FILE *fp = NULL;
- fp = fopen(pPicFile, "wb");
- if(NULL == fp)
- {
- printf("file open error %s\n", pPicFile);
- return -1;
- }
- // set BITMAPFILEHEADER value
- bmpheader.bfType = ('M' << 8) | 'B';
- bmpheader.bfReserved1 = 0;
- bmpheader.bfReserved2 = 0;
- bmpheader.bfOffBits = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER);
- bmpheader.bfSize = bmpheader.bfOffBits + nWidth * nHeight * nBitCount / 8;
- // set BITMAPINFOHEADER value
- bmpinfo.biSize = sizeof(BITMAPINFOHEADER);
- bmpinfo.biWidth = nWidth;
- bmpinfo.biHeight = 0 - nHeight;
- bmpinfo.biPlanes = 1;
- bmpinfo.biBitCount = nBitCount;
- bmpinfo.biCompression = BI_RGB;
- bmpinfo.biSizeImage = 0;
- bmpinfo.biXPelsPerMeter = 100;
- bmpinfo.biYPelsPerMeter = 100;
- bmpinfo.biClrUsed = 0;
- bmpinfo.biClrImportant = 0;
- // write pic file
- fwrite(&bmpheader, sizeof(BITMAPFILEHEADER), 1, fp);
- fwrite(&bmpinfo, sizeof(BITMAPINFOHEADER), 1, fp);
- fwrite(pRGBBuffer, nWidth * nHeight * nBitCount / 8, 1, fp);
- fclose(fp);
- return 0;
- }
// 我们做了一些标准的文件打开动作,然后写入RGB数据。
// 我们一次向文件写入一行数据。
// PPM格式文件的是一种包含一长串的RGB数据的文件。
// 如果你了解 HTML色彩表示的方式,那么它就类似于把每个像素的颜色头对头的展开,
// 就像#ff0000#ff0000....就表示了了个红色的屏幕。
//(它被保存成二进制方式并且没有分隔符,但是你自己是知道如何分隔的)。
// 文件的头部表示了图像的宽度和高度以及最大的RGB值的大小。
// 保存帧数据为PPM图片
- int CGetFirstIFrameToPic::SaveFrameToPPM(char *pPicFile, AVFrame *pFrame, int nWidth, int nHeight)
- {
- FILE *fp = fopen(pPicFile, "wb");
- if(NULL == fp)
- {
- printf("file open error %s\n", pPicFile);
- return -1;
- }
- // write header
- fprintf(fp, "P6\n%d %d\n255\n", nWidth, nHeight);
- // write pixel data
- for(int y = 0; y < nHeight; y++)
- {
- fwrite(pFrame->data[0] + y * pFrame->linesize[0], 1, nWidth * 3, fp);
- }
- fclose(fp);
- return 0;
- }
// 保存帧数据为JPG图片
// 该函数使用了jpeglib库进行图片压缩
- // 保存帧数据为JPG图片
- // 该函数使用了jpeglib库进行图片压缩
- int CGetFirstIFrameToPic::SaveFrameToJPG(char *pPicFile, uint8_t *pRGBBuffer, int nWidth, int nHeight)
- {
- FILE *fp = fopen(pPicFile, "wb");
- if(fp == NULL)
- {
- printf("file open error %s\n", pPicFile);
- return -1;
- }
- struct jpeg_compress_struct jcs;
- // 声明错误处理器,并赋值给jcs.err域
- struct jpeg_error_mgr jem;
- jcs.err = jpeg_std_error(&jem);
- jpeg_create_compress(&jcs);
- jpeg_stdio_dest(&jcs, fp);
- // 为图的宽和高,单位为像素
- jcs.image_width = nWidth;
- jcs.image_height = nHeight;
- // 在此为1,表示灰度图, 如果是彩色位图,则为3
- jcs.input_components = 3;
- //JCS_GRAYSCALE表示灰度图,JCS_RGB表示彩色图像
- jcs.in_color_space = JCS_RGB;
- jpeg_set_defaults(&jcs);
- jpeg_set_quality (&jcs, 80, TRUE);
- jpeg_start_compress(&jcs, TRUE);
- // 一行位图
- JSAMPROW row_pointer[1];
- //每一行的字节数,如果不是索引图,此处需要乘以3
- int row_stride = jcs.image_width * 3;
- // 对每一行进行压缩
- while (jcs.next_scanline < jcs.image_height)
- {
- row_pointer[0] = &(pRGBBuffer[jcs.next_scanline * row_stride]);
- jpeg_write_scanlines(&jcs, row_pointer, 1);
- }
- jpeg_finish_compress(&jcs);
- jpeg_destroy_compress(&jcs);
- fclose(fp);
- return 0;
- }
http://blog.youkuaiyun.com/dgyanyong/article/details/22971921