接上篇博文代码实现:
#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; //存储一帧解码后像素数据
//==========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";
char filepath_in [1024];
printf("please Input videofile:");
scanf("%s",filepath_in);
#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;
}
//Output File
fp_out = fopen(filepath_out, "wb");
if (!fp_out) {
printf("Could not open output YUV file\n");
return -1;
}
pFrame = av_frame_alloc(); //分配像素存储内存
av_init_packet(&packet); //初始化压缩数据包
//==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 0 //这部分依赖于视频相关信息的创建需要在解码一帧视频后创建 或者采用FFMPEG 格式处理工具sws_getContext等函数
//获取视频分辨率
screen_w = pCodecCtx->width; ///这里直接调用 无法获取视频宽高
screen_h = 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,
pCodecCtx->width,pCodecCtx->height);
//SDL窗体位置 与大小
sdlRect.x=0;
sdlRect.y=0;
sdlRect.w=screen_w;
sdlRect.h=screen_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)
{
//获取到相应的视频信息 长宽等
//初始化视频显示窗体
screen_w = pCodecCtx->width; ///这里直接调用 无法获取视频宽高
screen_h = 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,
pCodecCtx->width,pCodecCtx->height);
//SDL窗体位置 与大小
sdlRect.x=0;
sdlRect.y=0;
sdlRect.w=screen_w;
sdlRect.h=screen_h;
//只进入一次窗体创建
first_time=0;
}
//显示视频
SDL_UpdateYUVTexture(sdlTexture, &sdlRect,
pFrame->data[0], pFrame->linesize[0], //Y行的长度
pFrame->data[1], pFrame->linesize[1], //U行的长度
pFrame->data[2], pFrame->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) {
SDL_UpdateYUVTexture(sdlTexture, &sdlRect,
pFrame->data[0], pFrame->linesize[0], //Y行的长度
pFrame->data[1], pFrame->linesize[1], //U行的长度
pFrame->data[2], pFrame->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);
fclose(fp_out);
#if USE_SWSCALE
sws_freeContext(img_convert_ctx);
av_frame_free(&pFrameYUV);
#endif
//清理工作
SDL_Quit();
av_parser_close(pCodecParserCtx);
av_frame_free(&pFrame);
avcodec_close(pCodecCtx);
av_free(pCodecCtx);
system("pause");
return 0;
}
SDL窗口创建需要一定时间,解码一帧后再创建其窗口其实不是最好的选择,可以采用创建固定窗口大小,解码视频帧后将图片进行相应转换显示,才是正确思路。