• MFC的YUV播放器实现


    MFC的YUV播放器实现

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

    目前已支持功能:

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


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

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

    一、主要参考链接

    https://latelee.blog.csdn.net/article/details/47832985

    二、开发踩坑记录

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

    1、Gdiplus 绘图前置条件

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

    //gdi+库
    #include 
    #pragma comment(lib, "gdiplus.lib")
    using namespace Gdiplus;
    
    • 1
    • 2
    • 3
    • 4

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

        ULONG_PTR m_gdiplusToken;
        virtual int ExitInstance();
    
    • 1
    • 2

    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();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68

    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);	
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    MySlider.h

    #pragma once
    #include 
    class MySlider :
    	public CSliderCtrl
    {
    	DECLARE_DYNAMIC(MySlider)
    public:
    	MySlider();
    	virtual ~MySlider();
    
    protected:
    	DECLARE_MESSAGE_MAP()
    public:
    	afx_msg void OnLButtonDown(UINT nFlags, CPoint point);
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    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	
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30

    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);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    // 显示小预览图
    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);	
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51

    预览子窗口中:

    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;
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36

    三、源码下载链接

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

  • 相关阅读:
    使用Nginx和uwsgi在自己的服务器上部署python的flask项目
    Node.js 项目搭建
    Linux网络基础-6
    学习网络安全:记一次某网站渗透测试过程
    集合原理简记
    数据结构 - 3(链表12000字详解)
    广西(柳州)创建国家级车联网先导区建设方案
    MST2101Q2 摩托车三相磁电机调压器控制芯片
    React-Context实现水印功能
    支付宝支付---流程详解
  • 原文地址:https://blog.csdn.net/qq_31507523/article/details/128055392