代码来源于雷神博客,作为学习又敲了一遍,并添加了详细注释加深理解。
链接:https://blog.youkuaiyun.com/leixiaohua1020/article/details/38868499
至于环境配置可以翻看我的前一篇博客。
- // FFmpeg_Test1.cpp : 定义控制台应用程序的入口点。
- //
- #include "stdafx.h"
- #define _STDC_CONSTANT_MACROS //ffmpeg开发文档要求包含
- #ifdef _WIN32
- extern"C"
- {
- #include"libavcodec\avcodec.h" //解码库
- #include"libavformat\avformat.h" //格式封装库
- #include"libswscale\swscale.h" //像素数据格式转换
- #include"libavutil\imgutils.h" //图像数据处理工具
- #include"SDL2\SDL.h" //视频窗口
- }
- #endif
- #define OUTPUT_YUV420P 0
- int _tmain(int argc, _TCHAR* argv[])
- {
- //======为播放视频文件做前期准备工作==================
- int i,videoindex; //用于查找码流信息
- AVFormatContext *pFormatCtx; //封装格式上下文结构体,保存视频文件封装格式相关内容
- AVCodecContext *pCodecCtx; //编码器上下文相关,保存音视频解码器相关内容
- AVCodec * pCodec; //各种视频编解码对应结构体
- AVPacket *packet; //存储一帧压缩编码数据包
- AVFrame *pFrame,*pFrameYUV; //存储一帧解码后像素数据
- unsigned char* out_buffer; //图像缓冲区
- int ret, got_picture;
- struct SwsContext *img_convert_ctx; //此结构体指针类型不可出现拼写不一致现象
- char* filepath = "../video.mp4"; //文件路径
- int y_size; //Yuv文件大小
- //==========SDL相关部分==========
- int screen_w = 0;
- int screen_h = 0;
- SDL_Window *screen; //窗体对象指针用于保存窗体创建返回结果
- SDL_Renderer *sdlRenderer; //2D渲染环境指针,用于保存返回创建环境结果
- SDL_Texture *sdlTexture; //创建表面纹理,用于显示图片
- SDL_Rect sdlRect; //窗体大小
- //============用于保存YUV文件===========
- FILE *fp_yuv;
- //======ffmpeg相关查找视频文件信息 ======
- av_register_all(); // 注册所有的codec 编解码器
- avformat_network_init(); //网络协议初始化类似于调用 WSAStartup() 函数去初始化Winsock 服务 (windows特有)
- pFormatCtx = avformat_alloc_context(); //初始化结构体(分配内存)
- if(avformat_open_input(&pFormatCtx,filepath,NULL,NULL)!=0) //打开输入流文件并且获取信息头 *important
- {
- printf("Couldn't open input stream.\n");
- return -1;
- }
- if(avformat_find_stream_info(pFormatCtx,NULL)<0) //解析媒体文件包并进一步获取流信息 尤其是音频文件的详细信息会更加完善
- {
- printf("Couldn't find stream information.\n");
- return -1;
- }
- videoindex = -1;
- for(i = 0;i < pFormatCtx->nb_streams; i++) //查找视频流(一般包含 音频流 1 与 视频流 0 两种)
- {
- if(pFormatCtx->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO) //判断是否为视频流
- {
- videoindex = i;
- break;
- }
- }
- if(videoindex == -1)
- {
- printf("Didn't find a video stream.\n");
- return -1;
- }
- pCodecCtx = pFormatCtx->streams[videoindex]->codec; //获取编解码器相关信息
- pCodec = avcodec_find_decoder(pCodecCtx->codec_id); //获取编解码器
- if(pCodec == NULL)
- {
- printf("Codec not found.\n");
- return -1;
- }
- if(avcodec_open2(pCodecCtx,pCodec,NULL)<0) //打开解码器并进一步完善编码器上下文相关信息以及各种分配内存检查参数
- {
- printf("Could not open codec.\n");
- return -1;
- }
- pFrame = av_frame_alloc(); //用来分配一个AVFrame(图片存储)结构体,并不负责分配内存,所以有下一步av_malloc
- pFrameYUV = av_frame_alloc();
- int num = av_image_get_buffer_size(AV_PIX_FMT_YUV420P, pCodecCtx->width, pCodecCtx->height,1);//计算420P所占内存大小
- out_buffer = (unsigned char *)av_malloc(num); //为图片存储数组分配内存
- //将分配好的内存指针传递给AVFrame(图片存储)结构体
- av_image_fill_arrays(pFrameYUV->data,pFrameYUV->linesize,out_buffer,AV_PIX_FMT_YUV420P,pCodecCtx->width,pCodecCtx->height,1);
- //为编码数据包分配内存,用于解包读取图片数据
- packet = (AVPacket *)av_malloc(sizeof(AVPacket));
- //=======================输出视频相关信息========================
- printf("-------------------File Information--------------------\n");
- av_dump_format(pFormatCtx,0,filepath,0);
- printf("-------------------------------------------------------\n");
- //结构体SwsContext,它记录进行图像格式转换时,源图像和目标图像的格式、大小分别是什么。然后即可调用sws_scale函数直接转换即可
- img_convert_ctx = sws_getContext(pCodecCtx->width,pCodecCtx->height,pCodecCtx->pix_fmt,
- pCodecCtx->width,pCodecCtx->height,AV_PIX_FMT_YUV420P,SWS_BICUBIC,NULL,NULL,NULL);
- #if OUTPUT_YUV420P
- fp_yuv=fopen("output.yuv","wb+");
- #endif
- //初始化SDL控件
- if(SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO | SDL_INIT_TIMER))
- {
- printf( "Could not initialize SDL - %s\n", SDL_GetError());
- return -1;
- }
- //获取视频分辨率
- 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;
- //==================视频解码读取==================
- while(av_read_frame(pFormatCtx,packet)>=0) //获取一帧压缩编码数据包
- {
- if(packet->stream_index == videoindex) //如果是视频
- {
- ret = avcodec_decode_video2(pCodecCtx,pFrame,&got_picture,packet); //解码一帧视频数据
- if(ret < 0 )
- {
- printf("Decode Error.\n");
- return -1;
- }
- if(got_picture) //该值为0表明没有图像可以解码,否则表明有图像可以解码
- {
- //将视频图片格式 转换到YUV420P (图片格式转换)
- sws_scale(img_convert_ctx, (const unsigned char* const*)pFrame->data, pFrame->linesize, 0, pCodecCtx->height,
- pFrameYUV->data, pFrameYUV->linesize);
- #if OUTPUT_YUV420P
- y_size=pCodecCtx->width*pCodecCtx->height;
- fwrite(pFrameYUV->data[0],1,y_size,fp_yuv); //Y
- fwrite(pFrameYUV->data[1],1,y_size/4,fp_yuv); //U
- fwrite(pFrameYUV->data[2],1,y_size/4,fp_yuv); //V
- #endif
- //SDL---------------------------
- #if 0 //更新纹理像素数据 1.目标纹理 2.矩形框 3.pixels像素数据 4.一行像素字节数 (后续深究效率问题,貌似解码非常慢)
- SDL_UpdateTexture( sdlTexture, NULL, pFrameYUV->data[0], pFrameYUV->linesize[0] );
- #else //效率更快
- SDL_UpdateYUVTexture(sdlTexture, &sdlRect,
- pFrameYUV->data[0], pFrameYUV->linesize[0], //Y行的长度
- pFrameYUV->data[1], pFrameYUV->linesize[1], //U行的长度
- pFrameYUV->data[2], pFrameYUV->linesize[2]); //V行的长度
- #endif
- SDL_RenderClear( sdlRenderer ); //清理一下渲染器
- SDL_RenderCopy( sdlRenderer, sdlTexture, NULL, &sdlRect); //纹理复制给渲染器 srcrect:选择输入纹理的一块矩形区域作为输入。设置为NULL的时候整个纹理作为输入
- SDL_RenderPresent( sdlRenderer ); //显示图片
- //SDL End-----------------------
- //Delay 40ms //一秒钟25帧
- SDL_Delay(40);
- }
- }
- av_free_packet(packet);//释放包内存
- }
- //=============后续清理工作==============================
- //当av_read_frame()循环退出的时候,实际上解码器中可能还包含剩余的几帧数据。因此需要通过“flush_decoder”将这几帧数据输出。
- while (true)
- {
- ret = avcodec_decode_video2(pCodecCtx, pFrame, &got_picture, packet);
- if (ret < 0)
- break;
- if (!got_picture)
- break;
- sws_scale(img_convert_ctx, (const unsigned char* const*)pFrame->data, pFrame->linesize, 0, pCodecCtx->height,
- pFrameYUV->data, pFrameYUV->linesize);
- #if OUTPUT_YUV420P
- int y_size=pCodecCtx->width*pCodecCtx->height;
- fwrite(pFrameYUV->data[0],1,y_size,fp_yuv); //Y
- fwrite(pFrameYUV->data[1],1,y_size/4,fp_yuv); //U
- fwrite(pFrameYUV->data[2],1,y_size/4,fp_yuv); //V
- #endif
- //=========SDL================
- SDL_UpdateTexture( sdlTexture, &sdlRect, pFrameYUV->data[0], pFrameYUV->linesize[0] );
- SDL_RenderClear( sdlRenderer );
- SDL_RenderCopy( sdlRenderer, sdlTexture, NULL, &sdlRect);
- SDL_RenderPresent( sdlRenderer );
- //SDL End========
- //Delay 40ms
- SDL_Delay(40);
- }
- sws_freeContext(img_convert_ctx); //释放内存
- #if OUTPUT_YUV420P
- fclose(fp_yuv);
- #endif
- //后续清理工作
- SDL_Quit();
- av_frame_free(&pFrameYUV);
- av_frame_free(&pFrame);
- avcodec_close(pCodecCtx);
- avformat_close_input(&pFormatCtx);
- return 0;