纯净版基于FFMPEG解码器(H264到YUV)

现在正在学FFMPEG相关知识,写此文的目的就是便于自己对一些函数接口的理解,也希望对同样学习FFMPEG的人有一些帮助。代码不是自己写的,主要是在原代码的基础上增加了一些注释。

代码中涉及到的AVFrame中的linesize知识,可以参考下此篇博文介绍:http://blog.csdn.net/h514434485/article/details/51788666


#include 
#include 

#define INBUF_SIZE 4096

FILE *pFin = NULL;
FILE *pFout = NULL;

AVCodec *pCodec = NULL;
AVCodecContext *pCodecContext = NULL;

//用于解析码流,生存可以供解码器解码的包
AVCodecParserContext *pCodecParserCtx=NULL;

AVFrame *frame=NULL;
AVPacket pkt;

static int open_decoder()
{
	//注册编解码器所有的组件
	avcodec_register_all();

	//初始化avpacket
	av_init_packet(&pkt);

	pCodec=avcodec_find_decoder(AV_CODEC_ID_H264);
	if ( !pCodec )
	{
	    printf("Error: find codec failed\n");
	    return -1;
	}

	pCodecContext = avcodec_alloc_context3(pCodec);
	if ( !pCodecContext )
	{
	    printf("Error: alloc codecCtx failed\n");
	    return -1;
	}

	//读取码流时,可能不是按一个完整的包来读取的,所以要判断下
	/*
	通知解码器,我们能够处理截断的流。
	为什么会有截断流?因为视频流中的数据是被分割放入包中的,因为每个视频帧的数据大小是可变的
	那么两帧之间的边界就不一定刚好是包的边界,这里通知解码器,我们可以处理截断流。
	*/
	if (pCodec->capabilities & AV_CODEC_CAP_TRUNCATED)
	{
		//截断的方式来读取,流帧边界可以在包中
	 	pCodecContext->flags |= AV_CODEC_CAP_TRUNCATED;
	}

	//根据h264格式初始化解析器
	pCodecParserCtx= av_parser_init(AV_CODEC_ID_H264);
	if ( !pCodecParserCtx )
	{
	    	printf("Error:alloc parser failed\n");
		return -1;	
	}

	//打开解码器
	if ( avcodec_open2(pCodecContext,pCodec,NULL)<0 )
	{
		printf("Error: Opening codec failed");	
	    	return -1;
	}

	//分配frame对象
	frame=av_frame_alloc();
	if ( !frame )
	{
		printf("Error: Alloc frame failed");
	    	return -1;
	}
	
	return 0;
}

static int open_input_output_file(char **argv)
{
	const char *inputFileName=argv[1];
	const char *outputFileName=argv[2];
	
	pFin=fopen(inputFileName,"rb+");
	if ( !pFin )
	{
		printf("Error: open input file failed\n");
		return -1;
	}

	pFout=fopen(outputFileName,"wb+");
	if ( !pFin )
	{
	    printf("Error: open output file failed\n");	
	    return -1;
	}

	return 0;
}

static void write_out_yuv_frame(AVFrame *frame)
{
	unsigned char **pBuf=frame->data;//frame所保存的像素的地址
	int *pStride = frame->linesize;//保存一帧的位宽,一行究竟有多少像素
	int color_idx;
	int idx;
	
	//YUV有三个颜色分量,分三步来实现
	for ( color_idx = 0 ; color_idx < 3 ; color_idx++ )
	{
	    	int nWidth = color_idx==0?frame->width:frame->width/2;
		int nHeight = color_idx==0?frame->height:frame->height/2;
		for ( idx = 0 ; idx < nHeight ; idx++ )
		{
		 	fwrite(pBuf[color_idx],1,nWidth,pFout);
			pBuf[color_idx]+=pStride[color_idx];
		}

		fflush(pFout);
	}
	
}

static void Close()
{
	fclose(pFin);
	fclose(pFout);
	
	av_parser_close(pCodecParserCtx);  
	avcodec_close(pCodecContext);
	av_free(pCodecContext);
	av_frame_free(&frame);
}

int main(int argc, char **argv)
{
	/*
	从输入文件中读取码流数据,保存到内存缓存中,AV_INPUT_BUFFER_PADDING_SIZE=32
	*/
	char inbuf[INBUF_SIZE+AV_INPUT_BUFFER_PADDING_SIZE]={0};
	int ret;
	if(open_input_output_file(argv)<0)
	{
		printf("Error:open file fail\n");
		return -1;
	}

	if ( open_decoder()<0 )
	{
		printf("Error: open decoder fail\n");
		return -1;    
	}

	printf("Open file and decoder succeed!\n");

	//一次读入到我们定义的缓存的长度
	int uDataSize=0;
	//缓存中被解析出数据的长度,
	int len;

	//got_frame表示是否解码出一个完整像素的帧
	int got_frame;
	unsigned char *pDataPtr=NULL;
	
	while ( 1 )
	{
		uDataSize = fread(inbuf, 1,INBUF_SIZE, pFin);
		if ( uDataSize==0)
		{
			//读取文件失败或读取到文件末尾
		    	break;
		}

		pDataPtr=inbuf;
		//从码流文件中解析出packet包
		while ( uDataSize > 0)
		{
		   	len=av_parser_parse2(pCodecParserCtx,pCodecContext,&pkt.data,&pkt.size,
								pDataPtr,uDataSize,AV_NOPTS_VALUE,AV_NOPTS_VALUE,AV_NOPTS_VALUE);
			pDataPtr+=len;
			uDataSize-=len;

			if ( pkt.size==0 )
			{
				//表示一个包还没解析完成
			    	continue;
			}
			//成功解析出一个packet的码流
			printf("Parse 1 packet.\n");

			//调用解码器,对包里面的码流进行解码
			ret=avcodec_decode_video2(pCodecContext,frame,&got_frame,&pkt);
			if ( ret < 0 )
			{
				printf("Error:decode failed\n");
			    	return -1;
			}

			if(got_frame)
			{
				printf("Decoded 1 frame ok!Width x Height:(%d x %d)\n",frame->width,frame->height);
				write_out_yuv_frame(frame);
			}
		}
	}

	//解码器中仍然可能存在尚未输出的数据
	pkt.data=NULL;
	pkt.size=0;
	while ( 1 )
	{
	 	ret=avcodec_decode_video2(pCodecContext,frame,&got_frame,&pkt);
		if ( ret<0 )
		{
			printf("Error:decode failed\n");
		    	return -1;
		}

		if ( got_frame )
		{
			printf("flush decoder:Decoded 1 frame ok!Width x Height:(%d x %d)\n",frame->width,frame->height);
		    	write_out_yuv_frame(frame);
		}
		else
		{
			break;	
		}
	}

	Close();
	
	return 0;	
}
#include 

#include 
#include 
#include 
#include 


const char *inputFilename=NULL;
const char *outputFilename=NULL;

FILE *pFin=NULL,*pFout=NULL;

int frameWidth=0,frameHeight=0;
int bitRate =0,frameToEncode=0;

AVCodec *codec = NULL;//编解码器
AVCodecContext *codecCtx=NULL;//编码器的上下文
AVFrame *frame=NULL;
AVPacket pkt; //编码之后的码流

static int read_yuv_data(int color);
	
static int parse_input_paramaters(int argc, char **argv)
{
	//inputFilename = argv[1];
	//outputFilename = argv[2];
	inputFilename = "./ds_480x272.yuv";
	outputFilename = "./ds1.h264";

	pFin=fopen(inputFilename,"rb+");
	if ( !pFin )
	{
	    return -1;
	}

	pFout=fopen(outputFilename,"wb+");
	if ( !pFin )
	{
	    return -1;
	}

	 /*获取视频宽高*/
	//frameWidth=atoi(argv[3]);
	//frameHeight=atoi(argv[4]);
	//bitRate=atoi(argv[5]);
	frameWidth=480;
	frameHeight=272;
	bitRate=400000;

	/*要编码的帧数*/
	//frameToEncode=atoi(argv[6]);
	frameToEncode=100;
	
	 
	return 1;
}

int main(int argc, char **argv)
{
	int got_packet=0;
	int frameIdx;
	int color_0;
	int color_1;
	int color_2;
	int y_size;
	int ret;
	int framecnt=0;
	
	if ( parse_input_paramaters(argc,argv) > 0 )
	{
	    printf("Input file:%s\nOutput file:%s\n",inputFilename,outputFilename);
		printf("Frame resolution:(%d x %d)\nBit rate:%d\nFrame num to code:%d\n",frameWidth,frameHeight,bitRate,frameToEncode);	
	}
	else
	{
		printf("Error: command line put error!\n");
	}

	/*注册编解码器组件*/
	avcodec_register_all();

	//查找AVCodec编解码器
	codec=avcodec_find_encoder(AV_CODEC_ID_H264);
	if ( !codec )
	{
	    //查找失败
	    return -1;
	}
	
	//分配AVCodecContext实列
	codecCtx=avcodec_alloc_context3( codec);
	if ( !codecCtx )
	{
	    //查找失败
	    return -11;
	}

	//设置编码器参数
	codecCtx->width = frameWidth;
	codecCtx->height = frameHeight;
	codecCtx->bit_rate = bitRate;
	//codecCtx->bit_rate = 400000;
	AVRational r = {1,25};
	codecCtx->time_base = r;
	//codecCtx->gop_size = 12; //连续画面组大小
	codecCtx->gop_size = 10; //连续画面组大小

	//两个非B帧之间允许出现多少个B帧数
	//设置0表示不使用B帧
	//B帧越多,图片越小
	codecCtx->max_b_frames = 1;
	codecCtx->pix_fmt = AV_PIX_FMT_YUV420P;

	//设置私有参数
	av_opt_set(codecCtx->priv_data,"preset","slow",0);
	
	
	//打开编码器
	if(avcodec_open2(codecCtx, codec, NULL)< 0)
	{
		return -11;
	}

	//分配AVFrame以及像素存储空间
	frame=av_frame_alloc();
	if ( !frame )
	{
	    return -11;
	}

	frame->width = codecCtx->width;
	frame->height = codecCtx->height;
	frame->format = codecCtx->pix_fmt;

	//分配avframe包含的像素空间
	/*
	分配像素的存储空间,内存以16字节对齐	
	*/
	if(av_image_alloc(frame->data,frame->linesize,frame->width,frame->height,codecCtx->pix_fmt,16) < 0)	
	{
		printf("ERROR:avimag alloc fail!\n");
		return -11;
	}

	y_size = codecCtx->width * codecCtx->height;
	
	/*编码过程*/
	for ( frameIdx = 0 ; frameIdx < frameToEncode; frameIdx++ )
	{
		//初始化以后avpacket就可以装载编码之后的码流
	    av_init_packet(&pkt);
		pkt.data = NULL;
		pkt.size = 0;
		
		//读取数据到AVFrame
		//将三个颜色分量读取到AVFrame中
		color_0=read_yuv_data(0);
		color_1=read_yuv_data(1);
		color_2=read_yuv_data(2);
		printf("Color0 size:%d,Colort1:%d,Color2:%d\n",color_0,color_1,color_2);

		//这一帧被显示的时间
		frame->pts = frameIdx;
		printf("The frame pts:%d\n",frameIdx);
		//编码
		//将AVFrame编码成avpack
		//got_packet表示是否完成编码出一个完整的数据包
		if ( avcodec_encode_video2(codecCtx, &pkt, frame, &got_packet) < 0)
		{
			printf("Error: encoding failed\n");
			return -2;
		}

		printf("Got packet flag:%d\n",got_packet);
		
		if ( got_packet )
		{
			//表示编码成一个完整的包
			printf("Write packet of frame %d,size = %d\n",frameIdx,pkt.size);
			fwrite(pkt.data, 1, pkt.size, pFout);
			av_packet_unref(&pkt);
		}
		
	}

	//有时候编码器中还保留未输出的编码数据,下面就是将编码器中可能存在的数据进行输出
	for ( got_packet = 1 ; got_packet  ; )
	{
		//第三个参数设置为NULL就不会从frame中读取数据,而是编码自身内部的缓存数据
		if ( avcodec_encode_video2(codecCtx, &pkt, NULL, &got_packet) < 0)
		{
		    printf("Error: encoding failed\n");
			return -2;
		}

		printf("Read data from cache got_packet:%d,line:%d\n",got_packet,__LINE__);
		
		if ( got_packet )
		{
		    //表示编码成一个完整的包
		    printf("Write  cached packet size = %d\n",pkt.size);
			fwrite(pkt.data, 1, pkt.size, pFout);
			av_packet_unref(&pkt);
			
		}
	}

	//收尾
	fclose(pFin);
	fclose(pFout);
	avcodec_close(codecCtx);
	av_free(codecCtx);

	/*释放frame中保存的像素*/
	av_freep(&frame->data[0]);

	/*释放frame本身*/
	av_frame_free(&frame);
	
	return 0; 
}


static int read_yuv_data(int color)
{
	//color = 0 表示Y分量
	//color = 1 表示U分量
	//color = 2 表示V分量
	 int row_idx;
	int color_height = color == 0 ? frameHeight:frameHeight/2;
	int color_width = color == 0 ? frameWidth:frameWidth/2;

	//颜色空间大小
	int color_size = color_height*color_width;

	//各分量图像缓存的宽度,一般linesize是大于图像width的
	int color_stride = frame->linesize[color];

	//往frame里面读取数据
	if ( color_width == color_stride )
	{
		//表示颜色分量在AVFrame中是连续存放
	    fread(frame->data[color], color_size, 1, pFin);
	}
	else
	{
		//表示颜色分量不是连续存放,内存空间的边缘有填充的数据
		//数据不能直接读取了,要按行,一行一行的读取
		for ( row_idx = 0 ; row_idx < color_height ; row_idx++ )
		{
		    fread(frame->data[color]+row_idx*color_stride, color_width, 1, pFin);
		}
	}

	return color_size;
}

你可能感兴趣的:(FFMPEG,H.264与音视频编解码)