MFC的YUV播放器实现

MFC的YUV播放器实现

本文记录使用MFC编写一个YUV播放器的过程,尽量实现播放器都有的常用功能。功能参考与网上的各个文章。

目前已支持功能:

1、播放/暂停
2、停止
3、倒放
4、循环播放
5、快进/快退
6、单帧步进/步退
7、滑动条显示进度,点击滑动条任意位置精确跳转到相应帧。
8、鼠标悬停在滑动条上时显示预览小窗口
9、鼠标悬停在按钮上有提示语


主界面如下图
在这里插入图片描述

动态演示如下图:
在这里插入图片描述

一、主要参考链接

https://latelee.blog.youkuaiyun.com/article/details/47832985

二、开发踩坑记录

目前发现基于此类方法制作的播放器在全屏或者放大窗口的情况下,程序会跑得很慢,估计是使用BMP绘图的像素点多了的原因。

1、Gdiplus 绘图前置条件

1 需要导入静态库和命名控件

//gdi+库
#include <gdiplus.h>
#pragma comment(lib, "gdiplus.lib")
using namespace Gdiplus;

2 在窗口App头文件定义如下

    ULONG_PTR m_gdiplusToken;
    virtual int ExitInstance();

3 在初始化函数添加上代码

// CYUVPlayerApp 初始化

BOOL CVideoStartApp::InitInstance()
{
	// 如果一个运行在 Windows XP 上的应用程序清单指定要
	// 使用 ComCtl32.dll 版本 6 或更高版本来启用可视化方式,
	//则需要 InitCommonControlsEx()。否则,将无法创建窗口。
	INITCOMMONCONTROLSEX InitCtrls;
	InitCtrls.dwSize = sizeof(InitCtrls);
	// 将它设置为包括所有要在应用程序中使用的
	// 公共控件类。
	InitCtrls.dwICC = ICC_WIN95_CLASSES;
	InitCommonControlsEx(&InitCtrls);

    GdiplusStartupInput gdiplusStartupInput;
    //ULONG_PTR gdiplusToken;
    GdiplusStartup(&m_gdiplusToken, &gdiplusStartupInput, NULL);

	CWinApp::InitInstance();


	AfxEnableControlContainer();

	// 创建 shell 管理器,以防对话框包含
	// 任何 shell 树视图控件或 shell 列表视图控件。
	CShellManager *pShellManager = new CShellManager;

	// 标准初始化
	// 如果未使用这些功能并希望减小
	// 最终可执行文件的大小,则应移除下列
	// 不需要的特定初始化例程
	// 更改用于存储设置的注册表项
	// TODO: 应适当修改该字符串,
	// 例如修改为公司或组织名
	SetRegistryKey(_T("应用程序向导生成的本地应用程序"));

	CYUVPlayerDlg dlg;
	m_pMainWnd = &dlg;
	INT_PTR nResponse = dlg.DoModal();
	if (nResponse == IDOK)
	{
		// TODO: 在此放置处理何时用
		//  “确定”来关闭对话框的代码
	}
	else if (nResponse == IDCANCEL)
	{
		// TODO: 在此放置处理何时用
		//  “取消”来关闭对话框的代码
	}

	// 删除上面创建的 shell 管理器。
	if (pShellManager != NULL)
	{
		delete pShellManager;
	}

	// 由于对话框已关闭,所以将返回 FALSE 以便退出应用程序,
	//  而不是启动应用程序的消息泵。
	return FALSE;
}

int CVideoStartApp::ExitInstance()
{
    // TODO: Add your specialized code here and/or call the base class
    GdiplusShutdown(m_gdiplusToken); // ??

    return CWinApp::ExitInstance();
}

2、播放时点击滑竿能精准跳转

首先在OnHScroll加上代碼,然後重寫Slider类的OnLButtonDown函数

// 响应滚动条处理
void CVideoStartDlg::OnHScroll(UINT nSBCode, UINT nPos, CScrollBar* pScrollBar)
{
	// TODO: 在此添加消息处理程序代码和/或调用默认值	
	m_nCurrentFrame = m_Slider1.GetPos();	
	if (nPos)
		m_nCurrentFrame = nPos;
	this->Read(m_nCurrentFrame);
	this->Show();

	CDialogEx::OnHScroll(nSBCode, nPos, pScrollBar);	
}

MySlider.h

#pragma once
#include <afxcmn.h>
class MySlider :
	public CSliderCtrl
{
	DECLARE_DYNAMIC(MySlider)
public:
	MySlider();
	virtual ~MySlider();

protected:
	DECLARE_MESSAGE_MAP()
public:
	afx_msg void OnLButtonDown(UINT nFlags, CPoint point);
};

MySlider.c

#include "pch.h"
#include "MySlider.h"

IMPLEMENT_DYNAMIC(MySlider, CSliderCtrl)
MySlider::MySlider(){}
MySlider::~MySlider(){}

BEGIN_MESSAGE_MAP(MySlider, CSliderCtrl)
	ON_WM_LBUTTONDOWN()
END_MESSAGE_MAP()

void MySlider::OnLButtonDown(UINT nFlags, CPoint point)
{
#if 1
	CRect rc, trc;
	GetChannelRect(rc);
	GetThumbRect(trc);
	rc.InflateRect(0, (trc.Height() - rc.Height()) / 2);

	if (!PtInRect(rc, point))
		return;

	LONG range = GetRangeMax();
	LONG pos = point.x - rc.left - trc.Width() / 2;
	LONG width = rc.Width() - trc.Width();
	CSliderCtrl::SetPos(int(DOUBLE(pos) * range / width + 0.5));

	CSliderCtrl::OnLButtonDown(nFlags, point);
#endif	
}

3、鼠标悬停在滑竿上时显示预览小窗口

创建预览子窗口时,需要把外观中的Border属性设置为None,如下图

在这里插入图片描述

主窗口中:

// 首先添加 WM_SETCURSOR 消息
// 1 原理就是当鼠标进入Slider控件区域后,计算出鼠标当前所处的滑竿位置
// 2 然后计算出相应的帧数,在创建一个的小窗口,将图片以BMP的形式显示在上面
// 3 当进入滑竿区域就显示预览窗口,离开则隐藏预览窗口(无需关闭预览窗口)
// 4 根据鼠标位置实时移动预览小窗口
// 5 关键的几个函数如下 OnSetCursor,PreviewShow,m_pPreview->ShowPicture

BOOL CVideoStartDlg::OnSetCursor(CWnd* pWnd, UINT nHitTest, UINT message)
{
	// TODO: 在此添加消息处理程序代码和/或调用默认值
	if (pWnd->GetDlgCtrlID() == IDC_SLIDER1)
	{
		CString str;
		RECT rect, rt, crt;
		CPoint point, point1;
		float fNum = 0;
		int posFrame = 0;

		pWnd->GetWindowRect(&rect);// 获取的坐标为屏幕坐标系中的坐标
		this->ScreenToClient(&rect);// 转换为客户区坐标
		int nWhide = rect.right - rect.left;

		GetCursorPos(&point);
		point1 = point;
		ScreenToClient(&point);

		if (point.x > 0 && point.x < rect.right)
		{
			fNum = (float)point.x / nWhide;
			posFrame = (int)(fNum * m_nTotalFrame);
		}

		str.Format(L"x=%d, y=%d, w=%d.	f=%.4f, fps=%d", point.x, point.y, nWhide, fNum, posFrame);

		m_pPreview->GetWindowRect(&crt);

		if (m_pPreview)
		{
			// 显示预览小窗口
			PreviewShow(posFrame);
			m_pPreview->SetWindowPos(NULL, point1.x + 5, point1.y - 5 - crt.bottom + crt.top, 0, 0, SWP_NOZORDER | SWP_NOSIZE);
			m_pPreview->ShowWindow(SW_SHOW);
		}
		IDC_PREVIEW;

		AfxGetMainWnd()->SetWindowText(str);
	}
	else
	{
		if (m_pPreview)
		{
			m_pPreview->ShowWindow(SW_HIDE);
		}
		AfxGetMainWnd()->SetWindowText(L"xxxx");
	}

	return CDialogEx::OnSetCursor(pWnd, nHitTest, message);
}
// 显示小预览图
void CVideoStartDlg::PreviewShow(int nCurrentFrame)
{
	// 防止越界
	if (nCurrentFrame < 1 || nCurrentFrame > m_nTotalFrame)
		return;

	m_cFile.Seek(m_nYuvSize * (nCurrentFrame - 1), SEEK_SET);
	m_cFile.Read(m_pbYuvData, m_nYuvSize);
	
	// 先添加BMP头
	m_bmHeader2.bfType = 'MB';
	m_bmHeader2.bfSize = m_nBmpSize;// + sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER);
	m_bmHeader2.bfReserved1 = 0;
	m_bmHeader2.bfReserved2 = 0;
	m_bmHeader2.bfOffBits = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER);
	m_bmInfo2.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
	m_bmInfo2.bmiHeader.biWidth = m_nWidth;
	// note BMP图像是倒过来的
	m_bmInfo2.bmiHeader.biHeight = -m_nHeight;
	m_bmInfo2.bmiHeader.biPlanes = 1;
	m_bmInfo2.bmiHeader.biBitCount = 24;
	m_bmInfo2.bmiHeader.biCompression = BI_RGB;
	m_bmInfo2.bmiHeader.biSizeImage = m_nBmpSize - 54;
	m_bmInfo2.bmiHeader.biXPelsPerMeter = 0;
	m_bmInfo2.bmiHeader.biYPelsPerMeter = 0;
	m_bmInfo2.bmiHeader.biClrUsed = 0;
	m_bmInfo2.bmiHeader.biClrImportant = 0;

	memcpy(m_pbBmpData2, &m_bmHeader2, sizeof(BITMAPFILEHEADER));
	memcpy(m_pbBmpData2 + sizeof(BITMAPFILEHEADER), &m_bmInfo2, sizeof(BITMAPINFOHEADER));

	// 再转换格式
	if (m_nYuvFormat == FMT_RGB24 || m_nYuvFormat == FMT_BGR24)
	{
		memcpy(m_pbBmpData2 + 54, (unsigned char *)m_pbYuvData, m_nBmpSize - 54);
	}
	else
	{
		yuv_to_rgb24((YUV_TYPE)m_nYuvFormat, (unsigned char *)m_pbYuvData, (unsigned char *)m_pbBmpData2 + 54, m_nWidth, m_nHeight);
	}

	// BMP是BGR格式的,要转换 rgb->bgr
	if (m_nYuvFormat != FMT_BGR24)
	{
		swaprgb((BYTE*)(m_pbBmpData2 + 54), m_nBmpSize - 54);
	}

	m_pPreview->ShowPicture((BYTE*)m_pbBmpData2, m_nBmpSize);	

}

预览子窗口中:

inline void RenderBitmap(CWnd *pWnd, Bitmap* pbmp)
{
	RECT rect;
	pWnd->GetClientRect(&rect);

	Graphics grf(pWnd->m_hWnd);
	if (grf.GetLastStatus() == Ok)
	{
		Rect rc(rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top);

		grf.DrawImage(pbmp, rc);
	}
}

// CPreview 消息处理程序
void CPreview::ShowPicture(BYTE* pbData, int iSize)
{
	// 显示
	CWnd* pWnd = GetDlgItem(IDC_PREVIEW);   // IDC_VIDEO:picture contral 控件ID
	IStream* pPicture = NULL;
	CreateStreamOnHGlobal(NULL, TRUE, &pPicture);
	if (pPicture != NULL)
	{
		pPicture->Write((BYTE *)pbData, iSize, NULL);
		LARGE_INTEGER liTemp = { 0 };
		pPicture->Seek(liTemp, STREAM_SEEK_SET, NULL);
		Bitmap TempBmp(pPicture);
		RenderBitmap(pWnd, &TempBmp);

	}
	if (pPicture != NULL)
	{
		pPicture->Release();
		pPicture = NULL;
	}
}

三、源码下载链接

github仓库:
https://github.com/NineSunTaoist/MFC_YUV_play

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值