MFC的YUV播放器实现

news2025/7/11 14:58:36

MFC的YUV播放器实现

文章目录

  • MFC的YUV播放器实现
    • 一、主要参考链接
    • 二、开发踩坑记录
      • 1、Gdiplus 绘图前置条件
      • 2、播放时点击滑竿能精准跳转
      • 3、鼠标悬停在滑竿上时显示预览小窗口

本文记录使用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 <gdiplus.h>
#pragma comment(lib, "gdiplus.lib")
using namespace Gdiplus;

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

    ULONG_PTR m_gdiplusToken;
    virtual int ExitInstance();

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

// CYUVPlayerApp 初始化

BOOL CYUVPlayerApp::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 CYUVPlayerApp::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 CYUVPlayerDlg::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 CYUVPlayerDlg::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;
	}
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/38474.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

四旋翼无人机学习第9节--OpenMV以及WIFI电路、供电电路再分析

文章目录0、参考与学习1、OpenMV插座电路2、OpenMV供电电路3、ESP8266电路分析3.0 绘制分享3.1 模块正面图3.2 模块背面图3.3 模块引脚介绍3.4 模块模式选择3.5 芯片手册参考电路4、供电电路分析4.1 电池接口与电源开关部分4.2 usb供电电路与电池电压降压电路4.3 5V降3.3V电路4…

[论文精度|博士论文]非结构环境下病虫害识别方法研究

概述 提出一种基于级联卷积神经网络的植物病害识别方法提出一种融合农田多源环境信息的害虫监测方法提出一种新的目标检测损失函数解决特征冲突问题 本文研究的主要方面在于特征提取。&#xff08;其他部分沿用目前最全面的公开数据和病虫害数据以及最先进的开源算法&#xf…

30岁之后身体还能像年轻的时候一样撸代码吗?

在IT圈流传着一句话&#xff0c;程序员吃的是青春饭。很多人认为&#xff0c;30岁是个阶段&#xff0c;在这个阶段后就需要往管理方向转型。因为在30岁之后身体再也不能像年轻的时候一样熬夜撸代码&#xff0c;而且继续从事一线开发的待遇也不如管理层优厚。至于转管理层失败的…

redis缓存一致性以及解决方案

一致性问题&#xff1a; 首先要到redis里面读取缓存&#xff0c;如果没有缓存&#xff0c;那么就到mysql里面去取数据&#xff0c;并且将其放置在缓存中 关于解决缓存一致性的问题&#xff0c;不难想到主要有两种解决方案&#xff0c;双更模式和删除模式 ** 双更模式&#xf…

【JVM】native关键字的使用

native关键字的使用一、JVM体系结构二、native是什么&#xff1f;三、native能干什么&#xff1f;四、native怎么使用&#xff1f;五、native总结在研读**《深入理解Java虚拟机》这本书时&#xff0c;看到 Java 虚拟机运行时数据区中有关本地方法栈**&#xff08;Native Method…

鲜花在线销售平台的设计与实现/鲜花商城/网上花店管理系统

摘 要 为了解决客户便捷地在网上购物&#xff0c;本文设计和开发了一个鲜花在线销售平台。本系统是基于web架构设计&#xff0c;SSM框架&#xff0c;javascript技术的前台页面设计与实现&#xff0c;使用Mysql数据库管理&#xff0c;综合采用java模式来完成系统的相关功能。主…

【GlobalMapper精品教程】028:栅格计算器的使用方法总结

文章目录 一、栅格计算器简介二、栅格计算器应用举例1. 归一化植被指数NDVI2. 归一化水体指数NDWI3. 归一化建筑指数NDBI一、栅格计算器简介 GlobalMapper中也提供了栅格计算器工具,可以方便的进行栅格计算、波段计算、指数计算等,使用方法有点儿像Envi软件。 用户可以使用系…

世界杯来了,让 Towhee 带你多语言「以文搜球」!

四年一度的世界杯已正式拉开战幕&#xff0c;各小组比赛正如火如荼地进行中。在这样一场球迷的盛宴中&#xff0c;不如让 Towhee 带你「以文搜球」&#xff0c;一览绿茵场上足球战将们的风采吧&#xff5e; 「以文搜球」是跨模态图文检索的一部分&#xff0c;如今最热门的跨模…

Leetcode刷题Day5休息 Day6----------哈希表

Leetcode刷题Day5休息 & Day6----------哈希表 1. 哈希表理论基础 数组、Set、Map 如果数据量小------------数组 如果数据量大------------Set 如果有Key、value------------Map 文章讲解&#xff1a;https://programmercarl.com/%E5%93%88%E5%B8%8C%E8%A1%A8%E7%90%86…

【雷达检测】基于复杂环境下的雷达目标检测技术(Matlab代码实现)

&#x1f468;‍&#x1f393;个人主页&#xff1a;研学社的博客 &#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜…

数据之道读书笔记-06面向“自助消费”的数据服务建设

数据之道读书笔记-06面向“自助消费”的数据服务建设 数据底座建设的目标是更好地支撑数据消费&#xff0c;在完成数据的汇聚、整合、联接之后&#xff0c;还需要在供应侧确保用户更便捷、更安全地获取数据。一方面业务人员希望尽可能快速地获取各种所需的数据&#xff0c;另一…

基于双目相机拍摄图像的深度信息提取和目标测距matlab仿真

目录 1.算法描述 2.仿真效果预览 3.MATLAB核心程序 4.完整MATLAB程序 1.算法描述 双目相机一般由左眼和右眼两个水平放置的相机组成。当然也可以做成上下两个目&#xff0c;但我们见到的主流双目都是做成左右的。在左右双目的相机中&#xff0c;我们可以把两个相机都看作针…

大数据毕设选题 - 深度学习图像超分辨率重建(opencv python cnn)

文章目录0 前言1 什么是图像超分辨率重建2 应用场景3 实现方法4 SRResNet算法原理5 SRCNN设计思路6 代码实现6.1 代码结构组织6.2 train_srresnet6.3 训练效果7 最后0 前言 &#x1f525; Hi&#xff0c;大家好&#xff0c;这里是丹成学长的毕设系列文章&#xff01; &#x…

读写分离和主从复制

这是只有一个数据库的情形&#xff0c;此时增删改查都是针对这个数据库而言 存在两个问题&#xff1a;所有压力都是由一台数据库承担&#xff0c;数据库压力很大 而且&#xff0c;一旦这个数据库发生故障&#xff0c;数据丢失&#xff0c;拿数据就全都没了 现在有两个数据库&…

总结使人进步,4句真章的理解和实践

在“总结使人进步&#xff0c;遵循事物的客观发展规律&#xff1b;祸福相依&#xff0c;知行合一”这篇文章里&#xff0c;首次全面提出了4句真章。 这么多年&#xff0c;最有感触的4句话。 一、4句真章 1、总结使人进步 2、遵循事物的发展规律 3、祸福相依 4、知行合一 …

muduo库中实现Protbuf编码器与消息分发器

文章目录1. protobuf的type name反射机制2. Protobuf编码器2.1 protbuf传输格式2.2 message转换为Buffer2.3 Buffer转换为message2.4 onMessage和send3. 消息分发器dispatcher3.1 成员变量3.2 onProtobufMessage3.3 registerMessageCallback4. 简单RPC4.1 query.proto4.2 serve…

QT:debug日志—打不开头文件以及qDebug和Q_CLASSINFO的使用

这个是因为链接器在给定路径上搜索不到对应的头文件&#xff0c;而大多数的Qt相关的头文件都集中在一个include文件夹里&#xff1a; 我电脑上的路径是&#xff1a;C:\Qt\Qt5.9.7\5.9.7\msvc2017_64\include 然后我们在项目设置里&#xff1a; 注意&#xff0c;这边要加上\*&…

Linux系统配置及服务管理-06-存储管理

基本分区 磁盘简介 磁盘/硬盘/disk是一个东西&#xff0c;不同于内存的是容量比较大。 类型 从工作原理区分 机械 机械硬盘即是传统普通硬盘&#xff0c;主要由&#xff1a;盘片&#xff0c;磁头&#xff0c;盘片转轴及控制电机&#xff0c;磁头控制器&#xff0c;数据转换…

钢材缺陷检测系统-ui界面

钢材缺陷检测系统-ui界面 之前写过这个博客: 工业缺陷检测项目实战(二)——基于深度学习框架yolov5的钢铁表面缺陷检测 里面介绍了使用yolov5进行训练的步骤。今天我们一起学习利用qt将缺陷检测封装为一个系统。 效果 首先看看效果&#xff1a; 我们运行&#xff0c;先可以看…

26岁月薪从7k到17K,这一切都要从那年失业讲起...

女生&#xff0c;目前在成都做了快4年的测试 先来说说我自己是怎么入行的以及我学到的一些经验分享&#xff0c;希望能帮助到更多的朋友们 我大学学的并不是计算机相关专业&#xff0c;学的市场营销&#xff0c;毕业后大部分同学都去做销售或者商务BD了&#xff0c;奈何自己性…