首页 > 代码库 > FFmpeg入门,简单播放器

FFmpeg入门,简单播放器

一个偶然的机缘,好像要做直播相关的项目

为了筹备,前期做一些只是储备,于是开始学习ffmpeg

这是学习的第一课

做一个简单的播放器,播放视频画面帧

思路是,将视频文件解码,得到帧,然后使用定时器,1秒显示24帧

1.创建win32工程,添加菜单项 “打开”

   为了避免闪烁,MyRegisterClass中设置hbrBackground为null

2.在main函数中初始化ffmpeg库:av_register_all();

3.响应菜单打开

 1 void LoadVideoPlay(HWND hWnd) 2 { 3     if (gbLoadVideo) 4     { 5         return; 6     } 7  8     TCHAR szPath[1024] = { 0 }; 9     DWORD dwPath = 1024;10     OPENFILENAME ofn = { 0 };11     ofn.lStructSize = sizeof(ofn);12     ofn.hwndOwner = hWnd;13     ofn.hInstance = hInst;14     ofn.lpstrFile = szPath;15     ofn.nMaxFile = dwPath;16     ofn.lpstrFilter = _T("Video(*.mp4)\0*.MP4*;*.avi*\0");17     ofn.Flags = OFN_FILEMUSTEXIST | OFN_HIDEREADONLY;18     ofn.lpstrInitialDir = _T("F:\\");19 20     if (!GetOpenFileName(&ofn))21     {22         DWORD dwErr = CommDlgExtendedError();23         OutputDebugString(_T("GetOpenFileName\n"));24         return;25     }26 27     std::wstring strVideo = szPath;28     std::thread loadVideoThread([hWnd, strVideo]() {29         gbLoadVideo = TRUE;30         std::string sVideo = UnicodeToUTF_8(strVideo.c_str(), strVideo.size());31         OpenVideoByFFmpeg(hWnd, sVideo.c_str());32         gbLoadVideo = FALSE;33     });34 35     loadVideoThread.detach();36 }

使用c++11的线程来加载视频文件并进行解码工作。

4.在加载完视频之后,设置窗口为不可缩放

  创建缓存DC等显示环境

  设置播放帧画面的定时器

5.将解码的帧画面转化为 RGB 32位格式,并存储至队列中等待播放

6.播放帧画面

   在WM_PAINT消息中进行绘画

   

 1 void PlayFrame(HWND hWnd, UCHAR* buffer, UINT uWidth, UINT uHeight) 2 { 3     do  4     { 5         if (GetFramesSize() > (24 * 5)) // 缓冲5秒的帧数 6         { 7             if (WaitForSingleObject(ghExitEvent, 3000) == WAIT_OBJECT_0) 8             { 9                 return;10             }11         }12         else13         {14             HDC hDC = GetDC(hWnd);15             HBITMAP hFrame = CreateBitmapFromPixels(hDC, uWidth, uHeight, 32, buffer);16             if (hFrame)17             {18                 PushFrame(hFrame);19             }20             ReleaseDC(hWnd, hDC);21             break;22         }23 24     } while (true);25 }

因为解码拿到的是像素数据,需要将像素数据转化为Win32兼容位图

 1 HBITMAP CreateBitmapFromPixels(HDC hDC, UINT uWidth, UINT uHeight, UINT uBitsPerPixel, LPVOID pBits) 2 { 3     if (uBitsPerPixel <= 8) // NOT IMPLEMENTED YET 4         return NULL; 5  6     HBITMAP hBitmap = 0; 7     if (!uWidth || !uHeight || !uBitsPerPixel) 8         return hBitmap; 9     LONG lBmpSize = uWidth * uHeight * (uBitsPerPixel / 8);10     BITMAPINFO bmpInfo = { 0 };11     bmpInfo.bmiHeader.biBitCount = uBitsPerPixel;12     bmpInfo.bmiHeader.biHeight = -(static_cast<LONG>(uHeight)); // 因为拿到的帧图像是颠倒的13     bmpInfo.bmiHeader.biWidth = uWidth;14     bmpInfo.bmiHeader.biPlanes = 1;15     bmpInfo.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);16     // Pointer to access the pixels of bitmap17     UINT * pPixels = 0;18     hBitmap = CreateDIBSection(hDC, (BITMAPINFO *)&19         bmpInfo, DIB_RGB_COLORS, (void **)&20         pPixels, NULL, 0);21 22     if (!hBitmap)23         return hBitmap; // return if invalid bitmaps24 25     memcpy(pPixels, pBits, lBmpSize);26 27     return hBitmap;28 }

 

7.播放完毕,回复窗口设定,关闭定时器

代码流程一目了然,用来学习ffmpeg入门

 

最后贴上总的代码

技术分享
  1 // main.cpp : 定义应用程序的入口点。  2 //  3   4 #include "stdafx.h"  5 #include "testPlayVideo.h"  6   7 #include <windows.h>  8 #include <commdlg.h>  9 #include <deque> 10 #include <string> 11 #include <mutex> 12 #include <thread> 13  14 extern "C" { 15 #include "libavcodec/avcodec.h" 16 #include "libavformat/avformat.h" 17 #include "libavutil/pixfmt.h" 18 #include "libavutil/imgutils.h" 19 #include "libavdevice/avdevice.h" 20 #include "libswscale/swscale.h" 21 } 22  23 #pragma comment(lib, "Comdlg32.lib") 24  25 #define MAX_LOADSTRING  100 26 #define TIMER_FRAME     101 27 #define CHECK_TRUE(v) {if(!v) goto cleanup;} 28 #define CHECK_ZERO(v) {if(v<0) goto cleanup;} 29  30 // 全局变量:  31 HINSTANCE hInst;                                // 当前实例 32 WCHAR szTitle[MAX_LOADSTRING];                  // 标题栏文本 33 WCHAR szWindowClass[MAX_LOADSTRING];            // 主窗口类名 34  35 std::mutex gFrameLock;                          // 图像位图帧的访问锁 36 std::deque<HBITMAP> gFrames;                    // 所有解码的视频图像位图帧 37 HDC ghFrameDC;                                  // 视频帧图像兼容DC 38 HBITMAP ghFrameBmp;                             // 兼容DC的内存位图 39 HBRUSH ghFrameBrush;                            // 兼容DC的背景画刷 40 HANDLE ghExitEvent;                             // 程序退出通知事件 41 UINT uFrameTimer;                               // 定时器,刷新窗口显示图像位图帧 42 UINT uBorderWidth; 43 UINT uBorderHeight; 44 BOOL gbLoadVideo; 45 DWORD dwWndStyle; 46  47 // 此代码模块中包含的函数的前向声明:  48 ATOM                MyRegisterClass(HINSTANCE hInstance); 49 BOOL                InitInstance(HINSTANCE, int); 50 LRESULT CALLBACK    WndProc(HWND, UINT, WPARAM, LPARAM); 51 INT_PTR CALLBACK    About(HWND, UINT, WPARAM, LPARAM); 52 HBITMAP CreateBitmapFromPixels(HDC hDC, UINT uWidth, UINT uHeight, UINT uBitsPerPixel, LPVOID pBits); 53  54 HBITMAP PopFrame(void) 55 { 56     std::lock_guard<std::mutex> lg(gFrameLock); 57  58     if (gFrames.empty()) 59     { 60         return nullptr; 61     } 62     else 63     { 64         HBITMAP hFrame = gFrames.front(); 65         gFrames.pop_front(); 66         return hFrame; 67     } 68 } 69  70 void PushFrame(HBITMAP hFrame) 71 { 72     std::lock_guard<std::mutex> lg(gFrameLock); 73     gFrames.push_back(hFrame); 74 } 75  76 size_t GetFramesSize(void) 77 { 78     std::lock_guard<std::mutex> lg(gFrameLock); 79     return gFrames.size(); 80 } 81  82 void ReleasePaint(void) 83 { 84     if (ghFrameDC) DeleteDC(ghFrameDC); 85     if (ghFrameBmp) DeleteObject(ghFrameBmp); 86     if (ghFrameBrush) DeleteObject(ghFrameBrush); 87  88     ghFrameDC = nullptr; 89     ghFrameBmp = nullptr; 90     ghFrameBrush = nullptr; 91 } 92  93 void CreatePaint(HWND hWnd) 94 { 95     RECT rc; 96     GetClientRect(hWnd, &rc); 97     HDC hDC = GetDC(hWnd); 98     ghFrameDC = CreateCompatibleDC(hDC); 99     ghFrameBmp = CreateCompatibleBitmap(hDC, rc.right - rc.left, rc.bottom - rc.top);100     ghFrameBrush = CreateSolidBrush(RGB(5, 5, 5));101     SelectObject(ghFrameDC, ghFrameBmp);102     ReleaseDC(hWnd, hDC);103 }104 105 void ReleaseFrames(void)106 {107     std::lock_guard<std::mutex> lg(gFrameLock);108     for (auto& hFrame : gFrames)109     {110         DeleteObject(hFrame);111     }112     gFrames.clear();113     114     ReleasePaint();115 }116 117 void PlayFrame(HWND hWnd, UCHAR* buffer, UINT uWidth, UINT uHeight)118 {119     do 120     {121         if (GetFramesSize() > (24 * 5)) // 缓冲5秒的帧数122         {123             if (WaitForSingleObject(ghExitEvent, 3000) == WAIT_OBJECT_0)124             {125                 return;126             }127         }128         else129         {130             HDC hDC = GetDC(hWnd);131             HBITMAP hFrame = CreateBitmapFromPixels(hDC, uWidth, uHeight, 32, buffer);132             if (hFrame)133             {134                 PushFrame(hFrame);135             }136             ReleaseDC(hWnd, hDC);137             break;138         }139 140     } while (true);141 }142 143 std::string UnicodeToUTF_8(const wchar_t *pIn, size_t nSize)144 {145     if (pIn == NULL || nSize == 0)146     {147         return "";148     }149 150     std::string s;151     int n = WideCharToMultiByte(CP_UTF8, NULL, pIn, nSize, NULL, 0, NULL, NULL);152     if (n > 0)153     {154         s.resize(n);155         WideCharToMultiByte(CP_UTF8, NULL, pIn, nSize, &s[0], n, NULL, NULL);156     }157 158     return s;159 }160 161 int APIENTRY wWinMain(_In_ HINSTANCE hInstance,162                      _In_opt_ HINSTANCE hPrevInstance,163                      _In_ LPWSTR    lpCmdLine,164                      _In_ int       nCmdShow)165 {166     UNREFERENCED_PARAMETER(hPrevInstance);167     UNREFERENCED_PARAMETER(lpCmdLine);168 169     // TODO: 在此放置代码。170     ghExitEvent = CreateEvent(nullptr, TRUE, FALSE, nullptr);171     av_register_all();172 173     // 初始化全局字符串174     LoadStringW(hInstance, IDS_APP_TITLE, szTitle, MAX_LOADSTRING);175     LoadStringW(hInstance, IDC_TESTPLAYVIDEO, szWindowClass, MAX_LOADSTRING);176     MyRegisterClass(hInstance);177 178     // 执行应用程序初始化: 179     if (!InitInstance (hInstance, nCmdShow))180     {181         return FALSE;182     }183 184     HACCEL hAccelTable = LoadAccelerators(hInstance, MAKEINTRESOURCE(IDC_TESTPLAYVIDEO));185 186     MSG msg;187 188     // 主消息循环: 189     while (GetMessage(&msg, nullptr, 0, 0))190     {191         if (!TranslateAccelerator(msg.hwnd, hAccelTable, &msg))192         {193             TranslateMessage(&msg);194             DispatchMessage(&msg);195         }196     }197 198     return (int) msg.wParam;199 }200 201 202 203 //204 //  函数: MyRegisterClass()205 //206 //  目的: 注册窗口类。207 //208 ATOM MyRegisterClass(HINSTANCE hInstance)209 {210     WNDCLASSEXW wcex;211 212     wcex.cbSize = sizeof(WNDCLASSEX);213 214     wcex.style          = CS_HREDRAW | CS_VREDRAW;215     wcex.lpfnWndProc    = WndProc;216     wcex.cbClsExtra     = 0;217     wcex.cbWndExtra     = 0;218     wcex.hInstance      = hInstance;219     wcex.hIcon          = LoadIcon(hInstance, MAKEINTRESOURCE(IDI_TESTPLAYVIDEO));220     wcex.hCursor        = LoadCursor(nullptr, IDC_ARROW);221     wcex.hbrBackground  = /*(HBRUSH)(COLOR_WINDOW+1)*/nullptr;222     wcex.lpszMenuName   = MAKEINTRESOURCEW(IDC_TESTPLAYVIDEO);223     wcex.lpszClassName  = szWindowClass;224     wcex.hIconSm        = LoadIcon(wcex.hInstance, MAKEINTRESOURCE(IDI_SMALL));225 226     return RegisterClassExW(&wcex);227 }228 229 //230 //   函数: InitInstance(HINSTANCE, int)231 //232 //   目的: 保存实例句柄并创建主窗口233 //234 //   注释: 235 //236 //        在此函数中,我们在全局变量中保存实例句柄并237 //        创建和显示主程序窗口。238 //239 BOOL InitInstance(HINSTANCE hInstance, int nCmdShow)240 {241    hInst = hInstance; // 将实例句柄存储在全局变量中242 243    HWND hWnd = CreateWindowW(szWindowClass, szTitle, WS_OVERLAPPEDWINDOW,244       CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, nullptr, nullptr, hInstance, nullptr);245 246    if (!hWnd)247    {248       return FALSE;249    }250 251    ShowWindow(hWnd, nCmdShow);252    UpdateWindow(hWnd);253 254    return TRUE;255 }256 257 HBITMAP CreateBitmapFromPixels(HDC hDC, UINT uWidth, UINT uHeight, UINT uBitsPerPixel, LPVOID pBits)258 {259     if (uBitsPerPixel <= 8) // NOT IMPLEMENTED YET260         return NULL;261 262     HBITMAP hBitmap = 0;263     if (!uWidth || !uHeight || !uBitsPerPixel)264         return hBitmap;265     LONG lBmpSize = uWidth * uHeight * (uBitsPerPixel / 8);266     BITMAPINFO bmpInfo = { 0 };267     bmpInfo.bmiHeader.biBitCount = uBitsPerPixel;268     bmpInfo.bmiHeader.biHeight = -(static_cast<LONG>(uHeight)); // 因为拿到的帧图像是颠倒的269     bmpInfo.bmiHeader.biWidth = uWidth;270     bmpInfo.bmiHeader.biPlanes = 1;271     bmpInfo.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);272     // Pointer to access the pixels of bitmap273     UINT * pPixels = 0;274     hBitmap = CreateDIBSection(hDC, (BITMAPINFO *)&275         bmpInfo, DIB_RGB_COLORS, (void **)&276         pPixels, NULL, 0);277 278     if (!hBitmap)279         return hBitmap; // return if invalid bitmaps280 281     memcpy(pPixels, pBits, lBmpSize);282 283     return hBitmap;284 }285 286 void PaintFrame(HWND hWnd, HDC hDC, RECT rc)287 {288     FillRect(ghFrameDC, &rc, ghFrameBrush);289     HBITMAP hFrame = PopFrame();290     if (hFrame)291     {292         BITMAP bmp;293         GetObject(hFrame, sizeof(bmp), &bmp);294         HDC hFrameDC = CreateCompatibleDC(hDC);295         HBITMAP hOld = (HBITMAP)SelectObject(hFrameDC, hFrame);296         BitBlt(ghFrameDC, 4, 2, bmp.bmWidth, bmp.bmHeight, hFrameDC, 0, 0, SRCCOPY);297         SelectObject(hFrameDC, hOld);298         DeleteObject(hFrame);299         DeleteDC(hFrameDC);300     }301 302     BitBlt(hDC, 0, 0, rc.right - rc.left, rc.bottom - rc.top, ghFrameDC, 0, 0, SRCCOPY);303 }304 305 void OpenVideoByFFmpeg(HWND hWnd, const char* szVideo)306 {307     AVFormatContext* pFmtCtx = nullptr;308     AVCodecContext* pCodecCtx = nullptr;309     AVCodec* pCodec = nullptr;310     AVFrame* pFrameSrc =http://www.mamicode.com/ nullptr;311     AVFrame* pFrameRGB = nullptr;312     AVPacket* pPkt = nullptr;313     UCHAR* out_buffer = nullptr;314     struct SwsContext * pImgCtx = nullptr;315     int ret = 0;316     int videoStream = -1;317     int numBytes = 0;318 319     pFmtCtx = avformat_alloc_context();320     CHECK_TRUE(pFmtCtx);321     ret = avformat_open_input(&pFmtCtx, szVideo, nullptr, nullptr);322     CHECK_ZERO(ret);323     ret = avformat_find_stream_info(pFmtCtx, nullptr);324     CHECK_ZERO(ret);325 326     for (UINT i = 0; i < pFmtCtx->nb_streams; ++i)327     {328         if (pFmtCtx->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO)329         {330             videoStream = i;331             break;332         }333     }334     CHECK_ZERO(videoStream);335 336     pCodecCtx = avcodec_alloc_context3(nullptr);337     CHECK_TRUE(pCodecCtx);338     ret = avcodec_parameters_to_context(pCodecCtx, pFmtCtx->streams[videoStream]->codecpar);339     CHECK_ZERO(ret);340     pCodec = avcodec_find_decoder(pCodecCtx->codec_id);341     CHECK_TRUE(pCodec);342     ret = avcodec_open2(pCodecCtx, pCodec, nullptr);343     CHECK_ZERO(ret);344 345     pFrameSrc =http://www.mamicode.com/ av_frame_alloc();346     pFrameRGB = av_frame_alloc();347     CHECK_TRUE(pFrameSrc);348     CHECK_TRUE(pFrameRGB);349 350     pImgCtx = sws_getContext(pCodecCtx->width, pCodecCtx->height, pCodecCtx->pix_fmt, 351         pCodecCtx->width, pCodecCtx->height, AV_PIX_FMT_RGB32, SWS_BICUBIC, nullptr, nullptr, nullptr);352     CHECK_TRUE(pImgCtx);353 354     numBytes = av_image_get_buffer_size(AV_PIX_FMT_RGB32, pCodecCtx->width, pCodecCtx->height, 1);355     out_buffer = (UCHAR*)av_malloc(numBytes);356     CHECK_TRUE(out_buffer);357 358     ret = av_image_fill_arrays(pFrameRGB->data, pFrameRGB->linesize, out_buffer, 359         AV_PIX_FMT_RGB32, pCodecCtx->width, pCodecCtx->height, 1);360     CHECK_ZERO(ret);361 362     pPkt = new AVPacket;363     ret = av_new_packet(pPkt, pCodecCtx->width * pCodecCtx->height);364     CHECK_ZERO(ret);365 366     SetWindowPos(hWnd, nullptr, 0, 0, pCodecCtx->width + uBorderWidth, pCodecCtx->height + uBorderHeight, SWP_NOMOVE);367     ReleasePaint();368     CreatePaint(hWnd);369     dwWndStyle = GetWindowLong(hWnd, GWL_STYLE);370     ::SetWindowLong(hWnd, GWL_STYLE, dwWndStyle&~WS_SIZEBOX);371     if (!uFrameTimer) uFrameTimer = SetTimer(hWnd, TIMER_FRAME, 40, nullptr);372 373     while (true)374     {375         if (av_read_frame(pFmtCtx, pPkt) < 0)376         {377             break;378         }379 380         if (pPkt->stream_index == videoStream)381         {382             ret = avcodec_send_packet(pCodecCtx, pPkt);383             if (ret < 0) continue;384             ret = avcodec_receive_frame(pCodecCtx, pFrameSrc);385             if (ret < 0) continue;386 387             ret = sws_scale(pImgCtx, pFrameSrc->data, pFrameSrc->linesize,388                 0, pCodecCtx->height, pFrameRGB->data, pFrameRGB->linesize);389             if (ret <= 0) continue;390             PlayFrame(hWnd, out_buffer, pCodecCtx->width, pCodecCtx->height);391         }392 393         av_packet_unref(pPkt);394     }395 396     if (uFrameTimer)397     {398         KillTimer(hWnd, uFrameTimer);399         uFrameTimer = 0;400     }401 402     if (dwWndStyle) ::SetWindowLong(hWnd, GWL_STYLE, dwWndStyle);403 404 cleanup:405     if (pFmtCtx) avformat_free_context(pFmtCtx);406     if (pCodecCtx) avcodec_free_context(&pCodecCtx);407     if (pFrameSrc) av_frame_free(&pFrameSrc);408     if (pFrameRGB) av_frame_free(&pFrameRGB);409     if (pImgCtx) sws_freeContext(pImgCtx);410     if (out_buffer) av_free(out_buffer);411     if (pPkt)412     {413         av_packet_unref(pPkt);414         delete pPkt;415     }416 }417 418 void LoadVideoPlay(HWND hWnd)419 {420     if (gbLoadVideo)421     {422         return;423     }424 425     TCHAR szPath[1024] = { 0 };426     DWORD dwPath = 1024;427     OPENFILENAME ofn = { 0 };428     ofn.lStructSize = sizeof(ofn);429     ofn.hwndOwner = hWnd;430     ofn.hInstance = hInst;431     ofn.lpstrFile = szPath;432     ofn.nMaxFile = dwPath;433     ofn.lpstrFilter = _T("Video(*.mp4)\0*.MP4*;*.avi*\0");434     ofn.Flags = OFN_FILEMUSTEXIST | OFN_HIDEREADONLY;435     ofn.lpstrInitialDir = _T("F:\\");436 437     if (!GetOpenFileName(&ofn))438     {439         DWORD dwErr = CommDlgExtendedError();440         OutputDebugString(_T("GetOpenFileName\n"));441         return;442     }443 444     std::wstring strVideo = szPath;445     std::thread loadVideoThread([hWnd, strVideo]() {446         gbLoadVideo = TRUE;447         std::string sVideo = UnicodeToUTF_8(strVideo.c_str(), strVideo.size());448         OpenVideoByFFmpeg(hWnd, sVideo.c_str());449         gbLoadVideo = FALSE;450     });451 452     loadVideoThread.detach();453 }454 455 //456 //  函数: WndProc(HWND, UINT, WPARAM, LPARAM)457 //458 //  目的:    处理主窗口的消息。459 //460 //  WM_COMMAND  - 处理应用程序菜单461 //  WM_PAINT    - 绘制主窗口462 //  WM_DESTROY  - 发送退出消息并返回463 //464 //465 LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)466 {467     switch (message)468     {469     case WM_CREATE:470     {471         CreatePaint(hWnd);472 473         RECT rc;474         GetClientRect(hWnd, &rc);475         RECT rcWnd;476         GetWindowRect(hWnd, &rcWnd);477         uBorderWidth = (rcWnd.right - rcWnd.left) - (rc.right - rc.left) + 2;478         uBorderHeight = (rcWnd.bottom - rcWnd.top) - (rc.bottom - rc.top) + 4;479     }480     break;481     case WM_TIMER:482     {483         if (uFrameTimer && (uFrameTimer == wParam))484         {485             if (IsIconic(hWnd)) // 如果最小化了,则直接移除图像帧486             {487                 HBITMAP hFrame = PopFrame();488                 if (hFrame)489                 {490                     DeleteObject(hFrame);491                 }492             }493             else494             {495                 InvalidateRect(hWnd, nullptr, FALSE);496             }497         }498     }499     break;500     case WM_COMMAND:501         {502             int wmId = LOWORD(wParam);503             // 分析菜单选择: 504             switch (wmId)505             {506             case IDM_OPEN:507                 LoadVideoPlay(hWnd);508                 break;509             case IDM_ABOUT:510                 DialogBox(hInst, MAKEINTRESOURCE(IDD_ABOUTBOX), hWnd, About);511                 break;512             case IDM_EXIT:513                 DestroyWindow(hWnd);514                 break;515             default:516                 return DefWindowProc(hWnd, message, wParam, lParam);517             }518         }519         break;520     case WM_PAINT:521         {522             PAINTSTRUCT ps;523             RECT rc;524             GetClientRect(hWnd, &rc);525             HDC hdc = BeginPaint(hWnd, &ps);526             PaintFrame(hWnd, hdc, rc);527             EndPaint(hWnd, &ps);528         }529         break;530     case WM_DESTROY:531         SetEvent(ghExitEvent);532         ReleaseFrames();533         PostQuitMessage(0);534         break;535     default:536         return DefWindowProc(hWnd, message, wParam, lParam);537     }538     return 0;539 }540 541 // “关于”框的消息处理程序。542 INT_PTR CALLBACK About(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam)543 {544     UNREFERENCED_PARAMETER(lParam);545     switch (message)546     {547     case WM_INITDIALOG:548         return (INT_PTR)TRUE;549 550     case WM_COMMAND:551         if (LOWORD(wParam) == IDOK || LOWORD(wParam) == IDCANCEL)552         {553             EndDialog(hDlg, LOWORD(wParam));554             return (INT_PTR)TRUE;555         }556         break;557     }558     return (INT_PTR)FALSE;559 }
main.cpp

完结撒花

FFmpeg入门,简单播放器