FFmpeg入门,简单播放器
Posted Jojodru
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了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 else 13 { 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 bitmap 17 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 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(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 else 129 { 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 YET 260 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 bitmap 273 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 bitmaps 280 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 = 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 = 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); 100行代码实现最简单的基于FFMPEG+SDL的视频播放器(SDL1.x)