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

为了筹备,前期做一些只是储备,于是开始学习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[] = { };
  9. DWORD dwPath = ;
  10. OPENFILENAME ofn = { };
  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() > ( * )) // 缓冲5秒的帧数
  6. {
  7. if (WaitForSingleObject(ghExitEvent, ) == WAIT_OBJECT_0)
  8. {
  9. return;
  10. }
  11. }
  12. else
  13. {
  14. HDC hDC = GetDC(hWnd);
  15. HBITMAP hFrame = CreateBitmapFromPixels(hDC, uWidth, uHeight, , 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 <= ) // NOT IMPLEMENTED YET
  4. return NULL;
  5.  
  6. HBITMAP hBitmap = ;
  7. if (!uWidth || !uHeight || !uBitsPerPixel)
  8. return hBitmap;
  9. LONG lBmpSize = uWidth * uHeight * (uBitsPerPixel / );
  10. BITMAPINFO bmpInfo = { };
  11. bmpInfo.bmiHeader.biBitCount = uBitsPerPixel;
  12. bmpInfo.bmiHeader.biHeight = -(static_cast<LONG>(uHeight)); // 因为拿到的帧图像是颠倒的
  13. bmpInfo.bmiHeader.biWidth = uWidth;
  14. bmpInfo.bmiHeader.biPlanes = ;
  15. bmpInfo.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
  16. // Pointer to access the pixels of bitmap
  17. UINT * pPixels = ;
  18. hBitmap = CreateDIBSection(hDC, (BITMAPINFO *)&
  19. bmpInfo, DIB_RGB_COLORS, (void **)&
  20. pPixels, NULL, );
  21.  
  22. if (!hBitmap)
  23. return hBitmap; // return if invalid bitmaps
  24.  
  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(, , ));
  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() > ( * )) // 缓冲5秒的帧数
  122. {
  123. if (WaitForSingleObject(ghExitEvent, ) == WAIT_OBJECT_0)
  124. {
  125. return;
  126. }
  127. }
  128. else
  129. {
  130. HDC hDC = GetDC(hWnd);
  131. HBITMAP hFrame = CreateBitmapFromPixels(hDC, uWidth, uHeight, , 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 == )
  146. {
  147. return "";
  148. }
  149.  
  150. std::string s;
  151. int n = WideCharToMultiByte(CP_UTF8, NULL, pIn, nSize, NULL, , NULL, NULL);
  152. if (n > )
  153. {
  154. s.resize(n);
  155. WideCharToMultiByte(CP_UTF8, NULL, pIn, nSize, &s[], 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, , ))
  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. // 函数: MyRegisterClass()
  203. //
  204. // 目的: 注册窗口类。
  205. //
  206. ATOM MyRegisterClass(HINSTANCE hInstance)
  207. {
  208. WNDCLASSEXW wcex;
  209.  
  210. wcex.cbSize = sizeof(WNDCLASSEX);
  211.  
  212. wcex.style = CS_HREDRAW | CS_VREDRAW;
  213. wcex.lpfnWndProc = WndProc;
  214. wcex.cbClsExtra = ;
  215. wcex.cbWndExtra = ;
  216. wcex.hInstance = hInstance;
  217. wcex.hIcon = LoadIcon(hInstance, MAKEINTRESOURCE(IDI_TESTPLAYVIDEO));
  218. wcex.hCursor = LoadCursor(nullptr, IDC_ARROW);
  219. wcex.hbrBackground = /*(HBRUSH)(COLOR_WINDOW+1)*/nullptr;
  220. wcex.lpszMenuName = MAKEINTRESOURCEW(IDC_TESTPLAYVIDEO);
  221. wcex.lpszClassName = szWindowClass;
  222. wcex.hIconSm = LoadIcon(wcex.hInstance, MAKEINTRESOURCE(IDI_SMALL));
  223.  
  224. return RegisterClassExW(&wcex);
  225. }
  226.  
  227. //
  228. // 函数: InitInstance(HINSTANCE, int)
  229. //
  230. // 目的: 保存实例句柄并创建主窗口
  231. //
  232. // 注释:
  233. //
  234. // 在此函数中,我们在全局变量中保存实例句柄并
  235. // 创建和显示主程序窗口。
  236. //
  237. BOOL InitInstance(HINSTANCE hInstance, int nCmdShow)
  238. {
  239. hInst = hInstance; // 将实例句柄存储在全局变量中
  240.  
  241. HWND hWnd = CreateWindowW(szWindowClass, szTitle, WS_OVERLAPPEDWINDOW,
  242. CW_USEDEFAULT, , CW_USEDEFAULT, , nullptr, nullptr, hInstance, nullptr);
  243.  
  244. if (!hWnd)
  245. {
  246. return FALSE;
  247. }
  248.  
  249. ShowWindow(hWnd, nCmdShow);
  250. UpdateWindow(hWnd);
  251.  
  252. return TRUE;
  253. }
  254.  
  255. HBITMAP CreateBitmapFromPixels(HDC hDC, UINT uWidth, UINT uHeight, UINT uBitsPerPixel, LPVOID pBits)
  256. {
  257. if (uBitsPerPixel <= ) // NOT IMPLEMENTED YET
  258. return NULL;
  259.  
  260. HBITMAP hBitmap = ;
  261. if (!uWidth || !uHeight || !uBitsPerPixel)
  262. return hBitmap;
  263. LONG lBmpSize = uWidth * uHeight * (uBitsPerPixel / );
  264. BITMAPINFO bmpInfo = { };
  265. bmpInfo.bmiHeader.biBitCount = uBitsPerPixel;
  266. bmpInfo.bmiHeader.biHeight = -(static_cast<LONG>(uHeight)); // 因为拿到的帧图像是颠倒的
  267. bmpInfo.bmiHeader.biWidth = uWidth;
  268. bmpInfo.bmiHeader.biPlanes = ;
  269. bmpInfo.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
  270. // Pointer to access the pixels of bitmap
  271. UINT * pPixels = ;
  272. hBitmap = CreateDIBSection(hDC, (BITMAPINFO *)&
  273. bmpInfo, DIB_RGB_COLORS, (void **)&
  274. pPixels, NULL, );
  275.  
  276. if (!hBitmap)
  277. return hBitmap; // return if invalid bitmaps
  278.  
  279. memcpy(pPixels, pBits, lBmpSize);
  280.  
  281. return hBitmap;
  282. }
  283.  
  284. void PaintFrame(HWND hWnd, HDC hDC, RECT rc)
  285. {
  286. FillRect(ghFrameDC, &rc, ghFrameBrush);
  287. HBITMAP hFrame = PopFrame();
  288. if (hFrame)
  289. {
  290. BITMAP bmp;
  291. GetObject(hFrame, sizeof(bmp), &bmp);
  292. HDC hFrameDC = CreateCompatibleDC(hDC);
  293. HBITMAP hOld = (HBITMAP)SelectObject(hFrameDC, hFrame);
  294. BitBlt(ghFrameDC, , , bmp.bmWidth, bmp.bmHeight, hFrameDC, , , SRCCOPY);
  295. SelectObject(hFrameDC, hOld);
  296. DeleteObject(hFrame);
  297. DeleteDC(hFrameDC);
  298. }
  299.  
  300. BitBlt(hDC, , , rc.right - rc.left, rc.bottom - rc.top, ghFrameDC, , , SRCCOPY);
  301. }
  302.  
  303. void OpenVideoByFFmpeg(HWND hWnd, const char* szVideo)
  304. {
  305. AVFormatContext* pFmtCtx = nullptr;
  306. AVCodecContext* pCodecCtx = nullptr;
  307. AVCodec* pCodec = nullptr;
  308. AVFrame* pFrameSrc = nullptr;
  309. AVFrame* pFrameRGB = nullptr;
  310. AVPacket* pPkt = nullptr;
  311. UCHAR* out_buffer = nullptr;
  312. struct SwsContext * pImgCtx = nullptr;
  313. int ret = ;
  314. int videoStream = -;
  315. int numBytes = ;
  316.  
  317. pFmtCtx = avformat_alloc_context();
  318. CHECK_TRUE(pFmtCtx);
  319. ret = avformat_open_input(&pFmtCtx, szVideo, nullptr, nullptr);
  320. CHECK_ZERO(ret);
  321. ret = avformat_find_stream_info(pFmtCtx, nullptr);
  322. CHECK_ZERO(ret);
  323.  
  324. for (UINT i = ; i < pFmtCtx->nb_streams; ++i)
  325. {
  326. if (pFmtCtx->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO)
  327. {
  328. videoStream = i;
  329. break;
  330. }
  331. }
  332. CHECK_ZERO(videoStream);
  333.  
  334. pCodecCtx = avcodec_alloc_context3(nullptr);
  335. CHECK_TRUE(pCodecCtx);
  336. ret = avcodec_parameters_to_context(pCodecCtx, pFmtCtx->streams[videoStream]->codecpar);
  337. CHECK_ZERO(ret);
  338. pCodec = avcodec_find_decoder(pCodecCtx->codec_id);
  339. CHECK_TRUE(pCodec);
  340. ret = avcodec_open2(pCodecCtx, pCodec, nullptr);
  341. CHECK_ZERO(ret);
  342.  
  343. pFrameSrc = av_frame_alloc();
  344. pFrameRGB = av_frame_alloc();
  345. CHECK_TRUE(pFrameSrc);
  346. CHECK_TRUE(pFrameRGB);
  347.  
  348. pImgCtx = sws_getContext(pCodecCtx->width, pCodecCtx->height, pCodecCtx->pix_fmt,
  349. pCodecCtx->width, pCodecCtx->height, AV_PIX_FMT_RGB32, SWS_BICUBIC, nullptr, nullptr, nullptr);
  350. CHECK_TRUE(pImgCtx);
  351.  
  352. numBytes = av_image_get_buffer_size(AV_PIX_FMT_RGB32, pCodecCtx->width, pCodecCtx->height, );
  353. out_buffer = (UCHAR*)av_malloc(numBytes);
  354. CHECK_TRUE(out_buffer);
  355.  
  356. ret = av_image_fill_arrays(pFrameRGB->data, pFrameRGB->linesize, out_buffer,
  357. AV_PIX_FMT_RGB32, pCodecCtx->width, pCodecCtx->height, );
  358. CHECK_ZERO(ret);
  359.  
  360. pPkt = new AVPacket;
  361. ret = av_new_packet(pPkt, pCodecCtx->width * pCodecCtx->height);
  362. CHECK_ZERO(ret);
  363.  
  364. SetWindowPos(hWnd, nullptr, , , pCodecCtx->width + uBorderWidth, pCodecCtx->height + uBorderHeight, SWP_NOMOVE);
  365. ReleasePaint();
  366. CreatePaint(hWnd);
  367. dwWndStyle = GetWindowLong(hWnd, GWL_STYLE);
  368. ::SetWindowLong(hWnd, GWL_STYLE, dwWndStyle&~WS_SIZEBOX);
  369. if (!uFrameTimer) uFrameTimer = SetTimer(hWnd, TIMER_FRAME, , nullptr);
  370.  
  371. while (true)
  372. {
  373. if (av_read_frame(pFmtCtx, pPkt) < )
  374. {
  375. break;
  376. }
  377.  
  378. if (pPkt->stream_index == videoStream)
  379. {
  380. ret = avcodec_send_packet(pCodecCtx, pPkt);
  381. if (ret < ) continue;
  382. ret = avcodec_receive_frame(pCodecCtx, pFrameSrc);
  383. if (ret < ) continue;
  384.  
  385. ret = sws_scale(pImgCtx, pFrameSrc->data, pFrameSrc->linesize,
  386. , pCodecCtx->height, pFrameRGB->data, pFrameRGB->linesize);
  387. if (ret <= ) continue;
  388. PlayFrame(hWnd, out_buffer, pCodecCtx->width, pCodecCtx->height);
  389. }
  390.  
  391. av_packet_unref(pPkt);
  392. }
  393.  
  394. if (uFrameTimer)
  395. {
  396. KillTimer(hWnd, uFrameTimer);
  397. uFrameTimer = ;
  398. }
  399.  
  400. if (dwWndStyle) ::SetWindowLong(hWnd, GWL_STYLE, dwWndStyle);
  401.  
  402. cleanup:
  403. if (pFmtCtx) avformat_free_context(pFmtCtx);
  404. if (pCodecCtx) avcodec_free_context(&pCodecCtx);
  405. if (pFrameSrc) av_frame_free(&pFrameSrc);
  406. if (pFrameRGB) av_frame_free(&pFrameRGB);
  407. if (pImgCtx) sws_freeContext(pImgCtx);
  408. if (out_buffer) av_free(out_buffer);
  409. if (pPkt)
  410. {
  411. av_packet_unref(pPkt);
  412. delete pPkt;
  413. }
  414. }
  415.  
  416. void LoadVideoPlay(HWND hWnd)
  417. {
  418. if (gbLoadVideo)
  419. {
  420. return;
  421. }
  422.  
  423. TCHAR szPath[] = { };
  424. DWORD dwPath = ;
  425. OPENFILENAME ofn = { };
  426. ofn.lStructSize = sizeof(ofn);
  427. ofn.hwndOwner = hWnd;
  428. ofn.hInstance = hInst;
  429. ofn.lpstrFile = szPath;
  430. ofn.nMaxFile = dwPath;
  431. ofn.lpstrFilter = _T("Video(*.mp4)\0*.MP4*;*.avi*\0");
  432. ofn.Flags = OFN_FILEMUSTEXIST | OFN_HIDEREADONLY;
  433. ofn.lpstrInitialDir = _T("F:\\");
  434.  
  435. if (!GetOpenFileName(&ofn))
  436. {
  437. DWORD dwErr = CommDlgExtendedError();
  438. OutputDebugString(_T("GetOpenFileName\n"));
  439. return;
  440. }
  441.  
  442. std::wstring strVideo = szPath;
  443. std::thread loadVideoThread([hWnd, strVideo]() {
  444. gbLoadVideo = TRUE;
  445. std::string sVideo = UnicodeToUTF_8(strVideo.c_str(), strVideo.size());
  446. OpenVideoByFFmpeg(hWnd, sVideo.c_str());
  447. gbLoadVideo = FALSE;
  448. });
  449.  
  450. loadVideoThread.detach();
  451. }
  452.  
  453. //
  454. // 函数: WndProc(HWND, UINT, WPARAM, LPARAM)
  455. //
  456. // 目的: 处理主窗口的消息。
  457. //
  458. // WM_COMMAND - 处理应用程序菜单
  459. // WM_PAINT - 绘制主窗口
  460. // WM_DESTROY - 发送退出消息并返回
  461. //
  462. //
  463. LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
  464. {
  465. switch (message)
  466. {
  467. case WM_CREATE:
  468. {
  469. CreatePaint(hWnd);
  470.  
  471. RECT rc;
  472. GetClientRect(hWnd, &rc);
  473. RECT rcWnd;
  474. GetWindowRect(hWnd, &rcWnd);
  475. uBorderWidth = (rcWnd.right - rcWnd.left) - (rc.right - rc.left) + ;
  476. uBorderHeight = (rcWnd.bottom - rcWnd.top) - (rc.bottom - rc.top) + ;
  477. }
  478. break;
  479. case WM_TIMER:
  480. {
  481. if (uFrameTimer && (uFrameTimer == wParam))
  482. {
  483. if (IsIconic(hWnd)) // 如果最小化了,则直接移除图像帧
  484. {
  485. HBITMAP hFrame = PopFrame();
  486. if (hFrame)
  487. {
  488. DeleteObject(hFrame);
  489. }
  490. }
  491. else
  492. {
  493. InvalidateRect(hWnd, nullptr, FALSE);
  494. }
  495. }
  496. }
  497. break;
  498. case WM_COMMAND:
  499. {
  500. int wmId = LOWORD(wParam);
  501. // 分析菜单选择:
  502. switch (wmId)
  503. {
  504. case IDM_OPEN:
  505. LoadVideoPlay(hWnd);
  506. break;
  507. case IDM_ABOUT:
  508. DialogBox(hInst, MAKEINTRESOURCE(IDD_ABOUTBOX), hWnd, About);
  509. break;
  510. case IDM_EXIT:
  511. DestroyWindow(hWnd);
  512. break;
  513. default:
  514. return DefWindowProc(hWnd, message, wParam, lParam);
  515. }
  516. }
  517. break;
  518. case WM_PAINT:
  519. {
  520. PAINTSTRUCT ps;
  521. RECT rc;
  522. GetClientRect(hWnd, &rc);
  523. HDC hdc = BeginPaint(hWnd, &ps);
  524. PaintFrame(hWnd, hdc, rc);
  525. EndPaint(hWnd, &ps);
  526. }
  527. break;
  528. case WM_DESTROY:
  529. SetEvent(ghExitEvent);
  530. ReleaseFrames();
  531. PostQuitMessage();
  532. break;
  533. default:
  534. return DefWindowProc(hWnd, message, wParam, lParam);
  535. }
  536. return ;
  537. }
  538.  
  539. // “关于”框的消息处理程序。
  540. INT_PTR CALLBACK About(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam)
  541. {
  542. UNREFERENCED_PARAMETER(lParam);
  543. switch (message)
  544. {
  545. case WM_INITDIALOG:
  546. return (INT_PTR)TRUE;
  547.  
  548. case WM_COMMAND:
  549. if (LOWORD(wParam) == IDOK || LOWORD(wParam) == IDCANCEL)
  550. {
  551. EndDialog(hDlg, LOWORD(wParam));
  552. return (INT_PTR)TRUE;
  553. }
  554. break;
  555. }
  556. return (INT_PTR)FALSE;
  557. }

main.cpp

完结撒花

FFmpeg入门,简单播放器的更多相关文章

  1. 视频播放器控制原理:ffmpeg之ffplay播放器源代码分析

    版权声明:本文由张坤原创文章,转载请注明出处: 文章原文链接:https://www.qcloud.com/community/article/535574001486630869 来源:腾云阁 ht ...

  2. 基于ffmpeg的C++播放器1

    基于ffmpeg的C++播放器 (1) 2011年12月份的时候发了这篇博客 http://blog.csdn.net/qq316293804/article/details/7107049 ,博文最 ...

  3. 基于FFMPEG的跨平台播放器实现(二)

    基于FFMPEG的跨平台播放器实现(二) 上一节讲到了在Android平台下采用FFmpeg+surface组合打造播放器的方法,这一节讲一下Windows平台FFmpeg + D3D.Linux平台 ...

  4. 基于FFMPEG的跨平台播放器实现

    基于FFMPEG的跨平台播放器实现 一.背景介绍 FFmpeg是一款超级强大的开源多媒体编解码框架,提供了录制.转换以及流化音视频的完整解决方案,包含了libavcodec.libavformat等多 ...

  5. 基于Live555,ffmpeg的RTSP播放器直播与点播

    基于Live555,ffmpeg的RTSP播放器直播与点播 多路RTSP高清视频播放器下载地址:http://download.csdn.net/detail/u011352914/6604437多路 ...

  6. 仿迅雷播放器教程 -- 基于ffmpeg的C++播放器 (1)

    2011年12月份的时候发了这篇博客 http://blog.csdn.net/qq316293804/article/details/7107049 ,博文最后说会开源一个播放器,没想到快两年了,才 ...

  7. ffmpeg学习(三)——ffmpeg+SDL2 实现简单播放器

    本篇实现基于ffmpeg动态库用测试程序播放本地文件和RTSP视频流. 参考文章:http://blog.csdn.net/leixiaohua1020/article/details/8652605 ...

  8. 基于libvlc和wxWidgets的简单播放器代码阅读

    源代码来自 http://git.videolan.org/?p=vlc.git;a=blob_plain;f=doc/libvlc/wx_player.cpp // g++ wx_player.cp ...

  9. 简单播放器(增加sdl事件控制)

    #include <libavcodec/avcodec.h> #include <libavformat/avformat.h> #include <libswscal ...

随机推荐

  1. AndroidManifest.xml文件

    AndroidManifest.xml常用标签解读 1.全局篇(包名,版本信息) 2.组件篇(四大组件) Activity Service Content Provider Broadcast Rec ...

  2. ubuntu下常用的apt-get 命令参数

    apt-cache search package 搜索包 apt-cache show package 获取包的相关信息,如说明.大小.版本等 sudo apt-get install package ...

  3. spring整合mybatis,springMVC的0配置文件方式

    0配置文件的形式主要是采用spring3.0提供的@configuration注解和spring容器在启动的时候会加载实现了WebApplicationInitializer的类,并调用其onStar ...

  4. 如何在Eclipse中配置python开发环境

    考虑到网上关于Eclipse中配置Python开发环境的文章千篇一律,故写此文以总结. 本文主要内容是:三种Pydev配置方法和一种PyDev卸载方法. 本文的前提是你已经安装了Eclipse和pyt ...

  5. Zepto 使用中的一些注意点(转)

    http://www.zeptojs.cn/     zepto英文站在线文档 http://www.css88.com/doc/zeptojs_api/       zepto中文站在线文档 htt ...

  6. 与文件上传到的三个类:FileItem类、ServletFileUpload 类、DiskFileItemFactory类

    文件上传: ServletFileUpload负责处理上传的文件数据,并将表单中每个输入项封装成一个FileItem对象中, 在使用ServletFileUpload对象解析请求时需要根据DiskFi ...

  7. js架构设计模式——由项目浅谈JS中MVVM模式

    1.    背景 最近项目原因使用了durandal.js和knockout.js,颇有受益.决定写一个比较浅显的总结. 之前一直在用SpringMVC框架写后台,前台是用JSP+JS+标签库,算是很 ...

  8. mysql 使用sqldump来进行数据库还原

    MYSQLdump参数详解 mysqldump备份: 复制代码代码如下: mysqldump -u用户名 -p密码 -h主机 数据库 a -w “sql条件” –lock-all-tables > ...

  9. 【JAVA笔记】JAVA后端实现统一扫码支付:微信篇

    最近做完了一个项目,正好没事做,产品经理就给我安排了一个任务.   做一个像收钱吧这样可以统一扫码收钱的功能.   一开始并不知道是怎么实现的,咨询了好几个朋友,才知道大概的业务流程:先是开一个网页用 ...

  10. 玩转 Ceph 的正确姿势

    玩转 Ceph 的正确姿势 本文先介绍 Ceph, 然后会聊到一些正确使用 Ceph 的姿势:在集群规模小的时候,Ceph 怎么玩都没问题:但集群大了(到PB级别),这些准则可是保证集群健康运行的不二 ...