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)

FFmpeg入门 - rtmp推流

最简单的基于FFMPEG的Helloworld程序

最简单的基于FFmpeg的移动端例子附件:IOS自带播放器

如何利用ffmpeg将一小段视频截取成图片

如何用 FFmpeg 编写一个简单播放器详