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