现开源一个项目 OEIP 项目例子: 项目实现的功能Demo展示
这个项目演示了在UE4中,接入摄像机通过OEIP直接输出到UE4纹理上,并直接把UE4里的RenderTarget当做输入源通过OEIP里GPU管线处理后推流出去,而另一边Unity3D也是把RenderTarget当做输入,用OEIP处理后推流,经过OEIP封装signalR技术的直播SDK通知,二边各自拉另一边的流并通过OEIP相应管线直接输出到Texture2D并显示出来。演示的机器配置是i5-7500,8G内存,有二个推1080P,拉1080P流的处理,再加上生成截屏视频和yolov3-tiny神经网络识别,所以CPU有点吃不消。
这是我个人验证一些技术所搭建的DEMO级方案,接入了基本的普通摄像头处理,也没有提供稳定的直播供应商的实现,一些基本的图像处理,推拉流也只支持422P/420P格式。但是我自己还是花了大量业余时间在这方案上,并以及大热情来完善,不过业余时间毕竟有限,测试不完善,加上本人C++不是太熟悉,所以肯定有很多隐藏问题,欢迎指出问题,更欢迎提交修改。
本项目重点主要在图像处理并与游戏引擎的对接上,主要实现与游戏引擎对接更少的性能消耗,方便引入各种图像处理,包括相关神经网络图像处理,余下处理都是结合网上代码加上测试完善逻辑。毕竟这个项目开始只是想验证DX11比CUDA的GPGPU计算资源占用高是不是因为线程组的分配方式,后来想着用神经网络层的做法来搭建相关逻辑,方便用来做测试一些算法。双十一腾讯的云服务器打折,一时手痒就买了台,现在不是直播很火吗,再加上对云游戏的概念感兴趣,本人在工作过程也接入过二个商业的直播SDK,通过接入SDK自己思考下流程,发现做一个技术验证性的DEMO还是比较容易的,所以也就有了这个项目。
本项目暂时只考虑WIN平台,但是框架从开始就考虑从多平台扩展,后面熟悉别的平台相关知识后,会把相应功能补起。
特点:
- 1 与游戏引擎UE4/Unity3D方便接入,引擎里的纹理可以直接传入传出。
- 2 图像处理现支持CUDA/DX11,图像处理管线可以直接输入输出DX11纹理,可以做到不需要CPU/内存做图像中转,提高效率。
- 3 图像处理管线类似神经网络框架的图像处理层设计,并且可以动态打开与关闭某层,方便组合。
- 4 方便接入各种神经网络框架处理,项目上面集成darknet,可以方便对比别的神经网络框架接入。
- 5 使用Media Foundation采集图像设备,WASAPI采集麦与声卡。
- 6 用signalR搭建直播SDK,配合nginx管理推拉流,使用ffmpeg编码解码推流拉流,设计支持多推流多拉流。
- 7 有一些同学找我要过我原来写的CUDA grabcut实现,我是感觉效果不好也没有商用价值,这次也集成在上面,要的可以去找相应实现自己改进。
- 8 结合后面5G,有4K,8K图像处理的,这种所有计算都用GPGPU来完成的应该有更多可能。
大致内容如下。
- 1 OEIP框架设计
- 2 GPGPU图像处理
- 3 采集音视频数据
- 4 FFmpeg编解码与推拉流
- 5 直播服务器设计
- 6 Unity3D插件
- 7 UE4插件
OEIP框架设计
和一般直播SDK类似,分为设备采集,图像/音频处理,编码,推流,服务器通知与分发,拉流,解码,图像显示这几步。
方案中,核心项目oeip定义上面模块的各个功能接口,插件模块化,图像处理层的设计。
图像处理层采用类神经网络实现,层之间可以互相结合,层支持多输入与多输出,可以方便扩展成别的GPGPU方案,现在主要是CUDA与DX11实现,CUDA模版添加与神经网络Darknet的集成,后续会引入别的神经网络框架集成图像处理。
关联项目:oeip
GPGPU图像处理
在游戏引擎里,想设计各种图像处理说方便也方便,说麻烦也很麻烦,说方便就是因为如果你想实现的功能在这个框架下,那很简单,嗯,UE4下如果要集成自己的Compute shader还是有点麻烦,复杂点我想引入抠图相关算法,会发现各种麻烦,以及如果想引入 神经网络框架的处理更是复杂,由此我想实现一个能支持CPU数据输入,也支持引擎里GPU数据直接输入,支持CPU数据输出,也支持直接把处理的GPU显存结果返回给游戏引擎,脱离实际游戏环境,只关注本身的逻辑实现。
最开始,并没有输入层与输出层的设计,但是有几个问题,如在DX11中,让所有层以纹理流通,而传入与传出的CPU数据与纹理长度不一定对应是其一,其二封装内存/显存处理,显存外部上下文与Oeip处理的上下文不同线程切换等,三是并不好处理多输入与多输出,中间层输出等各种问题,所以加入输入与输出层,这二层本身并没任何逻辑,专门用来解决上面的问题。
在GPU算法中,一是善用一些多线程的算法,如跨线程组步长的循环,以及线程组内二分操作,尽可以同时多利用线程组内所有线程。二是多利用共享显存,注意这个大小有限制,如果你把太多数据放进去,可能会起反作用。三是GPGPU线程组的划分也比较重要,如果出现几个线程同时访问或是读取某个显存地址,不管需要同步不,都不算太好的方式,情愿一个线程读写多个显存地址。四是可以在CPU确认判断可以先编译成不同GPU代码,如HLSL可以通过加入宏定义编译,而CUDA可以利用模版。五减少与CPU的数据交互,如1080P的数据下,上传与下载到显存的时间大约是你做一次基本图像处理的十倍左右,我认为的理想方法,要么是从CPU数据读入,然后所有处理在GPU,并通过引擎显示,或是数据就在GPU上,图像处理最后一步交给CPU传输用,或是从GPU来,GPU处理后再还给GPU,中间但凡出现多次CPU-GPU的交互不如考虑方案的合理性。
关联项目:oeip-win,oeip-win-cuda,oeip-win-dx11
CUDA版Grabcut的实现 整合Yolov3到UE4/Unity3D
采集音视频数据
这个没什么好说的,采集图像视频用的是Media Foundation技术,大约有几点,一是读不管异步还是同步,数据读取都应该放在非主线程中,用异步读自己不需要开,用同步自己管理线程,但是需要注意设备关闭时,确保相应数据流线程最好同步调用线程关闭,免的数据状态不正确。二是避免CPU处理数据,直接读取设备所支持的原生格式,如NV12(YUV420SP),YUV422I,我们在GPGPU图像处理层里有相应的YUV/RGB层,层里采集设备常用的NV12,YUV420I,BGR,YUV422I等都支持,当然传输用的YUV422P,YUV420P也是支持的,相应的CUDA/HLSL代码都有.三是我以前采过的坑,采集设备就是采集数据,他本身不应该和数据处理绑在一起。
音频采集用的WASAPI技术,处理没用Media Foundation,重采样,混音用的FFmpeg,音频采集主要是麦与声卡这二部分,麦还好,声卡处理需要注意静音的处理,别的跟着网上的代码来就行。
关联项目:oeip-win-mf,oeip-ffmpeg
FFmpeg编解码与推拉流
现在直播相关比较火,并且结合现在网络情况可以做很多原来想不到的事情,云游戏这种原概念产品感觉有完善的可能了,我今年也学了些FFmpeg相关知识用来储备。
推流前,数据处理后需要编码,主要用来压缩数据,可以说是超强的压缩率,在这只结合网上代码完善了H264与AAC这二种视频与音频编码方式,推拉流使用RTMP协议。
而拉流就是把上来的拉到的H264/AAC数据解码得到YUV/PCM固定格式后固定大小的数据,然后自己处理。
主要代码都是参照网上部分,然后整合,其中感觉主要是FFmpeg各种资源的销毁比较麻烦,比如要动态更新编码格式,重采样混音都有FFmpeg中间重用的资源,结合std::unique_ptr可以自定义销毁函数与模板,写出C#的感觉,省了我不少脑力与代码。
关联项目:oeip-ffmpeg
音视频开发-FFmpeg
直播服务器设计
直播服务器简单来说,就是通知一组成员之间消息流通,比如张三李四王五,张三上来了,李四推流了,王五关闭推流了等等这些消息,都需要及时通知这组里的所有成员,每个成员根据需求来对各种消息做各种处理。
直播服务器通信方案我选择的signarR,我对C#相关的技术熟悉点。
这只是一个非常简单的设计,主要分为三方,一是SDK调用方,也就是上面的张三李四王五他们,二是直播服务器,管理上面的各种通知,三是媒体服务器,管理推拉流的音视频数据。三方是可以分开放的,不过现没有丢桢方案,SDK调用方最好和媒体服务器在同一局域网效果会比较好。
相关流程简单来说,先打开直播服务器,然后打开媒体服务器,这样直播服务器就知道了所有的媒体服务器,然后SDK调用方连接直播服务器后,直播服务器返回给SDK调用方相应的媒体服务器地址,这样SDK调用方推流后就知道向那个媒体服务器的地址推流并记录下来,然后别的用户进来,就通知别的用户已经有别的用户推流了,并返回相应的推流地址,然后就可以拉流的,当然这个用户推流了,也需要返回相应推流地址给前一个用户。
注意事项,signalR 现在也是类似ASP net core里的一个中间件,在这为了直播服务器是否成功打开,我也写了个简单的中间件验证是否能成功连接服务器,打开服务器就会返回结果,在这中间件处理的是每个请求,每次请求都会生成一个HUB对象,这样导致相应的HUB里面绑定事件话,会累加,所以并不是一个好的选择,可以用GlobalHost返回这个HUB逻辑上的所有链接用户。
SDK调用方,我最开始找的是signalR的C++实现,可惜,一个是老版概念signalR 实现的,几年没更新了,最新的在asp net core下有份C++ 实现,这个还没BATA版,故客户端SDK调用方与直播服务器通信用C#完成,我们知道,与播服务器通信主要是二个部分,一个是我们主动提交的信息,如我们登陆了,我们推流了,还有一个是直播服务器的通知,比如通知你别的用户上线,别的用户推流了。第一个部分我们主动发起通知,表现就是我们从C++调用相关C#的实现,而第二部分是服务器通知回调,需要从C#端通知到C++端,这个算是不常用调用方法,综合考虑了下,把相应的C#客户端封装成COM接口,方便一是C++调用相关C#的实现,二是把相应的C++接口实现传入到C#环境中去执行。需要注意的,这个C++客户端事实上包含相应的CLI环境,所以如果销毁资源,如unity3D/UE4里的每次play/endplay间,要确认引用的C++DLL所关联的CLI环境已经清理干净,我反正是在对应销毁时调用GC.WaitForPendingFinalizers()才搞定关闭时不挂起的现象。
需要注意的是,客户端C#使用COM封装,那么每台机器需要注册相应的COM组件,如果你是用的VS,直接开管理员,编译相应的OeipLiveCom项目就行。
当然这个等asp net core signalR的C++实现完善后,会把相应C#+COM/C++调用方案改成全C++低层实现。
关联项目:OeipLiveServer,OeipLiveMedia,OeipLiveCom,oeip-live,oeip-live-ffmpeg
Unity3D插件
因为在验证各项功能前,我已经用WinForm+SharpDx做了验证项目,包含DX11纹理的传入传出验证,Unity3D的大部分代码和这部分共用,注意事项就一点,在Unity3D C#中我们拿不到DX11设备与上下文,我们需要编写一个Unity3D的非托管插件,在这插件里我们能拿到Unity3D的DX11设备与上下文,结合OEIP原来接口再封装一层。
注意事项,更新Unity3D的RHI资源,需要用到Unity3D的非托管插件特定的写法,保证在渲染线程中更新资源,而OEIP回调大部分在非主线程中,所以回调里要用到Unity3D游戏线程里的资源里,请转到游戏线程去执行。
关联项目:oeip-unity3d,OeipWrapper,OeipUnity3D
更详细说明请看 UE4/Unity3D中同时捕获多高清摄像头的高效插件
UE4插件
基本和Unity3D插件思路一样,相应数据处理编写相应管线,设备数据处理管线,拉流管线,推流管线,直播SDK的再封装都是差不多的,就连注意事项也是差不多,回调里用到UE4资源的,请转到游戏线程,用到RHI资源的,请转到渲染线程。
关联项目:OeipUE4
更详细说明请看 UE4/Unity3D中同时捕获多高清摄像头的高效插件
最后说下项目编译相关
我主要环境在VS2017上开发。
第三方库:
CUDA 10.1安裝:https://developer.nvidia.com/cuda-downloads
CUDNN 10.1安裝:https://developer.nvidia.com/cudnn
下载https://github.com/xxxzhou/oeip-thridparty在Oeip项目下,新建一个ThirdParty文件夹,把oeip-thridparty里的文件全部复制到这。 二种引用DLL方式。 一是把相应的DLL复制到对应oeip dll目录下。 二是在环境变量里把上面的几个文件夹的BIN目录写入,推荐第二种。(1 ThirdParty\cuda 2 ThirdParty\FFmpeg\dll 3 ThirdParty\opencv4\bin 4 ThirdParty\pthread\dll).
直播SDK环境配置:
- 1 先启动直播服务器 OeipLiveServer
- 2 启动媒体服务器 OeipLiveMedia
- 3 本机注册OeipLiveCom这个COM组件,然后就可以用了。
相应UE4/Unity3D里神经网络加载用的的绝对路径,请自己修改相应路径。
其主要只考虑了64位,相应编译的环境只有64位配置了,32位需要自己配置。