视频服务器(8) Kurento[3] unity客户端

目录

一、测试入口

二、.Binary messages not supported 

三、发送start指令

1.使用KurentoUtils(不行)

2.使用awrtc的StartSignaling(可行)

四、处理接收指令

五、暂停等指令接口

六、Unity播放

七、Unity帧率问题(视频分辨率)

八、播放多个摄像头视频

九、js核心原型代码


现在有两种方式:

1).awrtc.js连接java服务端,实现kurento-player-js的功能。

2).kurento-player-js加入awrtc.js里面的获取图片的功能,并对接unity。

先以用awrtc.js连接kurento播放视频为目标,因为awrtc.js和unity的对接已经是做好的了,只要能够播放视频,后续的unity内的代码不用做调整。

awrtc.js是AssetStore里面的插件中的js部分改造后的。

插件:WebRTC Video Chat

awrtc相关博客:WebGL实时视频(5) awrtc.js理解并修改WebGL实时视频(6) Unity里面显示视频

awrtc本身就是一个完整的webrtc的客户端js库,可以连接自己的服务端,改造后可以连接H5Stream,这次则是要连接kurento的java服务端,这些服务端从概念上讲都是信令服务器。

一、测试入口

在复制原来的func_CAPI_H5Stream_GetVideo的基础上修改出一个func_CAPI_Kurento_GetVideo。

                function func_CAPI_Kurento_GetVideo(rtsp,serverUrl) {//[kurento]
                    console.log("func_CAPI_Kurento_GetVideo",rtsp,serverUrl);
                    BrowserMediaStream.DEBUG_SHOW_ELEMENTS=true;//在网页中显示视频
                    var netConf=new NetworkConfig();
                    netConf.SignalingUrl=serverUrl;
                    //...
                }

二、.Binary messages not supported 

serverUrl传入ws://192.168.1.150:8444/player,其他不变,尝试连接,结果:

awrtc.js:3171 Websocket closed with code: 1003 Binary messages not supported 

原因是出在服务端的PlayerHandler的父类TextWebSocketHandler里面有

而客户端这边,则是在SendVersion那里用InternalSend发送了个Uint8Array。

因为客户端还有其他地方(心跳包)会发送Array,修改服务端,支持BinaryMessage就好了,但是也不用做什么处理,就是可以接收就行。在PlayerHandler中添加handleBinaryMessage

  @Override
  protected void handleBinaryMessage(WebSocketSession session, BinaryMessage message) {
    log.info(">> PlayerHandler.handleBinaryMessage");
    //...不做处理
  }

三、发送start指令

 SendVersion等二进制消息不影响后,服务端能够收到消息了。

原来的H5Stream是否通过call发送一个open指令过去的

var address="{\"type\": \"open\"}";//json字符串,unity传递过来的是字符串。不能用单引号。
//必须是这个格式,不然h5stream不会发送后续消息,onmessage也就进不去。
browserCall.Call(address);//=>Connect

   而从kurento的客户端(参考kurento-player-js)和服务端代码来看,是先发送一个{id:"start",videourl:...,sdpOffer:...},然后开始整个过程的。

1.使用KurentoUtils(不行)

而在kurento-player-js的index.js里面sdpOffer是通过

	webRtcPeer = new kurentoUtils.WebRtcPeer.WebRtcPeerRecvonly(options,
		function(error) {
			if (error)
				return console.error(error);
			webRtcPeer.generateOffer(onOffer);
		});

   这里的webRtcPeer.generateOffer的回调函数onOffer获取的,试着把这部分相关代码拿过来,并把获取的offerSdp组织到start指令中发送过去。

                    var mode="video and audio";
                    var video = document.getElementById('video1');
                    intWebRtcPeer(mode,video,function(sdp){
                       var msg={id:"start",videourl:rtsp,sdpOffer:sdp};
                       browserCall.Call(msg);
                    });
                function intWebRtcPeer(mode,video,callback) {
                    console.log('Creating WebRtcPeer in ' + mode + ' mode and generating local sdp offer ...');
                    // Video and audio by default
                    var userMediaConstraints = {
                        audio : true,
                        video : true
                    }
                    if (mode == 'video-only') {
                        userMediaConstraints.audio = false;
                    } else if (mode == 'audio-only') {
                        userMediaConstraints.video = false;
                    }
                    var options = {
                        remoteVideo : video,
                        mediaConstraints : userMediaConstraints,
                        onicecandidate : onIceCandidate
                    }
                    console.info('User media constraints' + userMediaConstraints);
                    var webRtcPeer = new kurentoUtils.WebRtcPeer.WebRtcPeerRecvonly(options,
                        function(error) {
                            if (error)
                                return console.error(error);
                            webRtcPeer.generateOffer(function(error, offerSdp){
                                if(!error){
                                    if(callback!=null){
                                        callback(offerSdp);
                                    }
                                }
                            });
                        });
                }

服务端收到后能够进入start处理,但是后续接不上。

进行下去会出现错误:

awrtc.js:3171 DOMException: Failed to execute 'setRemoteDescription' on 'RTCPeerConnection': Failed to set remote answer sdp: Called in wrong state: kStable

原因出在,这里进行下去的处理过程是用原来的awrtc.js的代码,但是kurento的index.js里面是用了webRtcPeer的processAnswer部分

function startResponse(message) {
	setState(I_CAN_STOP);
	console.log('SDP answer received from server. Processing ...');

	webRtcPeer.processAnswer(message.sdpAnswer, function(error) {
		if (error)
			return console.error(error);
	});
}

processAnswer内部其实就是setRemoteDescription。

generateOffer和processAnswer是对应的,generateOffer里面其实就是

pc.createOffer(createOfferOnSuccess, callback, constraints);

而awrtc.js里面也有相关的createOffer,

                        InnerAWebRtcPeer.prototype.CreateOffer = function() {
                                var rtcPeer = this;
                                console.log("InnerAWebRtcPeer.prototype.CreateOffer",this.mOfferOptions);
                                var offerPro = this.mPeer.createOffer(this.mOfferOptions);
                                offerPro.then(function(answer) {
                                    var json = JSON.stringify(answer),
                                        promise = rtcPeer.mPeer.setLocalDescription(answer);
                                    promise.then(function() {
                                        rtcPeer.RtcSetSignalingStarted();
                                        rtcPeer.EnqueueOutgoing(json);
                                    }),
                                        promise.
                                        catch(function(error) {
                                            Debug.LogError(error),
                                                rtcPeer.RtcSetSignalingFailed()
                                        })
                                }),
                                    offerPro.
                                    catch(function(t) {
                                        Debug.LogError(t),
                                            rtcPeer.RtcSetSignalingFailed()
                                    })
                            },

也就是说应该想办法从这里开始的。

2.使用awrtc的StartSignaling(可行)

CreateOffer是StartSignaling调用的,StartSignaling是UpdateSignalingNetwork里面判断的NewConnection分支中调用的

                                else if (netEvent.Type == NetEventType.NewConnection) {
                                    console.error("InnerWebRtcNetwork.prototype.UpdateSignalingNetwork",netEvent,this.mInSignaling);
                                    var t=this.mInSignaling[netEvent.ConnectionId.id];
                                    if(t){
                                        t.StartSignaling();
                                    }
                                    else{
                                        this.AddIncomingConnection(netEvent.ConnectionId);
                                    }
                                }
UpdateSignalingNetwork其实是处理OnWebsocketOnMessage中的消息的。

以前连接H5Stream是发送一条{type:open}指令给服务器,然后服务器返回信息,然后开始的。

按照这个思路,经过模式,修改后过程如下。

客户端发送一个{id:connect}指令

                    var address={id:"connect"};//对象
                    browserCall.Call(address);//=>Connect

服务端handleTextMessage里面处理connect指令,返回一个结果:

    try {
      switch (id) {
        case "connect"://[awrtc]
          sendMessage(session,"{\"type\":6,\"id\":1,\"data\":\"1\"}");
          break;

这里的type:6就是上面的NetEventType.NewConnection。

然后客户端在OnWebsocketOnMessage中增加一条路线处理。

                        InnerWebsocketNetwork.prototype.OnWebsocketOnMessage = function(e) {
                            console.log(">>>>>>> OnWebsocketOnMessage",e);
                            if (this.mStatus != WebsocketConnectionStatus.Disconnecting && this.mStatus != WebsocketConnectionStatus.NotConnected) {
                                if(e.data instanceof ArrayBuffer){ //原来的路线
                                    var t = new Uint8Array(e.data);
                                    this.ParseMessage(t)
                                }
                                else{ //新增的路线,处理h5stream或者kurento的webrtc的信息
                                    var dataObj = JSON.parse(e.data);
                                    console.log(">>>>>>> OnWebsocketOnMessage [H5Stream]",dataObj);
                                    if(dataObj.type && dataObj.id){ //[kurento] 服务端是基于kurento-java修改的
                                        console.log(">>>>>>> OnWebsocketOnMessage [NEW NetworkEvent]",dataObj);
                                        var evnt=new NetworkEvent(dataObj.type,{id:dataObj.id},dataObj.data);
                                        this.HandleIncomingEvent(evnt);
                                    }
                                    else{ //[H5Stream]
                                        //将offer和remoteice作为NetEventType.ReliableMessageReceived信息处理.
                                        var evnt=new NetworkEvent(NetEventType.ReliableMessageReceived,this.mLastConnectionId,e.data);
                                        //在InnerWebsocketNetwork.prototype.Connect时记录下this.mLastConnectionId.
                                        this.HandleIncomingEvent(evnt);
                                    }
                                }
                            }
                        },

这里的 var evnt=new NetworkEvent(dataObj.type,{id:dataObj.id},dataObj.data); 处理好后,就会进入StartSignaling,然后CreateOffer。 

不过这里的把sdp发送给服务端部分(SendNetworkEvent),需要修改一下,添加id属性,不然无法和服务端处理对接起来。

                    InnerWebsocketNetwork.prototype.SendNetworkEvent = function(networkEvent) {
                            /*
                            InnerWebsocketNetwork.SendNetworkEvent	@	awrtc.js:4300
                            InnerWebsocketNetwork.HandleOutgoingEvents	@	awrtc.js:4280
                            InnerWebsocketNetwork.Flush	@	awrtc.js:4343
                            InnerWebRtcNetwork.Flush	@	awrtc.js:3846
                            InnerBrowserMediaNetwork.Flush	@	awrtc.js:5982
                            InnerAWebRtcCall.Update
                             */
                            console.log(">>>>>>> SendNetworkEvent",networkEvent);
                            //原来的代码
                            // var t = NetworkEvent.toByteArray(networkEvent);
                            // this.InternalSend(t);

                            //新的代码[H5Stream]
                            if(networkEvent.data instanceof Array){
                                var t = NetworkEvent.toByteArray(networkEvent);
                                this.InternalSend(t);//原来的分支1:发送NetworkEvent
                            }
                            else if(typeof networkEvent.data == 'string'){
                                try {
                                    var obj=JSON.parse(networkEvent.data);//不是json格式的会抛出异常
                                    if(obj==null){
                                        var t = NetworkEvent.toByteArray(networkEvent);
                                        this.InternalSend(t);//原来的分支2:发送NetworkEvent
                                    }else{
                                        //新的分支1:发送data里面的内容的json字符串。对应于"{"type":"open"}"的发送。h5stream必须先发送open指令才能收到onmessage消息
                                        this.SendObject(obj);
                                    }
                                }
                                catch (ex) {
                                    console.error(ex);
                                    var t = NetworkEvent.toByteArray(networkEvent);
                                    this.InternalSend(t);//原来的分支2:发送NetworkEvent
                                }
                            }
                            else//[H5Stream]或者[kurento]
                            {
                                this.SendObject(networkEvent.data);//新的分支2:发送data里面的内容的json字符串。对应于answer的发送
                            }
                        },
                    InnerWebsocketNetwork.prototype.SendObject = function(data) {
                            if(data==null){
                                console.error("InnerWebsocketNetwork.prototype.SendObject data==null");
                                return;
                            }
                            if(!data.id){//[kurento]添加上id
                                if(data.candidate){ 
                                    console.log("onIceCandidate",data);
                                    data.id="onIceCandidate";
                                    var temp=JSON.stringify(data);
                                    data.candidate=JSON.parse(temp);//不能直接用 data.candidate=data,会导致JSON序列化出错的,死循环吧。
                                }
                                else if(data.sdp){
                                    console.log("start",data);
                                    data.id="start";
                                    data.videourl="rtsp://iom:[email protected]:554/cam/realmonitor?channel=1&subtype=0";
                                    //data.sdpOffer=data.sdp;
                                }
                                console.info("kurento data",data);
                            }
                            
                            var json=JSON.stringify(data);
                            console.log("send json",json,data);
                            this.mSocket.send(json);
                        },

这里还留下一个问题,videourl如何传到这里,测试整个过程先写死了。

这里的两个data.id设置对应于服务端的start和onIceCandidate处理。

四、处理接收指令

而服务端处理后发送过来的消息又在HandleIncomingSignaling里面处理,把index.js里面的代码抄过来,改一下startResponse的部分,对接上原来的 this.CreateAnswer(description) : this.RecAnswer(description) 部分。

                InnerAWebRtcPeer.prototype.HandleIncomingSignaling = function() {
                                /*
                                InnerAWebRtcPeer.HandleIncomingSignaling	@	awrtc.js:3464
                                InnerAWebRtcPeer.Update	@	awrtc.js:3457
                                InnerMediaPeer.Update	@	awrtc.js:5694
                                InnerWebRtcNetwork.CheckSignalingState	@	awrtc.js:3878
                                InnerWebRtcNetwork.Update	@	awrtc.js:3823
                                InnerBrowserMediaNetwork.Update	@	awrtc.js:5942
                                InnerAWebRtcCall.Update
                                    */
                                for (; this.mIncomingSignalingQueue.Count() > 0;) {
                                    var data = this.mIncomingSignalingQueue.Dequeue();
                                    console.info('Received message: ' + data);
                                    t = Helper.tryParseInt(data);
                                    if (null != t) {
                                        console.error("InnerWebRtcNetwork.prototype.HandleIncomingSignaling",data,t);
                                        this.mDidSendRandomNumber && (t < this.mRandomNumerSent ? (SLog.L("Signaling negotiation complete. Starting signaling."), this.StartSignaling()) : t == this.mRandomNumerSent ? this.NegotiateSignaling() : SLog.L("Signaling negotiation complete. Waiting for signaling."));
                                    }
                                    else {
                                        var parsedMessage = JSON.parse(data);

                                        if(parsedMessage.id){ //[kurento]
                                            switch (parsedMessage.id) {
                                                case 'startResponse':
                                                    //startResponse(parsedMessage);
                                                    var answer={
                                                        type: 'answer',
                                                        sdp: parsedMessage.sdpAnswer
                                                    }
                                                    //console.error(">>>>>>>>>  parsedMessage.sdpAnswer",answer);
                                                    var description = new RTCSessionDescription(answer);
                                                    console.error(">>>>>>>>>  startResponse Answer",description);
                                                    "offer" == description.type ? this.CreateAnswer(description) : this.RecAnswer(description);

                                                    //this.CreateAnswer(description);
                                                    break;
                                                case 'error':
                                                    if (state == I_AM_STARTING) {
                                                        setState(I_CAN_START);
                                                    }
                                                    onError('Error message from server: ' + parsedMessage.message);
                                                    break;
                                                case 'playEnd':
                                                    playEnd();
                                                    break;
                                                case 'videoInfo':
                                                    //showVideoData(parsedMessage);
                                                    // {"id":"videoInfo","isSeekable":false,"initSeekable":0,"endSeekable":0,"videoDuration":0}
                                                    break;
                                                case 'iceCandidate':
                                                    //这部分不处理也可以
                                                    // console.log(">>>>>>>>>  parsedMessage",parsedMessage,parsedMessage.candidate);
                                                    // var candidate = new RTCIceCandidate(parsedMessage.candidate);
                                                    // console.log(">>>>>>>>>  candidate",candidate);
                                                    // if (null != candidate) {
                                                    //     var pro = this.mPeer.addIceCandidate(candidate);
                                                    //     pro.then(function() {}),
                                                    //         pro.
                                                    //         catch(function(error) {
                                                    //             Debug.LogError(error)
                                                    //         })
                                                    // }
                                                    break;
                                                case 'seek':
                                                    console.log (parsedMessage.message);
                                                    break;
                                                case 'position':
                                                    document.getElementById("videoPosition").value = parsedMessage.position;
                                                    break;
                                                case 'iceCandidate':
                                                    break;
                                                default:
                                                    if (state == I_AM_STARTING) {
                                                        setState(I_CAN_START);
                                                    }
                                                    onError('Unrecognized message', parsedMessage);
                                            }
                                        }
                                        else{ //原来的
                                            var answer = parsedMessage;
                                            console.log(">>>>>>>>>  InnerAWebRtcPeer.prototype.HandleIncomingSignaling",answer);
                                            if (answer.sdp) {
                                                var description = new RTCSessionDescription(answer);
                                                console.log(">>>>>>>>>  Answer",description);
                                                "offer" == description.type ? this.CreateAnswer(description) : this.RecAnswer(description)
                                            } else {
                                                var candidate = new RTCIceCandidate(answer);
                                                console.log(">>>>>>>>>  addIceCandidate",candidate);
                                                if (null != candidate) {
                                                    var pro = this.mPeer.addIceCandidate(candidate);
                                                    pro.then(function() {}),
                                                        pro.
                                                        catch(function(error) {
                                                            Debug.LogError(error)
                                                        })
                                                }
                                            }
                                        }
                                    }
                                }
                            },

然后................................就好了,可以播放视频了

其实我对整个webrtc视频连接的过程的理解是懵懵懂懂的,大概知道几个关键步骤,具体细节的话有些地方还是不懂,如果自己写一个原型代码的话会更加深理解。

//todo:写个js连接websocket的原型代码

这个修改过程相当于把两个有一定兼容性的机器连接起来,这里的连接的依据就是双方是基于webrtc的规则来的。把客户端(awrtc)和服务端(kurento-player)的相关处理过程修改一下,把两者接起来。

五、暂停等指令接口

服务端提供了几个控制接口

其实就是发送响应的id,客户端这边封装一下

                        InnerBrowserWebRtcCall.prototype.SendObject = function(obj) {
                                console.log(">>>>>>>>>>>>>>>>>> BrowserWebRtcCall.SendObject",obj);
                                this.mNetwork.mSignalingNetwork.SendObject(obj);
                            },
                            InnerBrowserWebRtcCall.prototype.Stop = function() {//[kurento]
                                this.SendObject({id:"stop"});
                                this.DisposeInternal();
                            },
                            InnerBrowserWebRtcCall.prototype.Resume = function() {//[kurento]
                                this.SendObject({id:"resume"});
                            },
                            InnerBrowserWebRtcCall.prototype.Pause = function() {//[kurento]
                                this.SendObject({id:"pause"});
                            },
        $('#btnStop').click(function(){
             call.Stop();
         });
         $('#btnPause').click(function(){
             call.Pause();
         });
         $('#btnResume').click(function(){
             call.Resume();
         });

六、Unity播放

前面的js代码的修改,都是为了放到unity里面。

把awrtc.js代码拷贝到awrtc.jspre,在awrtc_unity.jslib里面加上想要的接口

	 Unity_Kurento_GetVideo: function(a,b)
	 {
		 var serverUrl=Pointer_stringify(a);
		 var videoUrl=Pointer_stringify(b);
		 console.log("------- Unity_Kurento_GetVideo",serverUrl,videoUrl);
		 return awrtc.CAPI_Kurento_GetVideo(serverUrl,videoUrl);
	 },

Unity里面则是

        [DllImport("__Internal")]
        public static extern InitState Unity_Kurento_GetVideo(string serverUrl,string videoUrl);

前面加了个VideoUrl参数,Unity里面需要相应调整。

        public class NetworkConfigEx : NetworkConfig
        {
            public string VideoUrl { get; set; }
        }

WebRtcVideo.CreateNetworkConfig:

        NetworkConfigEx netConfig = new NetworkConfigEx();
        if (string.IsNullOrEmpty(uIceServer) == false)
            netConfig.IceServers.Add(new IceServer(uIceServer, uIceServerUser, uIceServerPassword));
        if (string.IsNullOrEmpty(uIceServer2) == false)
            netConfig.IceServers.Add(new IceServer(uIceServer2));

        uSignalingUrl = mUi.InputUrl.text;
        videoUrl = mUi.VideoUrls.options[mUi.VideoUrls.value].text;

        Debug.Log("uSignalingUrl:"+ uSignalingUrl);
        Debug.Log("videoUrl:" + videoUrl);

        netConfig.SignalingUrl = uSignalingUrl;
        netConfig.VideoUrl = videoUrl;

BrowserMediaNetwork:

            string conf = "{\"IceServers\":" + iceServersJson.ToString() + ", \"SignalingUrl\":\"" + signalingUrl + "\", \"IsConference\":\"" + false + "\"}";
            if (lNetConfig is NetworkConfigEx)
            {
                NetworkConfigEx ex = lNetConfig as NetworkConfigEx;
                conf = "{\"IceServers\":" + iceServersJson.ToString() + 
                       ", \"SignalingUrl\":\"" + signalingUrl +
                       "\", \"VideoUrl\":\"" + ex.VideoUrl + 
                       "\", \"IsConference\":\"" + false + "\"}";
            }
            SLog.L("Creating BrowserMediaNetwork config: " + conf, this.GetType().Name);
            mReference = CAPI.Unity_MediaNetwork_Create(conf);

界面上再加上一个videoUrl的输入框,打包webgl测试,可以播放。

七、Unity帧率问题(视频分辨率)

发现播放一会,帧率就变成了2-4,同时在打印信息中发现视频分辨率有几次变化,最后变成1920*1080了。

公司有两个摄像头一个是1920*1080,一个是1280*720,低分辨率播放视频时的帧率在45-50,可能是分辨率问题。

另外发现,js里面的核心代码context.drawImage()实际上是可以修改分辨率的,参考:前端JS利用canvas的drawImage()对图片进行压缩

原本是mVideoElement.videoWidth的,改成用mVideoElement.width,使用video的大小来获取图片。

                            InnerBrowserMediaStream.prototype.CreateFrame = function() {
                                // console.log("InnerBrowserMediaStream.prototype.CreateFrame",
                                //     this.mVideoElement.videoWidth,this.mVideoElement.videoHeight,
                                //     this.mVideoElement.width,this.mVideoElement.height);
                                //var width=this.mVideoElement.videoWidth;
                                //var height=this.mVideoElement.videoHeight;
                                var width=this.mVideoElement.width;
                                var height=this.mVideoElement.height;
                                this.mCanvasElement.width = width;
                                this.mCanvasElement.height = height;
                                var context = this.mCanvasElement.getContext("2d");
                                context.clearRect(0, 0, this.mCanvasElement.width, this.mCanvasElement.height);
                                context.drawImage(this.mVideoElement, 0, 0,width,height);
                                try {
                                    var data = context.getImageData(0, 0, this.mCanvasElement.width, this.mCanvasElement.height).data,
                                        array = new Uint8Array(data.buffer);
                                    return new RawFrame(array, this.mCanvasElement.width, this.mCanvasElement.height)
                                } catch(error) { (array = new Uint8Array(this.mCanvasElement.width * this.mCanvasElement.height * 4)).fill(255, 0, array.length - 1);
                                    var frame = new RawFrame(array, this.mCanvasElement.width, this.mCanvasElement.height);
                                    return SLog.LogWarning("Firefox workaround: Refused access to the remote video buffer. Retrying next frame..."),
                                        this.DestroyCanvas(),
                                        this.mCanvasElement = this.SetupCanvas(),
                                        frame
                                }
                            },

在传入配置信息中,加上分辨率的设置。

             var config={
                 SignalingUrl:"ws://192.168.1.150:8444/player",
                 VideoUrl:"rtsp://iom:[email protected]:554/cam/realmonitor?channel=1&subtype=0",
                 VideoWidth:tmp[0],
                 VideoHeight:tmp[1]
             };
             console.log('config',config);
             call=awrtc.CAPI_Kurento_GetVideo(JSON.stringify(config));
                function func_CAPI_Kurento_GetVideo(configJson) {//[kurento]
                    console.log("func_CAPI_Kurento_GetVideo",configJson);
                    var config=JSON.parse(configJson);
                    console.log("config",config);
                    BrowserMediaStream.DEBUG_SHOW_ELEMENTS=true;//在网页中显示视频
                    AWebRtcPeer.SourceType="kurento";//原本用这个区分代码的
                    var browserCall=new BrowserWebRtcCall(config);

然后就可以在播放时设置分辨率了。

--------------------------------------------------------------------

测试了几种分辨率,发现改成4:3的分辨率时获取到的图片和Video里面的视频不一样,拉伸了一些,16:9的则是一样的,这里可能需要处理一下。

普屏4:3 320*240 640*480
宽屏16:9 480*272 640*360 672*378 720*480 1024*600 1280*720 1920*1080

 

现在相当于默认都要拉伸的,以后需要的话可以增加不同的方式。

--------------------------------------------------------------------

关键的Unity测试结果,从结论上讲,确实是受分辨率影响的,在1280*720以下的分辨率的帧率还能接受(30-60),再清晰一些则帧率下降到1-10了,不能接受。但是这里有个前提是这里的刷新时用FixedUpdate,0.02s一次。改成用InvokeRepeating的方式,0.1s一次,则就算是1920*1080的分辨率也播放,帧率20-40,还能接受。而1920*1080,0.2s一次,则可以达到40-50。

总之不能用FixedUpdata,没必要,看监控视频不是玩游戏,不需要0.02s刷新一次。0.1-0.2就可以了。

实际上应该根据UI界面的大小和视频本身分辨率,界面大小小于视频分辨率的话,使用界面的分辨率,大于的话使用视频本身的分辨率。

根据界面UI大小设置视频分辨率:

if (Config.resolution == "AutoUI")
        {
            //Config.resolution = mUi.uNoCameraTexture.width + "*" + mUi.uNoCameraTexture.height;
            RectTransform rectT = GetComponent<RectTransform>();
            var rect = rectT.rect;
            //ShowHtmlElement.ShowElement(rect,"video1");
            Config.resolution = (int)rect.width + "*" + (int)rect.height;
            Debug.Log("AutoUI:" + Config.resolution);
        }

-------------------------------------------------------------------

八、播放多个摄像头视频

整理了一下代码,原来的代码里面UI和控制代码在一起的,实际上UI部分只有一个RawImage是必要的。

直接将代码整理一下,和RawImage一起做成一个预设。再复制一下,修改复制后的摄像头地址。

打包webgl,测试。可以。

当然帧率还是受分辨率影响。

 

九、显示Video(并设置位置)

前面的在Unity里面播放WebRtc的方式的核心是,用一个隐藏的(Html5的)Video来播放WebRtc,通过和js交互,获取Video的图片,并不断刷新。相比于直接只是Video播放多了获取和显示在Unity中的两步,一定程度上会影响Unity内的帧率,还有视频的质量。不过这种的优点是效果上和Unity完美结合,通过Material还能再三维物体表面播放视频。

但是对于不需要再三维物体上显示的需求来说,在Unity里面也只是在一个界面上显示视频而已,直接可以把Video显示出来,并调整位置,放到Unity前面,达到看起来和Unity界面重叠一致的效果。

相关参考资料:UnityWebGL调研(7) 修改打包模板UnityWebGL调研(5) 和网页交互

 

 

 

十、js核心原型代码

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章