UE4 c++ Mediaplayer取消自动播放,运行时首帧为黑屏的问题

news2024/11/25 11:29:47

0,前言

        工作需要使用C++制作一个ue4的视频插件,其中一个功能是能够选择 运行时是否自动播放 视频的功能。

        在实现时遇见了一个问题,取消自动播放之后,运行时首帧是没有取到的,在场景里面看是黑色的。就这个问题我想到了使用了一个线程去监控纹理渲染,渲染到第一帧,就暂停,这样的效果看起来就是取消自动播放时,视频插件在场景中显示视频的第一帧。插件的代码不方便贴出来,我这里只贴线程监控的代码。

        大部分都是使用蓝图和UE自带的mediaplayer实现视频播放的方案,直接使用UE的mediaplayer,他在给plane附加纹理的时候,会自动生成一个当前视频帧的材质,所以看不到黑帧。但是其实在打开ue第一次运行的时候,还没有读取视频,视频帧的材质也是黑帧。

        中间尝试了在mediaplayer中添加回调,在MediaOpened事件中调用play(),然后调用seek()函数,在SeekCompleted事件中调用pause()函数,来做到播放第一帧的效果,但是play()函数只是设置rate为1,渲染第一帧的工作是别的线程实现的,这也就导致,在SeekCompleted事件中调用pause()函数时,另一个线程还没有渲染第一帧,从而运行时还是显示黑帧。

1,原理

        查看了mediaplayer的源码,大概捋了一下它的流程,下面是流程图,分了两个线程,一个向队列里面送buffer,一个从队列里面取buffer之后送去渲染

         这只是其中一部分和mediaplayer有关的线程,中间还有很多流程。但我们只需要了解到这个线程是怎么运行的就能够解决目前的问题。

        大家可以去贴出来的函数里面打个断点跟一下下流程,大概了解一下就行。

        主要看一下第二天取buffer的线程,也就是红框中的流程,有下面的代码:

				while (SampleQueue->Dequeue(Sample))
					;

				if (!Sample.IsValid())
				{
					// Player is active (do not clear), but we have no new data
					// -> we do not need to trigger anything on the renderthread
					return;
				}

				UpdateSampleInfo(Sample);

				RenderParams.TextureSample = Sample;

				RenderParams.Rate = CurrentPlayerPtr->GetRate();
				RenderParams.Time = Sample->GetTime();

        其中最后一行代码 Sample->GetTime(); 这个GetTime()是纹理中player的时间,我猜是记录播放时长,我们利用这个来实现监控纹理。

        原理就是获取视频的帧率,从而获取每帧播放的时长,启动一个线程,监控纹理的player的播放时长,在大于每帧播放时间的时候就暂停掉,从而实现第一帧暂停的效果。

2,实现

        多线程的时候代码参考(照抄)了这位博主的:

        UE4 C++ 子线程的创建及使用_ue4 启动线程_北极熊的奋斗史的博客-CSDN博客

头文件:

class RNGThread : public FRunnable
{
public:
	//Constructor
	RNGThread(int Count = 50000, int minNumber = 0, int maxNumber = 1000, int chunkCount = 20);
	//Destructor
	~RNGThread();

	//	杀死线程,该线程将不能再使用,想再开启的话,需要重新创建
	//Use this method to kill the thread!!
	void EnsureCompletion();

	//	暂停线程
	//Pause the thread 
	void PauseThread();

	//	继续线程
	//Continue/UnPause the thread
	void ContinueThread();

	//	当前线程是否处于暂停状态
	bool IsThreadPaused();

	bool setMediaPlayer(UMediaPlayer* MediaPlayer);

	bool setVideoComponent(UPXVideoComponent* PXVideoComponent);

protected:
	//FRunnable interface.
	virtual bool Init();
	virtual uint32 Run();
	virtual void Stop();

private:
	//Thread to run the worker FRunnable on
	FRunnableThread* Thread;

	UMediaPlayer* MediaPlayer = nullptr;
	UPXVideoComponent* PXVideoComponent = nullptr;

	FCriticalSection m_mutex;		//	线程锁
	FEvent* m_semaphore;			//	信号量

	int m_chunkCount;
	int m_amount;
	int m_MinInt;
	int m_MaxInt;

	//As the name states those members are Thread safe
	FThreadSafeBool m_Kill;		//	bool 类型的变量,线程安全
	FThreadSafeBool m_Pause;

};

源文件:

RNGThread::RNGThread(int Count, int minNumber, int maxNumber, int chunkCount)
{
	m_Kill = false;
	m_Pause = false;

	//Initialize FEvent (as a cross platform (Confirmed Mac/Windows))
	m_semaphore = FGenericPlatformProcess::GetSynchEventFromPool(false);			//	信号量

	m_MinInt = minNumber;
	m_MaxInt = maxNumber;

	m_chunkCount = chunkCount;

	//	启动线程
	Thread = FRunnableThread::Create(this, TEXT("RNGThread"), 0, TPri_BelowNormal);
}

RNGThread::~RNGThread()
{
	if (m_semaphore)
	{
		//Cleanup the FEvent
		FGenericPlatformProcess::ReturnSynchEventToPool(m_semaphore);
		m_semaphore = nullptr;
	}

	if (Thread)
	{
		//Cleanup the worker thread
		delete Thread;
		Thread = nullptr;
	}
}

bool RNGThread::Init()
{
	//Init the Data 
	return true;
}

/***************************************************************************************/
/*注意:不要在线程中做 spawning / modifying / deleting UObjects / AActors 等等之类的事 */
/***************************************************************************************/

uint32 RNGThread::Run()
{
	//	等待一下初始化
	//Initial wait before starting
	FPlatformProcess::Sleep(0.03);

	//	判断是否停止了线程
	while (!m_Kill)
	{
		//	判断当前是否处于暂停状态
		if (m_Pause)
		{
			//使用信号量使线程处于睡眠状态,直到被唤醒
			m_semaphore->Wait();

			if (m_Kill)
			{
				return 0;
			}
		}
		else
		{
			if(MediaPlayer != nullptr)
			{
				float framerate = MediaPlayer->GetVideoTrackFrameRate(0,0);
				FTimespan FrameTime = FTimespan::FromSeconds(1 / (FMath::IsNearlyZero(framerate) ? -1 : framerate));
				if (MediaPlayer->GetTime() <= FrameTime)
				{
					while ((MediaPlayer->GetTime() <= FrameTime) && !m_Pause);
					MediaPlayer->Pause();
					break;
				}
			}
		}

		FPlatformProcess::Sleep(0.01);
	}

	return 0;
}

void RNGThread::PauseThread()
{
	m_Pause = true;
}

void RNGThread::ContinueThread()
{
	m_Pause = false;

	//	启动线程
	if (m_semaphore)
	{
		//Here is a FEvent signal "Trigger()" -> it will wake up the thread.
		m_semaphore->Trigger();
	}
}

void RNGThread::Stop()
{
	//	设置停止的标志
	m_Kill = true; //Thread kill condition "while (!m_Kill){...}"
	m_Pause = false;

	//	触发一下线程,让其在下一次判断中退出
	if (m_semaphore)
	{
		//We shall signal "Trigger" the FEvent (in case the Thread is sleeping it shall wake up!!)
		m_semaphore->Trigger();
	}
}

//Use this method to kill the thread!!
void RNGThread::EnsureCompletion()
{
	//	停止线程
	Stop();

	//	等待线程运行结束
	if (Thread)
	{
		Thread->WaitForCompletion();
	}

	if (this->MediaPlayer != nullptr)
	{
		MediaPlayer = nullptr;
	}
	if (this->PXVideoComponent != nullptr)
	{
		this->PXVideoComponent->UnregisterComponent();
		this->PXVideoComponent->DestroyComponent();
		this->PXVideoComponent = nullptr;
	}
}


bool RNGThread::IsThreadPaused()
{
	return (bool)m_Pause;
}

bool RNGThread::setMediaPlayer(UMediaPlayer* pMediaPlayer)
{
	this->MediaPlayer = pMediaPlayer;
	return true;
}
bool RNGThread::setVideoComponent(UPXVideoComponent* pPXVideoComponent)
{	
	this->PXVideoComponent = pPXVideoComponent;
	return true;
}

使用方法:

启动线程:
RNGThread * pThread = new RNGThread();
pThread->setMediaPlayer(MediaPlayer);

关闭线程:
if (pThread != nullptr)
{
	pThread->EnsureCompletion();
	delete pThread;
	pThread = nullptr;
}

        

 

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

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

相关文章

Kubernetes k8s 笔记

核心功能 容器编排和管理&#xff1a;Kubernetes可以自动化容器的部署、管理和扩展&#xff0c;使得应用程序可以在多个容器之间进行平滑的切换。自动化负载均衡&#xff1a;Kubernetes可以通过将请求分配到不同的容器来平衡负载&#xff0c;以确保应用程序的高可用性和性能。…

【大数据离线开发】8.4 Hive的查询、操作以及自定义函数

8.5 Hive的查询 执行SQL&#xff08;HQL&#xff09;。HQL是SQL的一个子集 案例&#xff1a;创建部门表&#xff0c;对数据进行查询 创建部门表 create table deptno(deptno int,dname string,loc string ) row format delimited fileds terminated by ,;导入数据 load data…

函数式编程:Lambda 表达式

函数式编程&#xff1a;Lambda 表达式 每博一文案 曾经读过的依然令我感动的句子&#xff0c;生活总是不如意&#xff0c;但往往是在无数痛苦中&#xff0c;但往往是在无数痛苦中&#xff0c;在重重矛盾 和艰难中才能成熟起来&#xff0c;坚强起来&#xff0c;爱情啊&#xf…

JMeter 控制并发数

文章目录一、误区二、正确设置 JMeter 的并发数总结没用过 JMeter 的同学&#xff0c;可以先过一遍他的简单使用例子 https://blog.csdn.net/weixin_42132143/article/details/118875293?spm1001.2014.3001.5501 一、误区 在使用 JMeter 做压测时&#xff0c;大家都知道要这么…

又拍云邵海杨 - 25年Linux老兵,聊聊运维的“术”与“道”

您好邵总&#xff0c;请您先做个自我介绍吧&#xff0c;聊聊您的履历和现状&#xff0c;让大家更好的认识您&#xff0c;了解您的背景也有助于读者理解后面的采访内容 我是来自又拍云的邵海杨&#xff0c;从1998年开始使用Linux至今快25年了&#xff0c;资深(老鸟)Linux系统运维…

Log Structure Merge Tree

LSM是一种基于日志追加写的数据结构&#xff0c;非常适合为具有高写入数据提供索引访问 LSM基于以下前提 内存读写速度远高于磁盘&#xff0c;但内存有限磁盘顺序读写速度远高于随机读写 结构 WAL WAL(write-ahead log)是用于在系统错误时提供持久化&#xff0c;在写入数据…

SpringBoot自动装配原理、条件注解及封装Starter

1.什么是 SpringBoot 自动装配&#xff1f; 我们现在提到自动装配的时候&#xff0c;一般会和Spring Boot联系在一起。但是实际上SpringFramework 早就实现了这个功能。Spring Boot 只是在其基础上&#xff0c;通过 SPI 的方式&#xff0c;做了进一步优化。 SpringBoot 定义了…

C++之string字符串不同类型间转换

&#x1f4e3;&#x1f4e3;&#x1f4e3;&#x1f4e3;&#x1f4e3;&#x1f4e3;&#x1f4e3;&#x1f4e3; ✏️作者主页&#xff1a;枫霜剑客 &#x1f4cb; 系列专栏&#xff1a;C实战宝典 &#x1f332;上一篇: VS2019加载解决方案时不能自动打开之前的文档&#xff08…

pytorch 笔记:torch.fft

1 FFT 进行一个维度的快速傅里叶变换 torch.fft.fft(input, nNone, dim- 1, normNone, *, outNone) 1.1 主要参数 input输入&#xff0c;需要傅里叶变换的tensorn 需要变换的tensor的长度&#xff0c;默认是input的长度 如果比input长度大&#xff0c;那么补0如果比input长度…

乐山持点科技:抖音极速版电商入驻指南

“抖音极速版电商”拥有海量活跃用户&#xff0c;着眼下沉市场&#xff0c;为消费者带来高性价比产业带源头好货。针对中小商家设立更宽松的经营要求、提供简单易上手的经营工具&#xff0c;助力商家轻松经营&#xff01;来看入驻指南&#xff1a;一、抖音与抖音极速版电商入驻…

JavaScript基础五、语句

零、文章目录 文章地址 个人博客-CSDN地址&#xff1a;https://blog.csdn.net/liyou123456789个人博客-GiteePages&#xff1a;https://bluecusliyou.gitee.io/techlearn 代码仓库地址 Gitee&#xff1a;https://gitee.com/bluecusliyou/TechLearnGithub&#xff1a;https:…

float的表示范围为什么比long大

●很多人会有一个疑问, 一个用来表示小数的 float 为什么表示的范围会比 long 还要大呢 ? ●这次, 咱们就来详细说一说这个事情 从长计议 ●聊到这个话题, 我们就要从计算机存储数字这个位置说起了 ●计算机存储数字的方式其实就是 : 二进制 二进制是计算机中最基本的数字存储…

Java学习笔记 --- jQuery

一、jQuery介绍 jQuery&#xff0c;顾名思义&#xff0c;也就是JavaScript和查询&#xff08;Query&#xff09;&#xff0c;它就是辅助JavaScript开发的js类库。它的核心思想是write less&#xff0c;do more&#xff08;写得更少&#xff0c;做得更多&#xff09;&#xff0c…

2023王道考研数据结构笔记第五章——树

第五章 树 5.1 树的基本概念 树是n&#xff08;n≥0&#xff09;个结点的有限集合&#xff0c;n 0时&#xff0c;称为空树。 空树——结点数为0的树 非空树——①有且仅有一个根节点 ​ ②没有后继的结点称为“叶子结点”&#xff08;或终端结点&#xff09; ​ ③有后继的结…

webStorm svn不显示的问题

看图就行了&#xff0c;怎么下载安装我就不说了&#xff0c;网上一搜全是

<JVM上篇:内存与垃圾回收篇>13 - 垃圾回收器

笔记来源&#xff1a;尚硅谷 JVM 全套教程&#xff0c;百万播放&#xff0c;全网巅峰&#xff08;宋红康详解 java 虚拟机&#xff09; 文章目录13.1. GC 分类与性能指标13.1.1. 垃圾回收器概述13.1.2. 垃圾收集器分类13.1.3. 评估 GC 的性能指标13.2. 不同的垃圾回收器概述13.…

JavaScript基础一、简介

零、文章目录 文章地址 个人博客-CSDN地址&#xff1a;https://blog.csdn.net/liyou123456789个人博客-GiteePages&#xff1a;https://bluecusliyou.gitee.io/techlearn 代码仓库地址 Gitee&#xff1a;https://gitee.com/bluecusliyou/TechLearnGithub&#xff1a;https:…

1.认识网络爬虫

1.认识网络爬虫网络爬虫爬虫的合法性HTTP协议请求与响应(重点)网络爬虫 爬虫的全名叫网络爬虫&#xff0c;简称爬虫。他还有其他的名字&#xff0c;比如网络机器人&#xff0c;网络蜘蛛等等。爬虫就好像一个探测机器&#xff0c;它的基本操作就是模拟人的行为去各个网站溜达&am…

Modbus转profinet网关连接Smart PLC与ABB变频器实现Modbus通信

本案例讲述了modbus转profinet网关&#xff0c;在系统改造中把ABB变频器接入到Smart PLC的Profinet总线上&#xff0c;PLC不用编程实现Profinet转modbus的协议互转&#xff0c;并且SmartPLC485口连接触摸屏采集数据&#xff0c;完成对变频器读写控制 网络拓展图如下图所示&…

达梦数据库运行内存参数配置学习笔记

一、设置运行中的数据库参数 SP_SET_PARA_VALUE(2,MAX_SESSIONS,500) 设置数据库运行参数&#xff0c;参数为 1 表示获取 INI 文件中配置参数的值&#xff0c;为 2 表示获取内存中配置参数的值 二、修改dm.ini数据库运行参数文件&#xff0c;重启数据库 不能动态修改dm.ini参数…