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



// 使用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;
}





评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值