invideo重大更新-全异步打开关闭

Part1前言

InVideo是一款基于虚幻引擎的安防视频播放插件,项目开源地址

https://github.com/inveta/InVideo

最近在使用中发现一个严重的问题:当摄像机不在线的时候,关闭会将整个虚幻蓝图卡主。

Part2问题分析

我们在打开视频的时候,会开启一个线程,详见代码

void UInVideoWidget::StartPlay

但是当视频不在线的时候,我们的run函数会卡主,大概持续时间在30秒左右。

if (false == m_WrapOpenCv->m_Stream.open(TCHAR_TO_UTF8(*m_VideoURL)))

当我们调用关闭的时候由于run函数没有退出,所以会一直阻塞 等待run函数退出。

void UInVideoWidget::StopPlay()
m_Thread->Kill();

这就是导致虚幻引擎蓝图卡主的根本原因。

Part3问题解决

既然找到了原因,我们只需要将void UInVideoWidget::StopPlay()改为异步执行,是不是就解决了呢?其实这里非常麻烦。需要解决以下几个问题:

1问题1

关闭完 再打开,所有变量是共享的,所以必须等待StopPlay执行成功才能进行后续的请求。
为了解决这个问题,我们需要将所有成员变量单独保存,每次打开都采用新的变量。另外还需要保持每次打开的线程都是重新开启的。

所以我们定义一个类,将成员变量全部移入

class VideoPlay :public FRunnable
{
public:
 void StartPlay(const FString VideoURL, FDelegatePlayFailed Failed, FDelegateFirstFrame FirstFrame,
  const bool RealMode = true, const int Fps = 25, UInVideoWidget* widget=nullptr);
 void StopPlay();
public:
 bool Init() override;
 uint32 Run() override;
 void Stop() override;
 void Exit() override;
private:
 void UpdateTexture();
 void UpdateTextureRegions(UTexture2D* Texture, int32 MipIndex, uint32 NumRegions, FUpdateTextureRegion2D* Regions, uint32 SrcPitch, uint32 SrcBpp, uint8* SrcData, bool bFreeData);
 void NotifyFailed();
 void NotifyFirstFrame();
public:
 UTexture2D* VideoTexture = nullptr;
 UInVideoWidget* m_widget = nullptr;
private:
 FRunnableThread* m_Thread = nullptr;
 TAtomic<bool> m_Stopping = false;
 FString m_VideoURL;
 float m_UpdateTime = 20;
 int m_Fps = 25;
 bool m_RealMode = true;
 float m_SleepSecond = 1 / 50;
 FDateTime m_LastReadTime = FDateTime::Now();

 FDelegatePlayFailed m_Failed;
 FDelegateFirstFrame m_FirstFrame;
 bool m_BFirstFrame = false;
 class WrapOpenCv
 {
 public:
  cv::VideoCapture m_Stream;
  cv::Mat m_Frame;
 };
 WrapOpenCv* m_WrapOpenCv = nullptr;

 FVector2D m_VideoSize = FVector2D(0, 0);
 FUpdateTextureRegion2D* m_VideoUpdateTextureRegion = nullptr;
 FTexture2DResource* m_Texture2DResource = nullptr;
 TArray64<FColor> Data;
};

2问题2

这个对象肯定保存在UInVideoWidget类中,如何对这个对象进行管理呢?比如我们又打开了一个新的,之前的对象如何释放?
对象释放的流程为:停止线程---》释放对象
如果我们来回打开不在线的时候,可能会存在好几个对象需要被释放?那么我们是不是还需要一个队列进行存储呢?还需要涉及多线程同步队列的问题。
这里我们引入一个非常厉害的概念:智能指针。通过智能指针来自动管理这个对象,这样我们就不需要用一个队列来管理指针的释放了。
我们定义一个智能指针,保存已经打开的视频。

TUniquePtr<VideoPlay> m_VideoPlayPtr;

在释放的时候,我们采用MoveTemp方式,将指针的所有权转移,这样就自动进行释放了。为了保证不卡主当前线程,我们采用了UE的异步任务来解决,代码如下

void UInVideoWidget::StopPlay()
{
 if (m_VideoPlayPtr.Get() == nullptr)
 {
  return;
 }
 AsyncTask(ENamedThreads::AnyBackgroundThreadNormalTask, [ptr = MoveTemp(m_VideoPlayPtr)]()
  {
   ptr->StopPlay();
  });
}

3问题3

UInVideoWidget和VideoPlay生命周期问题。之前我们将所有业务写在UInVideoWidget类中,这样完全不需要担心生命周期问题,本来就是一个对象。现在遇到的问题是,UInVideoWidget是由虚幻引擎的回收机制来进行回收,而VideoPlay是在一个异步任务里面进行回收,两个完全无法保证同一时刻谁能存在。

为了解决这个问题,我们又引入了一个新的概念,就是将所有和UInVideoWidget对象打交道的地方全部放到GameThread,然后再通过IsValidLowLevel函数检查是否存在。这样就可以确保每次执行的时候都能够知道UInVideoWidget对象是否被释放,示例代码如下

AsyncTask(ENamedThreads::GameThread, [vt = VideoTexture, widget = m_widget]()
  {
   if (false == widget->IsValidLowLevel())
   {
    return;
   }
   if (nullptr == widget->ImageVideo)
   {
    return;
   }
   if (false == vt->IsValidLowLevel())
   {
    return;
   }
   widget->ImageVideo->SetBrushFromTexture(vt);
  });

Part4问题4

UTexture2D的释放问题。之前我们将创建的UTexture2D保存在UInVideoWidget类,这样其生命周期和UInVideoWidget对象一直,但是我们的VideoPlay是一个C++对象,UE无法感知UTexture2D的对象存在,所以创建完之后,就会将其自动释放掉,为了解决这个问题,我们采用了AddtoRoot函数可以防止被虚幻自动释放。示例代码如下

VideoTexture = UTexture2D::CreateTransient(m_VideoSize.X, m_VideoSize.Y);
    if (VideoTexture)
    {
     VideoTexture->UpdateResource();
    }
    VideoTexture->AddToRoot();

当然在释放的时候,也需要RemoveFromRoot,示例代码如下

AsyncTask(ENamedThreads::GameThread, [vt = VideoTexture]()
  {
   if (vt->IsValidLowLevel()) 
   {
    vt->RemoveFromRoot();
   }
  });

Part5总结

本文针对invideo关闭视频卡住的问题进行了解决,并解决了在解决这个问题过程发现的另外四个问题:1、成员变量单独提取
2、智能指针自动释放
3、UInVideoWidget和VideoPlay生命周期同步
4、UTexture2D自动回收

Part6Inveta团队

Inveta团队由研发、美术设计、建模等组成。团队介绍:
https://www.inveta.cn/about.html
团队开源项目:
https://github.com/inveta

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值