一、FFmpeg+SDL+MFC视频播放器
1.MFC知识
1).创建MFC工程的方法
打开VC++
文件->新建->项目->MFC应用程序
应用程序类型->基于对话框
取消勾选"使用unicode库"
其中,在创建的过程中,单个文档表示意思是只有一个页面(窗口),多个文档表示的意思是有多个页面(窗口)。
2).设置控件
找到“工具箱”,就可以将相应的控件拖拽至应用程序对话框中
常用控件有:Button,Edit Control,Static Text等
找到“属性”选项卡
可以在“Caption”属性上修改控件上的文字
可以在“ID”属性上修改控件上的ID(ID是控件的标识,不可重复)
3).添加消息响应函数
双击Button控件,就可以给该控件添加消息响应函数。
在菜单栏的 项目->类向导 处,可以添加更多种类的消息响应函数。
如果已经添加了响应函数的控件删除时,不能直接删出,如果直接删除就要手动去删除添加的相关响应的操作代码,所以正确删除这个 一般是先点击项目 类向导中该控件对应的删除处理程序,再来进行删除界面上的。
熟悉的话,也可以在相应的地方手动进行操作。
4).MFC最简单的弹出消息框的函数是AfxMessageBox("xxx");
5).MFC中新建一个XX项目,则会自动生成一个XX.CPP类,同时如果有
一个对话框则还会有对应一个类 xxDlg.cpp(也是MFC自动生成),运行的过程是,先启动xx.cpp的初始化实例函数,由xx.cpp加载xxDlg.cpp
6).对话框的弹出:首先创建这个对话框的类,然后调用其相关的Doxxx的方法。
CFileDialog dlg();//打开一个对话框
dlg.DoModel() //弹出模式对话框,不能操作别的,只能操作这个对话框中的,点击确认后才可以继续往下走
7).在类向导中的成员变量栏,定义指定ID的成员变量,就可以通过该变量来访问ID对应的控件,或者说是操作对应的控件。
8).SDLmain.lib只在控制台中用到
9).mfc中字符串的类为CString,同时printf在mfc中已经没有用了
10).MFC中事件触发的线程栈比较小,一般如果耗栈大的,必须开辟一个新的线程(AfsBeginThread(函数名,函数的输入参数),其中的线程函数格式固定)
2.FFmpeg + SDL + MFC实现图形界面视频播放器
1).FFmpeg解码器与MFC的整合
需要将视频文件路径从MFC界面上的 Edit Control 控件传递给FFmpeg解码器
GetWindowText()
2).SDL与MFC的整合
需要将SDL显示的画面绘制到MFC的 Picture Control 控件上。
SDL_CreateWindowFrom()
PS:SDL2有一个Bug。在系统退出的时候会把显示图像的控件隐藏起来,因此需要调用该控件的ShowWindow()方法将控件显示出来。
3).在播放器播放,就应该让SDL将画面渲染到播放器定义的组件上
原先SDL创建的窗口是在控制台上的,调用的是SDL_CreateWindow函数,现在是在MFC的控件上创建窗口则调用的是SDL_CreateWindowFrom()函数
4).SDL播放完会隐藏起来窗口,导致我们找不到窗口,解决方法:SDL退出后再次调用MFC的相关函数来显示窗口
5).播放器显示的地方可以是在frame类型的控件上,也可以是在BitMap上,这样停止的时候就可以显示这个BitMap控件了,也就是可以显示背景图片了。
6).如果要添加菜单栏,在资源视图中的添加资源,新建menu,然后进行填充就可以出来菜单
menu资源怎么放到mfc的界面对话框中,是通过在mfc界面对话框属性中的杂项menu栏填入资源menu的ID即可
二、ywfPlayer代码
FFmpeg + SDL + MFC实现的图形界面视频播放器,取了个名字称为ywfPlayer
主要是ywfPlayerDlg.cpp文件,代码如下(基本上每一行都有注释):
1 /***************************************************************************** 2 * Copyright (C) 2017-2020 Hanson Yu All rights reserved. 3 ------------------------------------------------------------------------------ 4 * File Module : ywfPlayerDlg.cpp 5 * Description : ywfPlayerDlg 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 "ywfPlayer.h" 20 #include "ywfPlayerDlg.h" 21 #include "afxdialogex.h" 22 23 24 #ifdef _DEBUG 25 #define new DEBUG_NEW 26 #endif 27 28 29 30 31 32 /***************************************************************************** 33 -Fuction : CDialogEx 34 -Description : 用于应用程序“关于”菜单项的 CAboutDlg 对话框 35 -Input : 36 -Output : 37 -Return : 38 * Modify Date Version Author Modification 39 * ----------------------------------------------- 40 * 2017/09/21 V1.0.0 Yu Weifeng Created 41 ******************************************************************************/ 42 class CAboutDlg : public CDialogEx 43 { 44 public: 45 CAboutDlg(); 46 47 // 对话框数据 48 #ifdef AFX_DESIGN_TIME 49 enum { IDD = IDD_ABOUTBOX }; 50 #endif 51 52 protected: 53 virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV 支持 54 55 // 实现 56 protected: 57 DECLARE_MESSAGE_MAP() 58 }; 59 /***************************************************************************** 60 -Fuction : CDialogEx 61 -Description : 用于应用程序“关于”菜单项的 CAboutDlg 对话框 62 -Input : 63 -Output : 64 -Return : 65 * Modify Date Version Author Modification 66 * ----------------------------------------------- 67 * 2017/09/21 V1.0.0 Yu Weifeng Created 68 ******************************************************************************/ 69 CAboutDlg::CAboutDlg() : CDialogEx(IDD_ABOUTBOX) 70 { 71 } 72 /***************************************************************************** 73 -Fuction : DoDataExchange 74 -Description : DoDataExchange 75 -Input : 76 -Output : 77 -Return : 78 * Modify Date Version Author Modification 79 * ----------------------------------------------- 80 * 2017/09/21 V1.0.0 Yu Weifeng Created 81 ******************************************************************************/ 82 void CAboutDlg::DoDataExchange(CDataExchange* pDX) 83 { 84 CDialogEx::DoDataExchange(pDX); 85 } 86 87 BEGIN_MESSAGE_MAP(CAboutDlg, CDialogEx) 88 END_MESSAGE_MAP() 89 90 91 // CywfPlayerDlg 对话框 92 93 94 /***************************************************************************** 95 -Fuction : CywfPlayerDlg 96 -Description : CywfPlayerDlg 97 -Input : 98 -Output : 99 -Return : 100 * Modify Date Version Author Modification 101 * ----------------------------------------------- 102 * 2017/09/21 V1.0.0 Yu Weifeng Created 103 ******************************************************************************/ 104 CywfPlayerDlg::CywfPlayerDlg(CWnd* pParent /*=NULL*/) 105 : CDialog(IDD_YWFPLAYER_DIALOG, pParent) 106 { 107 m_hIcon = AfxGetApp()->LoadIcon(IDI_ICON1); 108 } 109 /***************************************************************************** 110 -Fuction : DoDataExchange 111 -Description : DoDataExchange 112 -Input : 113 -Output : 114 -Return : 115 * Modify Date Version Author Modification 116 * ----------------------------------------------- 117 * 2017/09/21 V1.0.0 Yu Weifeng Created 118 ******************************************************************************/ 119 void CywfPlayerDlg::DoDataExchange(CDataExchange* pDX) 120 { 121 CDialog::DoDataExchange(pDX); 122 } 123 124 BEGIN_MESSAGE_MAP(CywfPlayerDlg, CDialog) 125 ON_WM_SYSCOMMAND() 126 ON_WM_PAINT() 127 ON_WM_QUERYDRAGICON() 128 // ON_BN_CLICKED(IDOK, &CywfPlayerDlg::OnBnClickedOk) 129 ON_BN_CLICKED(IDC_PLAY, &CywfPlayerDlg::OnBnClickedPlay) 130 ON_BN_CLICKED(IDPAUSE, &CywfPlayerDlg::OnBnClickedPause) 131 ON_BN_CLICKED(IDSTOP, &CywfPlayerDlg::OnBnClickedStop) 132 ON_COMMAND(ID_OPENFILE, &CywfPlayerDlg::Openfile) 133 //ON_COMMAND(IDABORT, &CywfPlayerDlg::OnIdAbort) 134 ON_COMMAND(IDEXIT, &CywfPlayerDlg::OnIdExit) 135 ON_COMMAND(ID_ABOUT, &CywfPlayerDlg::OnAbout) 136 END_MESSAGE_MAP() 137 138 139 140 /***************************************************************************** 141 -Fuction : OnInitDialog 142 -Description : // CywfPlayerDlg 消息处理程序 143 -Input : 144 -Output : 145 -Return : 146 * Modify Date Version Author Modification 147 * ----------------------------------------------- 148 * 2017/09/21 V1.0.0 Yu Weifeng Created 149 ******************************************************************************/ 150 BOOL CywfPlayerDlg::OnInitDialog() 151 { 152 CDialog::OnInitDialog(); 153 154 // 将“关于...”菜单项添加到系统菜单中。 155 156 // IDM_ABOUTBOX 必须在系统命令范围内。 157 ASSERT((IDM_ABOUTBOX & 0xFFF0) == IDM_ABOUTBOX); 158 ASSERT(IDM_ABOUTBOX < 0xF000); 159 160 CMenu* pSysMenu = GetSystemMenu(FALSE); 161 if (pSysMenu != NULL) 162 { 163 BOOL bNameValid; 164 CString strAboutMenu; 165 bNameValid = strAboutMenu.LoadString(IDS_ABOUTBOX); 166 ASSERT(bNameValid); 167 if (!strAboutMenu.IsEmpty()) 168 { 169 pSysMenu->AppendMenu(MF_SEPARATOR); 170 pSysMenu->AppendMenu(MF_STRING, IDM_ABOUTBOX, strAboutMenu); 171 } 172 } 173 174 // 设置此对话框的图标。 当应用程序主窗口不是对话框时,框架将自动 175 // 执行此操作 176 SetIcon(m_hIcon, TRUE); // 设置大图标 177 SetIcon(m_hIcon, FALSE); // 设置小图标 178 179 // TODO: 在此添加额外的初始化代码 180 181 return TRUE; // 除非将焦点设置到控件,否则返回 TRUE 182 } 183 /***************************************************************************** 184 -Fuction : OnSysCommand 185 -Description : OnSysCommand 186 -Input : 187 -Output : 188 -Return : 189 * Modify Date Version Author Modification 190 * ----------------------------------------------- 191 * 2017/09/21 V1.0.0 Yu Weifeng Created 192 ******************************************************************************/ 193 void CywfPlayerDlg::OnSysCommand(UINT nID, LPARAM lParam) 194 { 195 if ((nID & 0xFFF0) == IDM_ABOUTBOX) 196 { 197 CAboutDlg dlgAbout; 198 dlgAbout.DoModal(); 199 } 200 else 201 { 202 CDialog::OnSysCommand(nID, lParam); 203 } 204 } 205 206 207 /***************************************************************************** 208 -Fuction : OnPaint 209 -Description : 210 // 如果向对话框添加最小化按钮,则需要下面的代码 211 // 来绘制该图标。 对于使用文档/视图模型的 MFC 应用程序, 212 // 这将由框架自动完成。 213 -Input : 214 -Output : 215 -Return : 216 * Modify Date Version Author Modification 217 * ----------------------------------------------- 218 * 2017/09/21 V1.0.0 Yu Weifeng Created 219 ******************************************************************************/ 220 void CywfPlayerDlg::OnPaint() 221 { 222 if (IsIconic()) 223 { 224 CPaintDC dc(this); // 用于绘制的设备上下文 225 226 SendMessage(WM_ICONERASEBKGND, reinterpret_cast<WPARAM>(dc.GetSafeHdc()), 0); 227 228 // 使图标在工作区矩形中居中 229 int cxIcon = GetSystemMetrics(SM_CXICON); 230 int cyIcon = GetSystemMetrics(SM_CYICON); 231 CRect rect; 232 GetClientRect(&rect); 233 int x = (rect.Width() - cxIcon + 1) / 2; 234 int y = (rect.Height() - cyIcon + 1) / 2; 235 236 // 绘制图标 237 dc.DrawIcon(x, y, m_hIcon); 238 } 239 else 240 { 241 CDialog::OnPaint(); 242 } 243 } 244 245 246 /***************************************************************************** 247 -Fuction : OnQueryDragIcon 248 -Description : 249 //当用户拖动最小化窗口时系统调用此函数取得光标 250 //显示 251 -Input : 252 -Output : 253 -Return : 254 * Modify Date Version Author Modification 255 * ----------------------------------------------- 256 * 2017/09/21 V1.0.0 Yu Weifeng Created 257 ******************************************************************************/ 258 HCURSOR CywfPlayerDlg::OnQueryDragIcon() 259 { 260 return static_cast<HCURSOR>(m_hIcon); 261 } 262 263 264 265 266 /***************************************************************************** 267 -Fuction : OnBnClickedOk 268 -Description : 269 // TODO: 在此添加控件通知处理程序代码 270 -Input : 271 -Output : 272 -Return : 273 * Modify Date Version Author Modification 274 * ----------------------------------------------- 275 * 2017/09/21 V1.0.0 Yu Weifeng Created 276 ******************************************************************************/ 277 void CywfPlayerDlg::OnBnClickedPlay() 278 { 279 // CString strSet("123"); 280 // AfxMessageBox(strSet); 281 // CDialog::OnOK(); 282 m_iThreadPauseFlag = 0; 283 } 284 285 /***************************************************************************** 286 -Fuction : OnBnClickedPause 287 -Description : 288 // TODO: 在此添加控件通知处理程序代码 289 -Input : 290 -Output : 291 -Return : 292 * Modify Date Version Author Modification 293 * ----------------------------------------------- 294 * 2017/09/21 V1.0.0 Yu Weifeng Created 295 ******************************************************************************/ 296 void CywfPlayerDlg::OnBnClickedPause() 297 { 298 m_iThreadPauseFlag = 1; 299 } 300 301 /***************************************************************************** 302 -Fuction : OnBnClickedStop 303 -Description : 304 // TODO: 在此添加控件通知处理程序代码 305 -Input : 306 -Output : 307 -Return : 308 * Modify Date Version Author Modification 309 * ----------------------------------------------- 310 * 2017/09/21 V1.0.0 Yu Weifeng Created 311 ******************************************************************************/ 312 void CywfPlayerDlg::OnBnClickedStop() 313 { 314 m_iThreadExitFlag = 1; 315 } 316 317 /***************************************************************************** 318 -Fuction : Openfile 319 -Description : 320 // TODO: 在此添加控件通知处理程序代码 321 -Input : 322 -Output : 323 -Return : 324 * Modify Date Version Author Modification 325 * ----------------------------------------------- 326 * 2017/09/21 V1.0.0 Yu Weifeng Created 327 ******************************************************************************/ 328 void CywfPlayerDlg::Openfile() 329 { 330 CFileDialog dlg(TRUE, NULL, NULL, NULL, NULL);///TRUE为OPEN对话框,FALSE为SAVE AS对话框 331 if (dlg.DoModal() == IDOK) 332 { 333 m_strURL = dlg.GetPathName(); 334 pThreadYwfPlayer = AfxBeginThread((AFX_THREADPROC)YwfPlayerThread, (LPVOID)this);//开启线程 335 } 336 } 337 338 /***************************************************************************** 339 -Fuction : OnIdExit 340 -Description : 341 // TODO: 在此添加控件通知处理程序代码 342 -Input : 343 -Output : 344 -Return : 345 * Modify Date Version Author Modification 346 * ----------------------------------------------- 347 * 2017/09/21 V1.0.0 Yu Weifeng Created 348 ******************************************************************************/ 349 void CywfPlayerDlg::OnIdExit() 350 { 351 OnCancel();//mfc函数 352 } 353 354 /***************************************************************************** 355 -Fuction : OnAbout 356 -Description : 357 // TODO: 在此添加控件通知处理程序代码 358 -Input : 359 -Output : 360 -Return : 361 * Modify Date Version Author Modification 362 * ----------------------------------------------- 363 * 2017/09/21 V1.0.0 Yu Weifeng Created 364 ******************************************************************************/ 365 void CywfPlayerDlg::OnAbout() 366 { 367 CAboutDlg dlg; 368 dlg.DoModal(); 369 } 370 371 /***************************************************************************** 372 -Fuction : RefreshPlayThread 373 -Description : 必须定义成静态的(c函数)才能作为线程函数 374 -Input : 375 -Output : 376 -Return : 377 * Modify Date Version Author Modification 378 * ----------------------------------------------- 379 * 2017/09/21 V1.0.0 Yu Weifeng Created 380 ******************************************************************************/ 381 int CywfPlayerDlg::RefreshPlayThread(void *args) 382 { 383 if (NULL != args) 384 { 385 CywfPlayerDlg *pThis = (CywfPlayerDlg *)args; 386 return pThis->RefreshPlayProc(NULL);//静态成员引用非静态成员必须要这样 387 } 388 } 389 /***************************************************************************** 390 -Fuction : RefreshPlayThread 391 -Description : RefreshPlayThread 392 -Input : 393 -Output : 394 -Return : 395 * Modify Date Version Author Modification 396 * ----------------------------------------------- 397 * 2017/09/21 V1.0.0 Yu Weifeng Created 398 ******************************************************************************/ 399 int CywfPlayerDlg::RefreshPlayProc(void *opaque) 400 { 401 m_iThreadExitFlag = 0; 402 m_iThreadPauseFlag = 0; 403 SDL_Event tEvent = { 0 }; 404 //AfxMessageBox(m_strURL); 405 while (!m_iThreadExitFlag) 406 { 407 if (0 == m_iThreadPauseFlag) 408 { 409 tEvent.type = PLAY_REFRESH_EVENT; 410 SDL_PushEvent(&tEvent);//发送事件给其他线程 411 } 412 SDL_Delay(20);//延时函数 填40的时候,视频会有种卡的感觉 413 } 414 //Break 415 m_iThreadExitFlag = 0; 416 m_iThreadPauseFlag = 0; 417 tEvent.type = PLAY_BREAK_EVENT; 418 SDL_PushEvent(&tEvent);//发送事件给其他线程 发送一个事件 419 420 return 0; 421 } 422 423 424 /***************************************************************************** 425 -Fuction : RefreshPlayThread 426 -Description : 必须定义成静态的(c函数)才能作为线程函数 427 -Input : 428 -Output : 429 -Return : 430 * Modify Date Version Author Modification 431 * ----------------------------------------------- 432 * 2017/09/21 V1.0.0 Yu Weifeng Created 433 ******************************************************************************/ 434 UINT CywfPlayerDlg::YwfPlayerThread(LPVOID args) 435 { 436 if (NULL != args) 437 { 438 CywfPlayerDlg *pThis = (CywfPlayerDlg *)args; 439 return pThis->YwfPlayerProc(args);//静态成员引用非静态成员必须要这样 440 } 441 } 442 /***************************************************************************** 443 -Fuction : main 444 -Description : main 445 -Input : 446 -Output : 447 -Return : 448 * Modify Date Version Author Modification 449 * ----------------------------------------------- 450 * 2017/09/21 V1.0.0 Yu Weifeng Created 451 ******************************************************************************/ 452 int CywfPlayerDlg::YwfPlayerProc(LPVOID args) 453 { 454 CywfPlayerDlg *dlg = (CywfPlayerDlg *)args; 455 /*------------FFmpeg----------------*/ 456 AVFormatContext *ptFormatContext = NULL;//封装格式上下文,内部包含所有的视频信息 457 int i = 0; 458 int iVideoindex = 0;//纯视频信息在音视频流中的位置,也就是指向音视频流数组中的视频元素 459 AVCodecContext *ptCodecContext;//编码器相关信息上下文,内部包含编码器相关的信息,指向AVFormatContext中的streams成员中的codec成员 460 AVCodec *ptCodec;//编码器,使用函数avcodec_find_decoder或者,该函数需要的id参数,来自于ptCodecContext中的codec_id成员 461 AVFrame *ptFrame = NULL;//存储一帧解码后像素(采样)数据 462 AVFrame *ptFrameAfterScale = NULL;//存储(解码数据)转换后的像素(采样)数据 463 unsigned char *pucFrameAfterScaleBuf = NULL;//用于存储ptFrameAfterScale中的像素(采样)缓冲数据 464 AVPacket *ptPacket = NULL;//存储一帧压缩编码数据 465 int iRet = 0; 466 int iGotPicture = 0;//解码函数的返回参数,got_picture_ptr Zero if no frame could be decompressed, otherwise, it is nonzero 467 468 /*------------SDL----------------*/ 469 int iScreenWidth = 0, iScreenHeight = 0;//视频的宽和高,指向ptCodecContext中的宽和高 470 SDL_Window *ptSdlWindow = NULL;//用于sdl显示视频的窗口(用于显示的屏幕) 471 SDL_Renderer* ptSdlRenderer = NULL;//sdl渲染器,把纹理数据画(渲染)到window上 472 SDL_Texture* ptSdlTexture = NULL;//sdl纹理数据,用于存放像素(采样)数据,然后给渲染器 473 SDL_Rect tSdlRect = { 0 };//正方形矩形结构,存了矩形的坐标,长宽,以便确定纹理数据画在哪个位置,确定位置用,比如画在左上角就用这个来确定。被渲染器调用 474 SDL_Thread *ptVideoControlTID = NULL;//sdl线程id,线程的句柄 475 SDL_Event tSdlEvent = { 0 };//sdl事件,代表一个事件 476 477 /*------------像素数据处理----------------*/ 478 struct SwsContext *ptImgConvertInfo;//图像转换(上下文)信息,图像转换函数sws_scale需要的参数,由sws_getContext函数赋值 479 480 481 /*------------FFmpeg----------------*/ 482 av_register_all();//注册FFmpeg所有组件 483 avformat_network_init();//初始化网络组件 484 //TRACE1("avcodec_configuration %s ", avcodec_configuration()); 485 486 ptFormatContext = avformat_alloc_context();//分配空间给ptFormatContext 487 488 //snprintf(strFilePath,sizeof(strFilePath),"%s", m_strURL);//不能使用如此字符,包括strncpy,memcpy都不行,因为在unicode下不行,同时拷贝传入的长度值从GetLength()来获取有问题 489 int iLength = m_strURL.GetLength(); 490 int iBytes = WideCharToMultiByte(CP_ACP, 0, m_strURL, iLength, NULL, 0, NULL, NULL); 491 char* strFilePath = new char[iBytes + 1]; 492 memset(strFilePath, 0, iLength + 1); 493 WideCharToMultiByte(CP_OEMCP, 0, m_strURL, iLength, strFilePath, iBytes, NULL, NULL); 494 strFilePath[iBytes] = 0; 495 496 iRet = avformat_open_input(&ptFormatContext, strFilePath, NULL, NULL); 497 if (iRet != 0) 498 {//打开输入视频文件 499 TRACE1 ("Couldn't open input stream:%s,%d ", strFilePath, iRet); 500 return -1; 501 } 502 if (avformat_find_stream_info(ptFormatContext, NULL)<0) 503 {//获取视频文件信息 504 OutputDebugString (L"Couldn't find stream information. "); 505 return -1; 506 } 507 //获取编码器相关信息上下文,并赋值给ptCodecContext 508 iVideoindex = -1; 509 for (i = 0; i<ptFormatContext->nb_streams; i++) 510 { 511 if (ptFormatContext->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO) 512 { 513 iVideoindex = i; 514 break; 515 } 516 } 517 if (iVideoindex == -1) 518 { 519 OutputDebugString (L"Didn't find a video stream. "); 520 return -1; 521 } 522 ptCodecContext = ptFormatContext->streams[iVideoindex]->codec; 523 524 ptCodec = avcodec_find_decoder(ptCodecContext->codec_id);//查找解码器 525 if (ptCodec == NULL) 526 { 527 OutputDebugString (L"Codec not found. "); 528 return -1; 529 } 530 if (avcodec_open2(ptCodecContext, ptCodec, NULL)<0) 531 {//打开解码器 532 OutputDebugString (L"Could not open codec. "); 533 return -1; 534 } 535 536 ptPacket = (AVPacket *)av_malloc(sizeof(AVPacket));//分配保存解码前数据的空间 537 ptFrame = av_frame_alloc();//分配结构体空间,结构体内部的指针指向的数据暂未分配,用于保存图像转换前的像素数据 538 539 /*------------像素数据处理----------------*/ 540 ptFrameAfterScale = av_frame_alloc();//分配结构体空间,结构体内部的指针指向的数据暂未分配,用于保存图像转换后的像素数据 541 pucFrameAfterScaleBuf = (uint8_t *)av_malloc(avpicture_get_size(PIX_FMT_YUV420P, ptCodecContext->width, ptCodecContext->height));//分配保存数据的空间 542 /*int avpicture_fill(AVPicture *picture, uint8_t *ptr,int pix_fmt, int width, int height); 543 这个函数的使用本质上是为已经分配的空间的结构体(AVPicture *)ptFrame挂上一段用于保存数据的空间, 544 这个结构体中有一个指针数组data[AV_NUM_DATA_POINTERS],挂在这个数组里。一般我们这么使用: 545 1) pFrameRGB=avcodec_alloc_frame(); 546 2) numBytes=avpicture_get_size(PIX_FMT_RGB24, pCodecCtx->width,pCodecCtx->height); 547 buffer=(uint8_t *)av_malloc(numBytes*sizeof(uint8_t)); 548 3) avpicture_fill((AVPicture *)pFrameRGB, buffer, PIX_FMT_RGB24,pCodecCtx->width, pCodecCtx->height); 549 以上就是为pFrameRGB挂上buffer。这个buffer是用于存缓冲数据的。 550 ptFrame为什么不用fill空间。主要是下面这句: 551 avcodec_decode_video(pCodecCtx, pFrame, &frameFinished,packet.data, packet.size); 552 很可能是ptFrame已经挂上了packet.data,所以就不用fill了。*/ 553 avpicture_fill((AVPicture *)ptFrameAfterScale, pucFrameAfterScaleBuf, PIX_FMT_YUV420P, ptCodecContext->width, ptCodecContext->height); 554 //sws开头的函数用于处理像素(采样)数据 555 ptImgConvertInfo = sws_getContext(ptCodecContext->width, ptCodecContext->height, ptCodecContext->pix_fmt, 556 ptCodecContext->width, ptCodecContext->height, PIX_FMT_YUV420P, SWS_BICUBIC, NULL, NULL, NULL);//获取图像转换(上下文)信息 557 558 /*------------SDL----------------*/ 559 if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_TIMER)) 560 {//初始化SDL系统 561 TRACE1("Could not initialize SDL - %s ", SDL_GetError()); 562 return -1; 563 } 564 //SDL 2.0 Support for multiple windows 565 iScreenWidth = ptCodecContext->width; 566 iScreenHeight = ptCodecContext->height; 567 //创建窗口SDL_Window 568 //ptSdlWindow = SDL_CreateWindow("Simplest ffmpeg player's Window", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED,iScreenWidth, iScreenHeight, SDL_WINDOW_OPENGL); 569 //显示在MFC控件上 570 ptSdlWindow = SDL_CreateWindowFrom(dlg->GetDlgItem(IDC_SCREEN)->GetSafeHwnd()); 571 if (!ptSdlWindow) 572 { 573 TRACE1("SDL: could not create window - exiting:%s ", SDL_GetError()); 574 return -1; 575 } 576 ptSdlRenderer = SDL_CreateRenderer(ptSdlWindow, -1, 0);//创建渲染器SDL_Renderer 577 //IYUV: Y + U + V (3 planes) 578 //YV12: Y + V + U (3 planes) 579 //创建纹理SDL_Texture 580 ptSdlTexture = SDL_CreateTexture(ptSdlRenderer, SDL_PIXELFORMAT_IYUV, SDL_TEXTUREACCESS_STREAMING, ptCodecContext->width, ptCodecContext->height); 581 582 tSdlRect.x = 0;//x y值是左上角为圆点开始的坐标值,调整x y值以及w h值,就可以实现在窗口的指定位置显示,没有画面的地方为黑框 583 tSdlRect.y = 0;//当x y等于0,w h等于窗口的宽高时即为全屏显示,此时调整宽高大小,只需调整窗口大小即可 584 tSdlRect.w = iScreenWidth; 585 tSdlRect.h = iScreenHeight; 586 587 ptVideoControlTID = SDL_CreateThread(RefreshPlayThread, NULL, (LPVOID)this);//创建一个线程 588 589 while (1) 590 {//Event Loop 591 SDL_WaitEvent(&tSdlEvent);//Wait,等待其他线程过来的事件 592 if (tSdlEvent.type == PLAY_REFRESH_EVENT) //自定义刷新图像(播放)事件 593 { 594 /*------------FFmpeg----------------*/ 595 if (av_read_frame(ptFormatContext, ptPacket) >= 0) //从输入文件读取一帧压缩数据 596 { 597 if (ptPacket->stream_index == iVideoindex) 598 { 599 iRet = avcodec_decode_video2(ptCodecContext, ptFrame, &iGotPicture, ptPacket);//解码一帧压缩数据 600 if (iRet < 0) 601 { 602 OutputDebugString (L"Decode Error. "); 603 return -1; 604 } 605 if (iGotPicture) 606 { 607 //图像转换,sws_scale()函数需要用到的转换信息,即第一个参数,是由sws_getContext函数获得的 608 sws_scale(ptImgConvertInfo, (const uint8_t* const*)ptFrame->data, ptFrame->linesize, 0, ptCodecContext->height, ptFrameAfterScale->data, ptFrameAfterScale->linesize); 609 610 /*------------SDL----------------*/ 611 SDL_UpdateTexture(ptSdlTexture, NULL, ptFrameAfterScale->data[0], ptFrameAfterScale->linesize[0]);//设置(更新)纹理的数据 612 SDL_RenderClear(ptSdlRenderer);//先清除渲染器里的数据 613 //SDL_RenderCopy( ptSdlRenderer, ptSdlTexture, &tSdlRect, &tSdlRect ); //将纹理的数据拷贝给渲染器 614 SDL_RenderCopy(ptSdlRenderer, ptSdlTexture, NULL, NULL);//将纹理的数据拷贝给渲染器 615 SDL_RenderPresent(ptSdlRenderer);//显示 616 } 617 } 618 av_free_packet(ptPacket);//释放空间 619 } 620 else 621 { 622 m_iThreadExitFlag = 1;//Exit Thread 623 } 624 } 625 else if (tSdlEvent.type == SDL_QUIT) //也是SDL自带的事件,当点击窗口的×时触发//SDL_WINDOWENVENT sdl系统自带的事件,当拉伸窗口的时候会触发 626 { 627 m_iThreadExitFlag = 1; 628 } 629 else if (tSdlEvent.type == PLAY_BREAK_EVENT) //自定义退出播放事件 630 { 631 break; 632 } 633 634 } 635 636 /*------------像素数据处理----------------*/ 637 sws_freeContext(ptImgConvertInfo);//释放空间 638 639 /*------------SDL----------------*/ 640 SDL_Quit();//退出SDL系统 641 //SDL Hide Window When it finished 642 dlg->GetDlgItem(IDC_SCREEN)->ShowWindow(SW_SHOWNORMAL); 643 644 /*------------FFmpeg----------------*/ 645 av_frame_free(&ptFrameAfterScale);//释放空间 646 av_frame_free(&ptFrame);//释放空间 647 avcodec_close(ptCodecContext);//关闭解码器 648 avformat_close_input(&ptFormatContext);//关闭输入视频文件 649 650 return 0; 651 }
具体代码见github:
https://github.com/fengweiyu/ywfPlayer