ffmpeg学习——基本的解码流程

由于工作需要,所以基本了解了一下视频的解码流程。

参考教程为:

1、王纲的《跟我一起学FFmpeg》系列

2、雷霄骅雷神的博客

原理部分暂时没有整理,后期可以补充一下知识。

ffmepg的api使用方面

1、打开一个输入流

2、设置解码器

3、读取每一个包,并获取到一帧的数据

4、交给解码器解码

下面的代码就是,读取一个视频或者文件,将其中的一帧图片保存为BGR24格式的文件,该文件加上BMP文件头即可使用图片浏览器打开。需要注意的是,这样会将图片上下翻转。所以图片会倒着显示。

 

 

#include 
#include 
#include 
#include 
#include 
#include 

#include 

#define myprintf(x) printf(x)

unsigned char *filecontent;
unsigned char *content; 

static int parse_cmd(int argc, char * argv[])
{
	int ret = 0;
	if (argc < 2)
		return -1;
	return ret;
}

static void show_help()
{
	printf("Input error!\n");
	printf("Usage: ./decodec-and-display-ffmpeg \n");
}



void add_header()
{
	filecontent     = (unsigned char*) malloc (0x5eec36);
	content         = filecontent + 0x36;
	FILE *file = fopen("./1920*1080.bmp", "rb");
	if (file < 0)
	{
		myprintf("file can not open!\n");
		return;
	}
	printf("1920*1080.bmp open success!\n");
	
	fread(filecontent, 1, 0x36, file);
	printf("finish reading 1920*1080.bmp header!\n");
	fclose(file);
	
	file = fopen("./fpsave.bgr24", "rb");
	fread(content, 1, 0x5eec00, file);
	myprintf("finish reading ./fpsave.bgr24!\n");
	fclose(file);
	
	file = fopen("./fpsave-bgr24.bmp", "wb+");
	fwrite(filecontent, 1, 0x5eec36, file);
	fclose(file);
	myprintf("Add header finish!\n");
}

int main(int argc, char *argv[])
{

	int  ret, got_picture;
	int  i, videoindex = -1;                  //这两个变量,主要是从流中找到视频流的编号,而非音频流
	char filepath[128];						  //输入文件路径
	int  dst_bytes_num;						  //一幅解码后的BGR24格式图片存储所需的内存
	
	AVFormatContext	  *pFormatCtx;			  //上下文结构体,包含了所有的信息
	
	AVCodecContext	  *pCodecCtx;		      //解码器用到的上下文结构体
	AVCodec			  *pCodec;				  //解码器实例
	AVFrame	          *pFrame,*pFrameBGR;	  //存放帧和BGR数据的结构体
	 
	AVPacket          *avpacket = NULL;		  //这个结构体很关键,其中包含了ffmpeg从输入流中得到的裸流的数据和大小
	FILE              *fpSave;                //先将解码后的数据保存到这个文件中,以便检验
	
	uint8_t 		  *out_buffer;			  //解码后的帧数据缓存
	struct SwsContext *img_convert_ctx;		  //格式转换需要用到的结构体,暂时不对视频进行格式转换

	
	/* 0、简单判断输入否合法 */
	ret = parse_cmd(argc, argv);
	if (ret < 0)
	{
		show_help();
		return 0;
	}
	else 
	{
		printf("%s\n", argv[1]);
	}
	/* 1、初始化ffmpeg */
	av_register_all();
	
	/* 2、打开输入流 */
	/* 2.1打开网络流或文件流 */
	pFormatCtx = avformat_alloc_context();	          						//分配一个输入流的上下文结构体,用以存储相关的数据              
	ret = avformat_open_input(&pFormatCtx, argv[1], NULL, NULL);  			//以设定的方式(参数)打开输入流,并与pFormatCtx结构体关联,但是此时未填充
	if(ret)  
	{
		printf("Couldn't open input stream.\n");
		return -1;
	}
	/* 2.2获取流信息,并保存在上下文pFormatCtx中 */
	ret = avformat_find_stream_info(pFormatCtx, NULL);   		    		//通过该函数,填充pFormatCtx结构体,如编码方式,流(音频、视频)的个数,宽高,帧率等
	if(ret)
	{
		printf("Couldn't find stream information.\n");
		return -1;
	}
	
	/* 2.3打印出输入文件信息 */
	printf("---------------- File Information ---------------\n");
	av_dump_format(pFormatCtx, 0, argv[1], 0);
	printf("-------------------------------------------------\n");
		
	/* 2.4从流中,找出视频流编号 */
	videoindex=-1;									
	for( i = 0; i < pFormatCtx->nb_streams; i++) 
	{
		if(pFormatCtx->streams[i]->codec->codec_type==AVMEDIA_TYPE_VIDEO)	//我们不关心音频AVMEDIA_TYPE_AUDIO,所以找出来视频流AVMEDIA_TYPE_VIDEO的编号,通常为0
		{
			videoindex=i;
			//printf("videoindex = %d\n", videoindex);
			break;
		}
	}
	if(-1 == videoindex)
	{
		printf("Didn't find a video stream.\n");
		return -1;
	}
	
	/* 3、解码输入流,获取到一帧的数据 */
	/* 3.1根据编码类型查找解码器 */
	pCodecCtx = pFormatCtx->streams[videoindex]->codec;						//获取到视频的编码类型,对于MP4,pFormatCtx->streams[videoindex]->codec->codec_id = 13,即AV_CODEC_ID_MPEG4
	//printf("pCodecCtx = pFormatCtx->streams[videoindex]->codec->codec_id = %d \n", pFormatCtx->streams[videoindex]->codec->codec_id);
	pCodec = avcodec_find_decoder(pCodecCtx->codec_id); 					//用于查找FFmpeg的AV_CODEC_ID_MPEG4格式对应的解码器
	if(pCodec==NULL)
	{
		printf("Codec not found.\n");
		return -1;
	}
	/* 3.2打开解码器 */
	if(avcodec_open2(pCodecCtx, pCodec,NULL)<0)
	{
		printf("Could not open codec.\n");
		return -1;
	}
	
	/* 3.2为解码工作,分配存储空间 */
	
	//为一帧编码的数据分配内存空间,这个函数只是分配AVFrame结构体,但data指向的内存并没有分配,需要我们指定,这个内存的大小就是一张特定格式图像所需的大小。
	pFrame=av_frame_alloc();	
	
	//为一帧编解码后的BGR24数据分配一个结构体,这个函数只是分配AVFrame结构体,但data指向的内存并没有分配,需要我们指定,这个内存的大小就是一张特定格式图像所需的大小。
	pFrameBGR=av_frame_alloc();	
	
	//分配的out_buffer大小是宽*高*3(RGB24)对于测试视频320*240*3=230400Byte,该buffer只是一个缓存
	dst_bytes_num = avpicture_get_size(AV_PIX_FMT_BGR24, pCodecCtx->width, pCodecCtx->height);
	out_buffer=(uint8_t *)av_malloc(dst_bytes_num);
	if (out_buffer)
	{
		printf("out_buffer alloc success!\n");
		printf("address of out_buffer = %p\n", out_buffer);
	}
	else
	{
		printf("out_buffer alloc failed!\n");
		return 0;
	}
	
	//前面的av_frame_alloc函数,只是为这个AVFrame结构体分配了内存,而该类型的指针指向的内存还没分配。这里把av_malloc得到的内存和AVFrame关联起来,当然,其还会设置AVFrame的其他成员
	//该函数会初始化pFrameBGR->linesize
	ret = av_image_fill_arrays(pFrameBGR->data, pFrameBGR->linesize, out_buffer, AV_PIX_FMT_BGR24,pCodecCtx->width, pCodecCtx->height,1);
	
	
	//经过测试,pFrameBGR->linesize[4]的初始值均为0,经过上面的初始化后,分别被赋值;pFrameBGR->data[4]为4个指针,初值均为NULL,后续被分配到内存
	//如果参数是YUV420P,pFrameBGR->linesize[0] = width, pFrameBGR->linesize[1] = width / 2, pFrameBGR->linesize[2] = width / 2
	//如果参数是BGR24,pFrameBGR->linesize[0] = width * 3, pFrameBGR->linesize[1] = 0, pFrameBGR->linesize[2] = 0
	//printf("pFrameBGR->linesize[0] = %d, pFrameBGR->linesize[1] = %d, pFrameBGR->linesize[2] = %d, pFrameBGR->linesize[3] = %d\naddress of pFrameBGR->data[0] = %p, address of pFrameBGR->data[1] = %p, address of pFrameBGR->data[2] = %p\n", pFrameBGR->linesize[0], pFrameBGR->linesize[1], pFrameBGR->linesize[2], pFrameBGR->linesize[3], pFrameBGR->data[0], pFrameBGR->data[1], pFrameBGR->data[2]);
	
	//为AVPacket结构体分配缓存
	avpacket=(AVPacket *)av_malloc(sizeof(AVPacket));
	//对转换进行设置,这里要设置转换源的大小、格式和转换目标的大小、格式,以及转换所用的算法
	//img_convert_ctx = sws_getContext(pCodecCtx->width, pCodecCtx->height, pCodecCtx->pix_fmt, pCodecCtx->width, pCodecCtx->height, AV_PIX_FMT_BGR24, SWS_BICUBIC, NULL, NULL, NULL);
	img_convert_ctx = sws_getContext(pCodecCtx->width, pCodecCtx->height, pCodecCtx->pix_fmt, pCodecCtx->width, pCodecCtx->height, AV_PIX_FMT_BGR24, SWS_BICUBIC, NULL, NULL, NULL);
	if (img_convert_ctx == NULL)  
    {  
        printf("sws_getContext failed!\n ");  
        return -1;  
    }

	
	
	if ((fpSave = fopen("./fpsave.bgr24", "wb+")) == NULL) //data数据保存的文件名
		return 0; 
	int cnt = 0;
	int cnt_decode = 0;
	
	while(av_read_frame(pFormatCtx, avpacket)>=0)
	{
		if(avpacket->stream_index==videoindex)
		{
			ret = avcodec_decode_video2(pCodecCtx, pFrame, &got_picture, avpacket);  //真正的解码工作,将压缩的数据解码为yuv420p格式
			if(ret < 0)  
			{  
				printf("解码错误\n");  
				return -1;  
			}
			else
			{
				cnt_decode++;
			}

			
			if(got_picture)
			{
				
				printf("解码一帧所需的次数cnt_decode = %d\n", cnt_decode);
				printf("解码成功\n"); 
				
				sws_scale(img_convert_ctx, (const unsigned char* const*)pFrame->data, pFrame->linesize, 0, pCodecCtx->height, pFrameBGR->data, pFrameBGR->linesize);
				printf("YUV420P->BGR24格式转换完成\n"); 
				//y_size=pCodecCtx->width*pCodecCtx->height*3;  
				if (cnt == 0)
					fwrite(pFrameBGR->data[0], 1, dst_bytes_num, fpSave);    //BGR24
				//fwrite(pFrameBGR->data[0], 1, y_size,  fpSave);   //Y 
				//fwrite(pFrameBGR->data[1], 1, y_size/4,fpSave);   //U
				//fwrite(pFrameBGR->data[2], 1, y_size/4,fpSave);   //V
				printf("Succeed to decode 1 frame!\n");
				
				printf("avpacket->stream_index = %d\n", avpacket->stream_index);
				printf("avpacket->size = %d, cnt = %d\n", avpacket->size, cnt);
				//fwrite(avpacket->data,1,avpacket->size,fpSave);//写数据到文件中
				cnt++;
				if (cnt == 1)
					break;
				

			}

		}
		av_free_packet(avpacket);
	}
	
	fclose(fpSave);
	
	add_header();
	
	
	
	
	
	
	av_frame_free(&pFrameBGR);
	av_frame_free(&pFrame);
	avcodec_close(pCodecCtx);
	avformat_close_input(&pFormatCtx);


	return 0;
}

 

你可能感兴趣的:(学习&练习,工作)