首页 > 代码库 > 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 }
完结撒花
FFmpeg入门,简单播放器
声明:以上内容来自用户投稿及互联网公开渠道收集整理发布,本网站不拥有所有权,未作人工编辑处理,也不承担相关法律责任,若内容有误或涉及侵权可进行投诉: 投诉/举报 工作人员会在5个工作日内联系你,一经查实,本站将立刻删除涉嫌侵权内容。