FFmpeg入门,简单播放器
2017-03-02 10:11
127 查看
一个偶然的机缘,好像要做直播相关的项目
为了筹备,前期做一些只是储备,于是开始学习ffmpeg
这是学习的第一课
做一个简单的播放器,播放视频画面帧
思路是,将视频文件解码,得到帧,然后使用定时器,1秒显示24帧
1.创建win32工程,添加菜单项 “打开”
为了避免闪烁,MyRegisterClass中设置hbrBackground为null
2.在main函数中初始化ffmpeg库:av_register_all();
3.响应菜单打开
使用c++11的线程来加载视频文件并进行解码工作。
4.在加载完视频之后,设置窗口为不可缩放
创建缓存DC等显示环境
设置播放帧画面的定时器
5.将解码的帧画面转化为 RGB 32位格式,并存储至队列中等待播放
6.播放帧画面
在WM_PAINT消息中进行绘画
因为解码拿到的是像素数据,需要将像素数据转化为Win32兼容位图
7.播放完毕,回复窗口设定,关闭定时器
代码流程一目了然,用来学习ffmpeg入门
最后贴上总的代码
main.cpp
完结撒花
为了筹备,前期做一些只是储备,于是开始学习ffmpeg
这是学习的第一课
做一个简单的播放器,播放视频画面帧
思路是,将视频文件解码,得到帧,然后使用定时器,1秒显示24帧
1.创建win32工程,添加菜单项 “打开”
为了避免闪烁,MyRegisterClass中设置hbrBackground为null
2.在main函数中初始化ffmpeg库:av_register_all();
3.响应菜单打开
void LoadVideoPlay(HWND hWnd) { if (gbLoadVideo) { return; } TCHAR szPath[1024] = { 0 }; DWORD dwPath = 1024; OPENFILENAME ofn = { 0 }; ofn.lStructSize = sizeof(ofn); ofn.hwndOwner = hWnd; ofn.hInstance = hInst; ofn.lpstrFile = szPath; ofn.nMaxFile = dwPath; ofn.lpstrFilter = _T("Video(*.mp4)\0*.MP4*;*.avi*\0"); ofn.Flags = OFN_FILEMUSTEXIST | OFN_HIDEREADONLY; ofn.lpstrInitialDir = _T("F:\\"); if (!GetOpenFileName(&ofn)) { DWORD dwErr = CommDlgExtendedError(); OutputDebugString(_T("GetOpenFileName\n")); return; } std::wstring strVideo = szPath; std::thread loadVideoThread([hWnd, strVideo]() { gbLoadVideo = TRUE; std::string sVideo = UnicodeToUTF_8(strVideo.c_str(), strVideo.size()); OpenVideoByFFmpeg(hWnd, sVideo.c_str()); gbLoadVideo = FALSE; }); loadVideoThread.detach(); }
使用c++11的线程来加载视频文件并进行解码工作。
4.在加载完视频之后,设置窗口为不可缩放
创建缓存DC等显示环境
设置播放帧画面的定时器
5.将解码的帧画面转化为 RGB 32位格式,并存储至队列中等待播放
6.播放帧画面
在WM_PAINT消息中进行绘画
void PlayFrame(HWND hWnd, UCHAR* buffer, UINT uWidth, UINT uHeight) { do { if (GetFramesSize() > (24 * 5)) // 缓冲5秒的帧数 { if (WaitForSingleObject(ghExitEvent, 3000) == WAIT_OBJECT_0) { return; } } else { HDC hDC = GetDC(hWnd); HBITMAP hFrame = CreateBitmapFromPixels(hDC, uWidth, uHeight, 32, buffer); if (hFrame) { PushFrame(hFrame); } ReleaseDC(hWnd, hDC); break; } } while (true); }
因为解码拿到的是像素数据,需要将像素数据转化为Win32兼容位图
HBITMAP CreateBitmapFromPixels(HDC hDC, UINT uWidth, UINT uHeight, UINT uBitsPerPixel, LPVOID pBits) { if (uBitsPerPixel <= 8) // NOT IMPLEMENTED YET return NULL; HBITMAP hBitmap = 0; if (!uWidth || !uHeight || !uBitsPerPixel) return hBitmap; LONG lBmpSize = uWidth * uHeight * (uBitsPerPixel / 8); BITMAPINFO bmpInfo = { 0 }; bmpInfo.bmiHeader.biBitCount = uBitsPerPixel; bmpInfo.bmiHeader.biHeight = -(static_cast<LONG>(uHeight)); // 因为拿到的帧图像是颠倒的 bmpInfo.bmiHeader.biWidth = uWidth; bmpInfo.bmiHeader.biPlanes = 1; bmpInfo.bmiHeader.biSize = sizeof(BITMAPINFOHEADER); // Pointer to access the pixels of bitmap UINT * pPixels = 0; hBitmap = CreateDIBSection(hDC, (BITMAPINFO *)& bmpInfo, DIB_RGB_COLORS, (void **)& pPixels, NULL, 0); if (!hBitmap) return hBitmap; // return if invalid bitmaps memcpy(pPixels, pBits, lBmpSize); return hBitmap; }
7.播放完毕,回复窗口设定,关闭定时器
代码流程一目了然,用来学习ffmpeg入门
最后贴上总的代码
// main.cpp : 定义应用程序的入口点。 // #include "stdafx.h" #include "testPlayVideo.h" #include <windows.h> #include <commdlg.h> #include <deque> #include <string> #include <mutex> #include <thread> extern "C" { #include "libavcodec/avcodec.h" #include "libavformat/avformat.h" #include "libavutil/pixfmt.h" #include "libavutil/imgutils.h" #include "libavdevice/avdevice.h" #include "libswscale/swscale.h" } #pragma comment(lib, "Comdlg32.lib") #define MAX_LOADSTRING 100 #define TIMER_FRAME 101 #define CHECK_TRUE(v) {if(!v) goto cleanup;} #define CHECK_ZERO(v) {if(v<0) goto cleanup;} // 全局变量: HINSTANCE hInst; // 当前实例 WCHAR szTitle[MAX_LOADSTRING]; // 标题栏文本 WCHAR szWindowClass[MAX_LOADSTRING]; // 主窗口类名 std::mutex gFrameLock; // 图像位图帧的访问锁 std::deque<HBITMAP> gFrames; // 所有解码的视频图像位图帧 HDC ghFrameDC; // 视频帧图像兼容DC HBITMAP ghFrameBmp; // 兼容DC的内存位图 HBRUSH ghFrameBrush; // 兼容DC的背景画刷 HANDLE ghExitEvent; // 程序退出通知事件 UINT uFrameTimer; // 定时器,刷新窗口显示图像位图帧 UINT uBorderWidth; UINT uBorderHeight; BOOL gbLoadVideo; DWORD dwWndStyle; // 此代码模块中包含的函数的前向声明: ATOM MyRegisterClass(HINSTANCE hInstance); BOOL InitInstance(HINSTANCE, int); LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM); INT_PTR CALLBACK About(HWND, UINT, WPARAM, LPARAM); HBITMAP CreateBitmapFromPixels(HDC hDC, UINT uWidth, UINT uHeight, UINT uBitsPerPixel, LPVOID pBits); HBITMAP PopFrame(void) { std::lock_guard<std::mutex> lg(gFrameLock); if (gFrames.empty()) { return nullptr; } else { HBITMAP hFrame = gFrames.front(); gFrames.pop_front(); return hFrame; } } void PushFrame(HBITMAP hFrame) { std::lock_guard<std::mutex> lg(gFrameLock); gFrames.push_back(hFrame); } size_t GetFramesSize(void) { std::lock_guard<std::mutex> lg(gFrameLock); return gFrames.size(); } void ReleasePaint(void) { if (ghFrameDC) DeleteDC(ghFrameDC); if (ghFrameBmp) DeleteObject(ghFrameBmp); if (ghFrameBrush) DeleteObject(ghFrameBrush); ghFrameDC = nullptr; ghFrameBmp = nullptr; ghFrameBrush = nullptr; } void CreatePaint(HWND hWnd) { RECT rc; GetClientRect(hWnd, &rc); HDC hDC = GetDC(hWnd); ghFrameDC = CreateCompatibleDC(hDC); ghFrameBmp = CreateCompatibleBitmap(hDC, rc.right - rc.left, rc.bottom - rc.top); ghFrameBrush = CreateSolidBrush(RGB(5, 5, 5)); SelectObject(ghFrameDC, ghFrameBmp); ReleaseDC(hWnd, hDC); } void ReleaseFrames(void) { std::lock_guard<std::mutex> lg(gFrameLock); for (auto& hFrame : gFrames) { DeleteObject(hFrame); } gFrames.clear(); ReleasePaint(); } void PlayFrame(HWND hWnd, UCHAR* buffer, UINT uWidth, UINT uHeight) { do { if (GetFramesSize() > (24 * 5)) // 缓冲5秒的帧数 { if (WaitForSingleObject(ghExitEvent, 3000) == WAIT_OBJECT_0) { return; } } else { HDC hDC = GetDC(hWnd); HBITMAP hFrame = CreateBitmapFromPixels(hDC, uWidth, uHeight, 32, buffer); if (hFrame) { PushFrame(hFrame); } ReleaseDC(hWnd, hDC); break; } } while (true); } std::string UnicodeToUTF_8(const wchar_t *pIn, size_t nSize) { if (pIn == NULL || nSize == 0) { return ""; } std::string s; int n = WideCharToMultiByte(CP_UTF8, NULL, pIn, nSize, NULL, 0, NULL, NULL); if (n > 0) { s.resize(n); WideCharToMultiByte(CP_UTF8, NULL, pIn, nSize, &s[0], n, NULL, NULL); } return s; } int APIENTRY wWinMain(_In_ HINSTANCE hInstance, _In_opt_ HINSTANCE hPrevInstance, _In_ LPWSTR lpCmdLine, _In_ int nCmdShow) { UNREFERENCED_PARAMETER(hPrevInstance); UNREFERENCED_PARAMETER(lpCmdLine); // TODO: 在此放置代码。 ghExitEvent = CreateEvent(nullptr, TRUE, FALSE, nullptr); av_register_all(); // 初始化全局字符串 LoadStringW(hInstance, IDS_APP_TITLE, szTitle, MAX_LOADSTRING); LoadStringW(hInstance, IDC_TESTPLAYVIDEO, szWindowClass, MAX_LOADSTRING); MyRegisterClass(hInstance); // 执行应用程序初始化: if (!InitInstance (hInstance, nCmdShow)) { return FALSE; } HACCEL hAccelTable = LoadAccelerators(hInstance, MAKEINTRESOURCE(IDC_TESTPLAYVIDEO)); MSG msg; // 主消息循环: while (GetMessage(&msg, nullptr, 0, 0)) { if (!TranslateAccelerator(msg.hwnd, hAccelTable, &msg)) { TranslateMessage(&msg); DispatchMessage(&msg); } } return (int) msg.wParam; } // // 函数: MyRegisterClass() // // 目的: 注册窗口类。 // ATOM MyRegisterClass(HINSTANCE hInstance) { WNDCLASSEXW wcex; wcex.cbSize = sizeof(WNDCLASSEX); wcex.style = CS_HREDRAW | CS_VREDRAW; wcex.lpfnWndProc = WndProc; wcex.cbClsExtra = 0; wcex.cbWndExtra = 0; wcex.hInstance = hInstance; wcex.hIcon = LoadIcon(hInstance, MAKEINTRESOURCE(IDI_TESTPLAYVIDEO)); wcex.hCursor = LoadCursor(nullptr, IDC_ARROW); wcex.hbrBackground = /*(HBRUSH)(COLOR_WINDOW+1)*/nullptr; wcex.lpszMenuName = MAKEINTRESOURCEW(IDC_TESTPLAYVIDEO); wcex.lpszClassName = szWindowClass; wcex.hIconSm = LoadIcon(wcex.hInstance, MAKEINTRESOURCE(IDI_SMALL)); return RegisterClassExW(&wcex); } // // 函数: InitInstance(HINSTANCE, int) // // 目的: 保存实例句柄并创建主窗口 // // 注释: // // 在此函数中,我们在全局变量中保存实例句柄并 // 创建和显示主程序窗口。 // BOOL InitInstance(HINSTANCE hInstance, int nCmdShow) { hInst = hInstance; // 将实例句柄存储在全局变量中 HWND hWnd = CreateWindowW(szWindowClass, szTitle, WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, nullptr, nullptr, hInstance, nullptr); if (!hWnd) { return FALSE; } ShowWindow(hWnd, nCmdShow); UpdateWindow(hWnd); return TRUE; } HBITMAP CreateBitmapFromPixels(HDC hDC, UINT uWidth, UINT uHeight, UINT uBitsPerPixel, LPVOID pBits) { if (uBitsPerPixel <= 8) // NOT IMPLEMENTED YET return NULL; HBITMAP hBitmap = 0; if (!uWidth || !uHeight || !uBitsPerPixel) return hBitmap; LONG lBmpSize = uWidth * uHeight * (uBitsPerPixel / 8); BITMAPINFO bmpInfo = { 0 }; bmpInfo.bmiHeader.biBitCount = uBitsPerPixel; bmpInfo.bmiHeader.biHeight = -(static_cast<LONG>(uHeight)); // 因为拿到的帧图像是颠倒的 bmpInfo.bmiHeader.biWidth = uWidth; bmpInfo.bmiHeader.biPlanes = 1; bmpInfo.bmiHeader.biSize = sizeof(BITMAPINFOHEADER); // Pointer to access the pixels of bitmap UINT * pPixels = 0; hBitmap = CreateDIBSection(hDC, (BITMAPINFO *)& bmpInfo, DIB_RGB_COLORS, (void **)& pPixels, NULL, 0); if (!hBitmap) return hBitmap; // return if invalid bitmaps memcpy(pPixels, pBits, lBmpSize); return hBitmap; } void PaintFrame(HWND hWnd, HDC hDC, RECT rc) { FillRect(ghFrameDC, &rc, ghFrameBrush); HBITMAP hFrame = PopFrame(); if (hFrame) { BITMAP bmp; GetObject(hFrame, sizeof(bmp), &bmp); HDC hFrameDC = CreateCompatibleDC(hDC); HBITMAP hOld = (HBITMAP)SelectObject(hFrameDC, hFrame); BitBlt(ghFrameDC, 4, 2, bmp.bmWidth, bmp.bmHeight, hFrameDC, 0, 0, SRCCOPY); SelectObject(hFrameDC, hOld); DeleteObject(hFrame); DeleteDC(hFrameDC); } BitBlt(hDC, 0, 0, rc.right - rc.left, rc.bottom - rc.top, ghFrameDC, 0, 0, SRCCOPY); } void OpenVideoByFFmpeg(HWND hWnd, const char* szVideo) { AVFormatContext* pFmtCtx = nullptr; AVCodecContext* pCodecCtx = nullptr; AVCodec* pCodec = nullptr; AVFrame* pFrameSrc = nullptr; AVFrame* pFrameRGB = nullptr; AVPacket* pPkt = nullptr; UCHAR* out_buffer = nullptr; struct SwsContext * pImgCtx = nullptr; int ret = 0; int videoStream = -1; int numBytes = 0; pFmtCtx = avformat_alloc_context(); CHECK_TRUE(pFmtCtx); ret = avformat_open_input(&pFmtCtx, szVideo, nullptr, nullptr); CHECK_ZERO(ret); ret = avformat_find_stream_info(pFmtCtx, nullptr); CHECK_ZERO(ret); for (UINT i = 0; i < pFmtCtx->nb_streams; ++i) { if (pFmtCtx->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) { videoStream = i; break; } } CHECK_ZERO(videoStream); pCodecCtx = avcodec_alloc_context3(nullptr); CHECK_TRUE(pCodecCtx); ret = avcodec_parameters_to_context(pCodecCtx, pFmtCtx->streams[videoStream]->codecpar); CHECK_ZERO(ret); pCodec = avcodec_find_decoder(pCodecCtx->codec_id); CHECK_TRUE(pCodec); ret = avcodec_open2(pCodecCtx, pCodec, nullptr); CHECK_ZERO(ret); pFrameSrc = av_frame_alloc(); pFrameRGB = av_frame_alloc(); CHECK_TRUE(pFrameSrc); CHECK_TRUE(pFrameRGB); pImgCtx = sws_getContext(pCodecCtx->width, pCodecCtx->height, pCodecCtx->pix_fmt, pCodecCtx->width, pCodecCtx->height, AV_PIX_FMT_RGB32, SWS_BICUBIC, nullptr, nullptr, nullptr); CHECK_TRUE(pImgCtx); numBytes = av_image_get_buffer_size(AV_PIX_FMT_RGB32, pCodecCtx->width, pCodecCtx->height, 1); out_buffer = (UCHAR*)av_malloc(numBytes); CHECK_TRUE(out_buffer); ret = av_image_fill_arrays(pFrameRGB->data, pFrameRGB->linesize, out_buffer, AV_PIX_FMT_RGB32, pCodecCtx->width, pCodecCtx->height, 1); CHECK_ZERO(ret); pPkt = new AVPacket; ret = av_new_packet(pPkt, pCodecCtx->width * pCodecCtx->height); CHECK_ZERO(ret); SetWindowPos(hWnd, nullptr, 0, 0, pCodecCtx->width + uBorderWidth, pCodecCtx->height + uBorderHeight, SWP_NOMOVE); ReleasePaint(); CreatePaint(hWnd); dwWndStyle = GetWindowLong(hWnd, GWL_STYLE); ::SetWindowLong(hWnd, GWL_STYLE, dwWndStyle&~WS_SIZEBOX); if (!uFrameTimer) uFrameTimer = SetTimer(hWnd, TIMER_FRAME, 40, nullptr); while (true) { if (av_read_frame(pFmtCtx, pPkt) < 0) { break; } if (pPkt->stream_index == videoStream) { ret = avcodec_send_packet(pCodecCtx, pPkt); if (ret < 0) continue; ret = avcodec_receive_frame(pCodecCtx, pFrameSrc); if (ret < 0) continue; ret = sws_scale(pImgCtx, pFrameSrc->data, pFrameSrc->linesize, 0, pCodecCtx->height, pFrameRGB->data, pFrameRGB->linesize); if (ret <= 0) continue; PlayFrame(hWnd, out_buffer, pCodecCtx->width, pCodecCtx->height); } av_packet_unref(pPkt); } if (uFrameTimer) { KillTimer(hWnd, uFrameTimer); uFrameTimer = 0; } if (dwWndStyle) ::SetWindowLong(hWnd, GWL_STYLE, dwWndStyle); cleanup: if (pFmtCtx) avformat_free_context(pFmtCtx); if (pCodecCtx) avcodec_free_context(&pCodecCtx); if (pFrameSrc) av_frame_free(&pFrameSrc); if (pFrameRGB) av_frame_free(&pFrameRGB); if (pImgCtx) sws_freeContext(pImgCtx); if (out_buffer) av_free(out_buffer); if (pPkt) { av_packet_unref(pPkt); delete pPkt; } } void LoadVideoPlay(HWND hWnd) { if (gbLoadVideo) { return; } TCHAR szPath[1024] = { 0 }; DWORD dwPath = 1024; OPENFILENAME ofn = { 0 }; ofn.lStructSize = sizeof(ofn); ofn.hwndOwner = hWnd; ofn.hInstance = hInst; ofn.lpstrFile = szPath; ofn.nMaxFile = dwPath; ofn.lpstrFilter = _T("Video(*.mp4)\0*.MP4*;*.avi*\0"); ofn.Flags = OFN_FILEMUSTEXIST | OFN_HIDEREADONLY; ofn.lpstrInitialDir = _T("F:\\"); if (!GetOpenFileName(&ofn)) { DWORD dwErr = CommDlgExtendedError(); OutputDebugString(_T("GetOpenFileName\n")); return; } std::wstring strVideo = szPath; std::thread loadVideoThread([hWnd, strVideo]() { gbLoadVideo = TRUE; std::string sVideo = UnicodeToUTF_8(strVideo.c_str(), strVideo.size()); OpenVideoByFFmpeg(hWnd, sVideo.c_str()); gbLoadVideo = FALSE; }); loadVideoThread.detach(); } // // 函数: WndProc(HWND, UINT, WPARAM, LPARAM) // // 目的: 处理主窗口的消息。 // // WM_COMMAND - 处理应用程序菜单 // WM_PAINT - 绘制主窗口 // WM_DESTROY - 发送退出消息并返回 // // LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) { switch (message) { case WM_CREATE: { CreatePaint(hWnd); RECT rc; GetClientRect(hWnd, &rc); RECT rcWnd; GetWindowRect(hWnd, &rcWnd); uBorderWidth = (rcWnd.right - rcWnd.left) - (rc.right - rc.left) + 2; uBorderHeight = (rcWnd.bottom - rcWnd.top) - (rc.bottom - rc.top) + 4; } break; case WM_TIMER: { if (uFrameTimer && (uFrameTimer == wParam)) { if (IsIconic(hWnd)) // 如果最小化了,则直接移除图像帧 { HBITMAP hFrame = PopFrame(); if (hFrame) { DeleteObject(hFrame); } } else { InvalidateRect(hWnd, nullptr, FALSE); } } } break; case WM_COMMAND: { int wmId = LOWORD(wParam); // 分析菜单选择: switch (wmId) { case IDM_OPEN: LoadVideoPlay(hWnd); break; case IDM_ABOUT: DialogBox(hInst, MAKEINTRESOURCE(IDD_ABOUTBOX), hWnd, About); break; case IDM_EXIT: DestroyWindow(hWnd); break; default: return DefWindowProc(hWnd, message, wParam, lParam); } } break; case WM_PAINT: { PAINTSTRUCT ps; RECT rc; GetClientRect(hWnd, &rc); HDC hdc = BeginPaint(hWnd, &ps); PaintFrame(hWnd, hdc, rc); EndPaint(hWnd, &ps); } break; case WM_DESTROY: SetEvent(ghExitEvent); ReleaseFrames(); PostQuitMessage(0); break; default: return DefWindowProc(hWnd, message, wParam, lParam); } return 0; } // “关于”框的消息处理程序。 INT_PTR CALLBACK About(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam) { UNREFERENCED_PARAMETER(lParam); switch (message) { case WM_INITDIALOG: return (INT_PTR)TRUE; case WM_COMMAND: if (LOWORD(wParam) == IDOK || LOWORD(wParam) == IDCANCEL) { EndDialog(hDlg, LOWORD(wParam)); return (INT_PTR)TRUE; } break; } return (INT_PTR)FALSE; }
main.cpp
完结撒花
相关文章推荐
- ffmpeg+sdl教程----编写一个简单的播放器4(让程序更模块化)
- ffmpeg+sdl教程----编写一个简单的播放器6(其他的时钟同步方式)
- ffmpeg + sdl -03 简单音频播放器实现
- 最简单的基于FFMPEG+SDL的音频播放器
- 博客园第一篇——SDL2+FFmpeg 制作简单播放器&同步
- ffmpeg+sdl教程----编写一个简单的播放器2(输出视频到屏幕)
- ffmpeg+sdl2.0做一个简单的音频播放器
- 最简单的基于FFMPEG+SDL的音频播放器
- FFMPEG+SDL简单播放器
- 如何用FFmpeg编写一个简单播放器详细步骤介绍(转载)
- ffmpeg+sdl教程----编写一个简单的播放器7(处理快进快退命令)
- 基于Ffmpeg解码器的简单播放器(a simple audio player based on Ffmpeg)
- ffmpeg+sdl教程----------编写一个简单的播放器1
- 最简单的基于FFMPEG+SDL的音频播放器
- ffmpeg+sdl教程——编写一个简单的播放器3(为视频加入音频)
- ffmpeg+sdl教程----编写一个简单的播放器5(同步视频到音频)
- ffmpeg+sdl教程----编写一个简单的播放器2(输出视频到屏幕)
- ffmpeg+sdl教程——编写一个简单的播放器1
- 最简单的基于FFMPEG+SDL的音频播放器
- ffmpeg+sdl教程----编写一个简单的播放器5(同步视频到音频)