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

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;
}

        

猜你喜欢

转载自blog.csdn.net/asiwxy/article/details/129325334