您的位置:首页 > 其它

FFmpeg入门,简单播放器

2017-03-02 10:11 127 查看
一个偶然的机缘,好像要做直播相关的项目

为了筹备,前期做一些只是储备,于是开始学习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
完结撒花
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: