FFMPEG学习笔记---实现H264与Hevc文件播放(二)

实现先创建SDL窗体,解析完成视频文件后进行图像拉伸或者缩放处理显示。

注意sws_scale函数 要求为长宽均为16的倍数,否则会有警告提示字节不对齐。

#define TEST_H264  1
#define TEST_HEVC  0

int _tmain(int argc, _TCHAR* argv[])
{
	AVCodec *pCodec;  //编解码器结构体
    AVCodecContext *pCodecCtx= NULL;//编解码器信息头
	AVCodecParserContext *pCodecParserCtx=NULL;//解析输入的数据流信息头
	AVPacket packet;  //存储一帧压缩编码数据包
	AVFrame	*pFrame;  //存储一帧解码后像素数据
	AVFrame *pFramec;

	struct SwsContext *img_convert_ctx;   //用于图片拉伸显示
	//==========SDL相关部分==========
	int screen_w = 0;
	int screen_h = 0;

	SDL_Window *screen;				//窗体对象指针用于保存窗体创建返回结果
	SDL_Renderer *sdlRenderer;		//2D渲染环境指针,用于保存返回创建环境结果
	SDL_Texture *sdlTexture;		//创建表面纹理,用于显示图片
	SDL_Rect	 sdlRect;				//窗体大小

    
	FILE *fp_in;
	FILE *fp_out;
  
	const int in_buffer_size=4096; //一次文件读取长度
	uint8_t in_buffer[in_buffer_size + FF_INPUT_BUFFER_PADDING_SIZE]={0};//FF_INPUT_BUFFER_PADDING_SIZE:在解码的输入比特流的末尾所需的额外分配字节数
	uint8_t *cur_ptr;  //数据指针
	int cur_size;
  
	int ret, got_picture;
	int y_size;


#if TEST_HEVC
	enum AVCodecID codec_id=AV_CODEC_ID_HEVC;
	char filepath_in[]="bigbuckbunny_480x272.hevc";
#elif TEST_H264
	AVCodecID codec_id=AV_CODEC_ID_H264;   //解码器设置为h264
	char filepath_in[]="../bigbuckbunny_480x272.h264";
	
#else
	AVCodecID codec_id=AV_CODEC_ID_MPEG2VIDEO;
	char filepath_in[]="bigbuckbunny_480x272.m2v";
#endif

	char filepath_out[]="../bigbuckbunny_480x272.yuv";//输出文件
	int first_time=1;

#if USE_SWSCALE
	struct SwsContext *img_convert_ctx;
	AVFrame	*pFrameYUV;
	uint8_t *out_buffer;

#endif
	//av_log_set_level(AV_LOG_DEBUG);
	
	avcodec_register_all();	//注册所有编解码器

    pCodec = avcodec_find_decoder(codec_id);   //查找编解码器
    if (!pCodec) {
        printf("Codec not found\n");
        return -1;
    }
    pCodecCtx = avcodec_alloc_context3(pCodec);//为编解码信息头结构体分配内存
    if (!pCodecCtx){
        printf("Could not allocate video codec context\n");
        return -1;
    }

	pCodecParserCtx=av_parser_init(codec_id);  //初始化AVCodecParserContext。
	if (!pCodecParserCtx){
		printf("Could not allocate video parser context\n");
		return -1;
	}

    //if(pCodec->capabilities&CODEC_CAP_TRUNCATED)
    //    pCodecCtx->flags|= CODEC_FLAG_TRUNCATED; /* we do not send complete frames */
    
    if (avcodec_open2(pCodecCtx, pCodec, NULL) < 0) {//打开编解码器
        printf("Could not open codec\n");
        return -1;
    }
	//Input File
    fp_in = fopen(filepath_in, "rb");
    if (!fp_in) {
        printf("Could not open input stream\n");
        return -1;
    }

	int p_w = 640;//可设置等比例拉伸 480*272
	int p_h = 320;

    pFrame = av_frame_alloc(); //分配像素存储内存
	av_init_packet(&packet);	//初始化压缩数据包

	pFramec= av_frame_alloc();
	
	int num = av_image_get_buffer_size(AV_PIX_FMT_YUV420P, p_w, p_h,1);//计算420P所占内存大小
	unsigned char* out_buffer;
	out_buffer = (unsigned char *)av_malloc(num);  //为图片存储数组分配内存
	
	//将分配好的内存指针传递给AVFrame(图片存储)结构体
	av_image_fill_arrays(pFramec->data,pFramec->linesize,out_buffer,AV_PIX_FMT_YUV420P,p_w,p_h,1);

	//==SDL部分====
	//初始化SDL
	if(SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO | SDL_INIT_TIMER)) 
	{    
        printf( "Could not initialize SDL - %s\n", SDL_GetError());   
        return -1;  
    } 
#if 1 //这部分依赖于视频相关信息的创建需要在解码一帧视频后创建 或者采用FFMPEG 格式处理工具sws_getContext等函数
	//获取视频分辨率
	screen_w = 640;//pCodecCtx->width; ///这里直接调用 无法获取视频宽高(pCodecCtx并没有获取到数据信息,是空的)
	screen_h = 320;//pCodecCtx->height;
	
	
	 //SDL 2.0 支持多窗口 (创建播放窗口)	 SDL_WINDOWPOS_UNDEFINED(默认屏幕中间显示)
	screen = SDL_CreateWindow("Simplest ffmpeg player's Window", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED,  
        screen_w, screen_h, SDL_WINDOW_OPENGL); 

	if(!screen)
	{
		printf("SDL: could not create window - exiting:%s\n",SDL_GetError());    
        return -1; 
	}
	 // 基于窗口创建2d渲染器
	 sdlRenderer = SDL_CreateRenderer(screen, -1, 0); 

	 // 创建纹理(Texture)
	 sdlTexture = SDL_CreateTexture(sdlRenderer, SDL_PIXELFORMAT_IYUV, SDL_TEXTUREACCESS_STREAMING,
		  p_w,p_h);
	 
	 //SDL窗体位置 与大小
	 sdlRect.x=0;  
     sdlRect.y=0;  
     sdlRect.w=p_w;  
     sdlRect.h=p_h;
#endif

	while (1) {
		
        cur_size = fread(in_buffer, 1, in_buffer_size, fp_in);//读取文件
        if (cur_size == 0)
            break;
        cur_ptr=in_buffer;

        while (cur_size>0){
							//主要是通过av_parser_parse2拿到AVPaket数据,跟av_read_frame类似。
							//输入必须是只包含视频编码数据“裸流”(例如H.264、HEVC码流文件),而不能是包含封装格式的媒体数据(例如AVI、MKV、MP4)。
			int len = av_parser_parse2(		//获取编码数据包
				pCodecParserCtx, pCodecCtx,
				&packet.data, &packet.size,
				cur_ptr , cur_size ,
				AV_NOPTS_VALUE, AV_NOPTS_VALUE, AV_NOPTS_VALUE);
			 
			cur_ptr += len;
			cur_size -= len;

			if(packet.size==0)
				continue;
#if 0 	   //相关视频信息输出
			//Some Info from AVCodecParserContext
			printf("[Packet]Size:%6d\t",packet.size);
			switch(pCodecParserCtx->pict_type){
				case AV_PICTURE_TYPE_I: printf("Type:I\t");break;
				case AV_PICTURE_TYPE_P: printf("Type:P\t");break;
				case AV_PICTURE_TYPE_B: printf("Type:B\t");break;
				default: printf("Type:Other\t");break;
			}
			printf("Number:%4d\n",pCodecParserCtx->output_picture_number);
#endif
			ret = avcodec_decode_video2(pCodecCtx, pFrame, &got_picture, &packet); //解码像素数据
			if (ret < 0) {
				printf("Decode Error.\n");
				return ret;
			}
			if (got_picture) {		  //这里解码出来的像素数据可以用SDL直接刷新显示播放出来
				if(first_time)
				{
				  img_convert_ctx = sws_getContext(pCodecCtx->width,pCodecCtx->height,pCodecCtx->pix_fmt,
					p_w,p_h,AV_PIX_FMT_YUV420P,SWS_BICUBIC,NULL,NULL,NULL);
				  first_time = 0;
				}
				//sws_scale要求为长宽均为16的倍数	,否则会有警告提示字节不对齐
				sws_scale(img_convert_ctx, (const unsigned char* const*)pFrame->data, pFrame->linesize, 0, pCodecCtx->height,     
                  pFramec->data, pFramec->linesize); 
		
				//显示视频
				SDL_UpdateYUVTexture(sdlTexture, &sdlRect,  
                pFramec->data[0], pFramec->linesize[0],   //Y行的长度
                pFramec->data[1], pFramec->linesize[1],   //U行的长度
                pFramec->data[2], pFramec->linesize[2]);  //V行的长度 

			    SDL_RenderClear( sdlRenderer );								//清理一下渲染器
                SDL_RenderCopy( sdlRenderer, sdlTexture, NULL, &sdlRect);   //纹理复制给渲染器  srcrect:选择输入纹理的一块矩形区域作为输入。设置为NULL的时候整个纹理作为输入  
                SDL_RenderPresent( sdlRenderer );    						//显示图片
                //SDL End-----------------------  
                //Delay 40ms  //一秒钟25帧
                SDL_Delay(40);  

			}
		}

    }

	//Flush Decoder	   //清理解码器中的剩余数据
    packet.data = NULL;
    packet.size = 0;
	while(1){
		ret = avcodec_decode_video2(pCodecCtx, pFrame, &got_picture, &packet);
		if (ret < 0) {
			printf("Decode Error.\n");
			return ret;
		}
		if (!got_picture)
			break;
		if (got_picture) {
				//sws_scale要求为长宽均为16的倍数	,否则会有警告提示字节不对齐
				sws_scale(img_convert_ctx, (const unsigned char* const*)pFrame->data, pFrame->linesize, 0, pCodecCtx->height,     
                  pFramec->data, pFramec->linesize); 
			
				SDL_UpdateYUVTexture(sdlTexture, &sdlRect,  
                pFramec->data[0], pFramec->linesize[0],   //Y行的长度
                pFramec->data[1], pFramec->linesize[1],   //U行的长度
                pFramec->data[2], pFramec->linesize[2]);  //V行的长度 

			    SDL_RenderClear( sdlRenderer );								//清理一下渲染器
                SDL_RenderCopy( sdlRenderer, sdlTexture, NULL, &sdlRect);   //纹理复制给渲染器  srcrect:选择输入纹理的一块矩形区域作为输入。设置为NULL的时候整个纹理作为输入  
                SDL_RenderPresent( sdlRenderer );    						//显示图片
                //SDL End-----------------------  
                //Delay 40ms  //一秒钟25帧
                SDL_Delay(40); 
			}

			printf("Flush Decoder: Succeed to decode 1 frame!\n");
		}


    fclose(fp_in);

#if USE_SWSCALE
	
	av_frame_free(&pFrameYUV);
#endif	
	sws_freeContext(img_convert_ctx);
	//清理工作
	SDL_Quit();  
    av_parser_close(pCodecParserCtx);
	av_frame_free(&pFrame);
	av_frame_free(&pFramec);
	avcodec_close(pCodecCtx);
	av_free(pCodecCtx);
	system("pause");
	return 0;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值