音视频处理之FFmpeg+SDL视频播放器( 四 )


2.SDL_UpdateTexture函数最后一个参数表示的是,一行像素数据的数据量
窗口的宽和高无所谓,但是纹理数据的宽和高要和视频数据的宽和高一致 。
3.对于大数据的调试,如果直接打印出来,数据太多不好分析,一般是写入到文件,然后用工具打开文件去分析 。
比如视频像素数据,可以写入到yuv文件,再用yuv分析工具来分析 。
4.脱离开发环境的独立播放器
在解决方案中的debug目录下的*.exe文件即为编译好的可执行文件 。
执行这个文件就可以脱离开发环境运行 。
5.调试时,在属性->调试->命令参数 中填的东西,就是程序的输入参数,也就是argv中的内容
平时独立的exe程序,只需在执行的时候后面加上参数就是输入参数了,如ffplay.exe那样 。
四、FFmpeg+SDL视频播放器代码实现
主要是FFmpegAndSDL.cpp文件,代码如下(基本上每一行都有注释):
1 /***************************************************************************** 2 * Copyright (C) 2017-2020 Hanson Yu All rights reserved. 3 ------------------------------------------------------------------------------ 4 * File Module : FFmpegAndSDL.cpp 5 * Description : FFmpegAndSDL Demo 6 7 8 * Created : 2017.09.21. 9 * Author : Yu Weifeng 10 * Function List : 11 * Last Modified : 12 * History : 13 * Modify Date Version Author Modification 14 * ----------------------------------------------- 15 * 2017/09/21 V1.0.0 Yu Weifeng Created 16 ******************************************************************************/ 17 18 #include "stdafx.h" 19 #include <stdio.h> 20 21 22 /*解决错误: 23 LNK2019 无法解析的外部符号 __imp__fprintf,该符号在函数 _ShowError 中被引用 24 25 原因: 26 ……这是链接库问题 27 就是工程里面没有添加那两个函数需要的库,#progma这个是代码链接库 28 第二句是vs2015兼容的问题 。29 lib库的vs编译版本 和 工程的vs开发版本 不一致 。30 导出函数定义变了 。所以要人为加一个函数导出 。31 */ 32 #pragma comment(lib, "legacy_stdio_definitions.lib") 33 extern "C" { FILE __iob_func[3] = { *stdin,*stdout,*stderr }; } 34 35 /* 36 __STDC_LIMIT_macROS and __STDC_CONSTANT_MACROS are a workaround to allow C++ programs to use stdint.h 37 macros specified in the C99 standard that aren't in the C++ standard. The macros, such as UINT8_MAX, INT64_MIN, 38 and INT32_C() may be defined already in C++ Applications in other ways. To allow the user to decide 39 if they want the macros defined as C99 does, many implementations require that __STDC_LIMIT_MACROS 40 and __STDC_CONSTANT_MACROS be defined before stdint.h is included. 41 42 This isn't part of the C++ standard, but it has been adopted by more than one implementation. 43 */ 44 #define __STDC_CONSTANT_MACROS 45 46 extern "C" 47 { 48 #include "libavcodec/avcodec.h" 49 #include "libavformat/avformat.h" 50 #include "libswscale/swscale.h" 51 #include "SDL2/SDL.h" 52 }; 53 54 55 //Refresh Event 自定义事件 56 #define PLAY_REFRESH_EVENT (SDL_USEREVENT + 1)//自定义刷新图像(播放)事件 57 #define PLAY_BREAK_EVENT (SDL_USEREVENT + 2) //自定义退出播放事件 58 59 60 static int g_iThreadExitFlag = 0; 61 /***************************************************************************** 62 -Fuction : RefreshPlayThread 63 -Description : RefreshPlayThread 64 -Input : 65 -Output : 66 -Return : 67 * Modify Date Version Author Modification 68 * ----------------------------------------------- 69 * 2017/09/21 V1.0.0 Yu Weifeng Created 70 ******************************************************************************/ 71 int RefreshPlayThread(void *opaque) 72 { 73 g_iThreadExitFlag = 0; 74 SDL_Event tEvent={0}; 75 76 while (!g_iThreadExitFlag) 77 { 78 tEvent.type = PLAY_REFRESH_EVENT; 79 SDL_PushEvent(&tEvent);//发送事件给其他线程 80 SDL_Delay(20);//延时函数 填40的时候,视频会有种卡的感觉 81 } 82 //Break 83 g_iThreadExitFlag = 0; 84 tEvent.type = PLAY_BREAK_EVENT; 85 SDL_PushEvent(&tEvent);//发送事件给其他线程 发送一个事件 86 87 return 0; 88 } 89 90 /***************************************************************************** 91 -Fuction : main 92 -Description : main 93 -Input : 94 -Output : 95 -Return : 96 * Modify Date Version Author Modification 97 * ----------------------------------------------- 98 * 2017/09/21 V1.0.0 Yu Weifeng Created 99 ******************************************************************************/ 100 int main(int argc, char* argv[]) 101 { 102 /*------------FFmpeg----------------*/ 103 const char *strFilePath = "屌丝男士.mov"; 104 AVFormatContext *ptFormatContext = NULL;//封装格式上下文,内部包含所有的视频信息 105 int i = 0; 106 int iVideoindex=0;//纯视频信息在音视频流中的位置,也就是指向音视频流数组中的视频元素 107 AVCodecContext *ptCodecContext;//编码器相关信息上下文,内部包含编码器相关的信息,指向AVFormatContext中的streams成员中的codec成员 108 AVCodec *ptCodec;//编码器,使用函数avcodec_find_decoder或者,该函数需要的id参数,来自于ptCodecContext中的codec_id成员 109 AVFrame *ptFrame=NULL;//存储一帧解码后像素(采样)数据 110 AVFrame *ptFrameAfterScale=NULL;//存储(解码数据)转换后的像素(采样)数据 111 unsigned char *pucFrameAfterScaleBuf=NULL;//用于存储ptFrameAfterScale中的像素(采样)缓冲数据 112 AVPacket *ptPacket=NULL;//存储一帧压缩编码数据 113 int iRet =0; 114 int iGotPicture=0;//解码函数的返回参数,got_picture_ptr Zero if no frame could be decompressed, otherwise, it is nonzero 115 116 /*------------SDL----------------*/ 117 int iScreenWidth=0, iScreenHeight=0;//视频的宽和高,指向ptCodecContext中的宽和高 118 SDL_Window *ptSdlWindow=NULL;//用于sdl显示视频的窗口(用于显示的屏幕) 119 SDL_Renderer* ptSdlRenderer=NULL;//sdl渲染器,把纹理数据画(渲染)到window上 120 SDL_Texture* ptSdlTexture=NULL;//sdl纹理数据,用于存放像素(采样)数据,然后给渲染器 121 SDL_Rect tSdlRect ={0};//正方形矩形结构,存了矩形的坐标,长宽,以便确定纹理数据画在哪个位置,确定位置用,比如画在左上角就用这个来确定 。被渲染器调用 122 SDL_Thread *ptVideoControlTID=NULL;//sdl线程id,线程的句柄 123 SDL_Event tSdlEvent = {0};//sdl事件,代表一个事件 124 125 /*------------像素数据处理----------------*/ 126 struct SwsContext *ptImgConvertInfo;//图像转换(上下文)信息,图像转换函数sws_scale需要的参数,由sws_getContext函数赋值 127 128 129 130 /*------------FFmpeg----------------*/ 131 av_register_all();//注册FFmpeg所有组件 132 avformat_network_init();//初始化网络组件 133 134 ptFormatContext = avformat_alloc_context();//分配空间给ptFormatContext 135 if (avformat_open_input(&ptFormatContext, strFilePath, NULL, NULL) != 0) 136 {//打开输入视频文件 137 printf("Couldn't open input stream.n"); 138 return -1; 139 } 140 if (avformat_find_stream_info(ptFormatContext, NULL)<0) 141 {//获取视频文件信息 142 printf("Couldn't find stream information.n"); 143 return -1; 144 } 145 //获取编码器相关信息上下文,并赋值给ptCodecContext 146 iVideoindex = -1; 147 for (i = 0; i<ptFormatContext->nb_streams; i++) 148 { 149 if (ptFormatContext->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO) 150 { 151 iVideoindex = i; 152 break; 153 } 154 } 155 if (iVideoindex == -1) 156 { 157 printf("Didn't find a video stream.n"); 158 return -1; 159 } 160 ptCodecContext = ptFormatContext->streams[iVideoindex]->codec; 161 162 ptCodec = avcodec_find_decoder(ptCodecContext->codec_id);//查找解码器 163 if (ptCodec == NULL) 164 { 165 printf("Codec not found.n"); 166 return -1; 167 } 168 if (avcodec_open2(ptCodecContext, ptCodec, NULL)<0) 169 {//打开解码器 170 printf("Could not open codec.n"); 171 return -1; 172 } 173 174 ptPacket = (AVPacket *)av_malloc(sizeof(AVPacket));//分配保存解码前数据的空间 175 ptFrame = av_frame_alloc();//分配结构体空间,结构体内部的指针指向的数据暂未分配,用于保存图像转换前的像素数据 176 177 /*------------像素数据处理----------------*/ 178 ptFrameAfterScale = av_frame_alloc();//分配结构体空间,结构体内部的指针指向的数据暂未分配,用于保存图像转换后的像素数据 179 pucFrameAfterScaleBuf = (uint8_t *)av_malloc(avpicture_get_size(PIX_FMT_YUV420P, ptCodecContext->width, ptCodecContext->height));//分配保存数据的空间 180 /*int avpicture_fill(AVPicture *picture, uint8_t *ptr,int pix_fmt, int width, int height); 181 这个函数的使用本质上是为已经分配的空间的结构体(AVPicture *)ptFrame挂上一段用于保存数据的空间,182 这个结构体中有一个指针数组data[AV_NUM_DATA_POINTERS],挂在这个数组里 。一般我们这么使用: 183 1) pFrameRGB=avcodec_alloc_frame(); 184 2) numBytes=avpicture_get_size(PIX_FMT_RGB24, pCodecCtx->width,pCodecCtx->height); 185 buffer=(uint8_t *)av_malloc(numBytes*sizeof(uint8_t)); 186 3) avpicture_fill((AVPicture *)pFrameRGB, buffer, PIX_FMT_RGB24,pCodecCtx->width, pCodecCtx->height); 187 以上就是为pFrameRGB挂上buffer 。这个buffer是用于存缓冲数据的 。188 ptFrame为什么不用fill空间 。主要是下面这句: 189 avcodec_decode_video(pCodecCtx, pFrame, &frameFinished,packet.data, packet.size); 190 很可能是ptFrame已经挂上了packet.data,所以就不用fill了 。*/ 191 avpicture_fill((AVPicture *)ptFrameAfterScale, pucFrameAfterScaleBuf, PIX_FMT_YUV420P, ptCodecContext->width, ptCodecContext->height); 192 //sws开头的函数用于处理像素(采样)数据 193 ptImgConvertInfo = sws_getContext(ptCodecContext->width, ptCodecContext->height, ptCodecContext->pix_fmt, 194 ptCodecContext->width, ptCodecContext->height, PIX_FMT_YUV420P, SWS_BICUBIC, NULL, NULL, NULL);//获取图像转换(上下文)信息 195 196 /*------------SDL----------------*/ 197 if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_TIMER)) 198 {//初始化SDL系统 199 printf("Could not initialize SDL - %sn", SDL_GetError()); 200 return -1; 201 } 202 //SDL 2.0 Support for multiple windows 203 iScreenWidth = ptCodecContext->width; 204 iScreenHeight = ptCodecContext->height; 205 ptSdlWindow = SDL_CreateWindow("Simplest ffmpeg player's Window", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, 206 iScreenWidth, iScreenHeight, SDL_WINDOW_OPENGL);//创建窗口SDL_Window 207 208 if (!ptSdlWindow) 209 { 210 printf("SDL: could not create window - exiting:%sn", SDL_GetError()); 211 return -1; 212 } 213 ptSdlRenderer = SDL_CreateRenderer(ptSdlWindow, -1, 0);//创建渲染器SDL_Renderer 214 //IYUV: Y + U + V (3 planes) 215 //YV12: Y + V + U (3 planes) 216 //创建纹理SDL_Texture 217 ptSdlTexture = SDL_CreateTexture(ptSdlRenderer, SDL_PIXELFORMAT_IYUV, SDL_TEXTUREACCESS_STREAMING, ptCodecContext->width, ptCodecContext->height); 218 219 tSdlRect.x = 0;//x y值是左上角为圆点开始的坐标值,调整x y值以及w h值,就可以实现在窗口的指定位置显示,没有画面的地方为黑框 220 tSdlRect.y = 0;//当x y等于0,w h等于窗口的宽高时即为全屏显示,此时调整宽高大小,只需调整窗口大小即可 221 tSdlRect.w = iScreenWidth; 222 tSdlRect.h = iScreenHeight; 223 224 ptVideoControlTID = SDL_CreateThread(RefreshPlayThread, NULL, NULL);//创建一个线程 225 226 while (1) 227 {//Event Loop 228 SDL_WaitEvent(&tSdlEvent);//Wait,等待其他线程过来的事件 229 if (tSdlEvent.type == PLAY_REFRESH_EVENT) //自定义刷新图像(播放)事件 230 { 231 /*------------FFmpeg----------------*/ 232 if (av_read_frame(ptFormatContext, ptPacket) >= 0) //从输入文件读取一帧压缩数据 233 { 234 if (ptPacket->stream_index == iVideoindex) 235 { 236 iRet = avcodec_decode_video2(ptCodecContext, ptFrame, &iGotPicture, ptPacket);//解码一帧压缩数据 237 if (iRet < 0) 238 { 239 printf("Decode Error.n"); 240 return -1; 241 } 242 if (iGotPicture) 243 { 244 //图像转换,sws_scale()函数需要用到的转换信息,即第一个参数,是由sws_getContext函数获得的 245 sws_scale(ptImgConvertInfo, (const uint8_t* const*)ptFrame->data, ptFrame->linesize, 0, ptCodecContext->height, ptFrameAfterScale->data, ptFrameAfterScale->linesize); 246 247 /*------------SDL----------------*/ 248 SDL_UpdateTexture(ptSdlTexture, NULL, ptFrameAfterScale->data[0], ptFrameAfterScale->linesize[0]);//设置(更新)纹理的数据 249 SDL_RenderClear(ptSdlRenderer);//先清除渲染器里的数据 250 //SDL_RenderCopy( ptSdlRenderer, ptSdlTexture, &tSdlRect, &tSdlRect ); //将纹理的数据拷贝给渲染器 251 SDL_RenderCopy(ptSdlRenderer, ptSdlTexture, NULL, NULL);//将纹理的数据拷贝给渲染器 252 SDL_RenderPresent(ptSdlRenderer);//显示 253 } 254 } 255 av_free_packet(ptPacket);//释放空间 256 } 257 else 258 { 259 g_iThreadExitFlag = 1;//Exit Thread 260 } 261 } 262 else if (tSdlEvent.type == SDL_QUIT) //也是SDL自带的事件,当点击窗口的×时触发//SDL_WINDOWENVENT sdl系统自带的事件,当拉伸窗口的时候会触发 263 { 264 g_iThreadExitFlag = 1; 265 } 266 else if (tSdlEvent.type == PLAY_BREAK_EVENT) //自定义退出播放事件 267 { 268 break; 269 } 270 271 } 272 273 /*------------像素数据处理----------------*/ 274 sws_freeContext(ptImgConvertInfo);//释放空间 275 276 /*------------SDL----------------*/ 277 SDL_Quit();//退出SDL系统 278 279 /*------------FFmpeg----------------*/ 280 av_frame_free(&ptFrameAfterScale);//释放空间 281 av_frame_free(&ptFrame);//释放空间 282 avcodec_close(ptCodecContext);//关闭解码器 283 avformat_close_input(&ptFormatContext);//关闭输入视频文件 284 285 return 0; 286 } FFmpegAndSDL.cpp具体代码见github:


推荐阅读