視頻服務器(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核心原型代碼

 

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