Windows远程桌面实现之十三:浏览器客户端使用WebRTC传输,以及WebRTC和MSE渲染显示

Posted 雨中风华

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Windows远程桌面实现之十三:浏览器客户端使用WebRTC传输,以及WebRTC和MSE渲染显示相关的知识,希望对你有一定的参考价值。

                               by fanxiushu 2022-01-17 转载或引用请注明原始作者。
在前面阐述windows远程桌面实现的一系列文中,其实主要阐述的内容是如何采集桌面图像和电脑声音为主,
包括windows下的各种采集方式,linux平台,macOS平台,ios平台,
所以基本上70%-80%的内容,都是跟”采集“相关的, 而对网络传输,讲述的内容比较少,
也就是在第六章,提到xdisp_virt的新版本框架的时候,提到了网络通信框架。
Windows远程桌面实现之六(新版本框架更新,以及网页HTML5音频采集通讯)_fanxiushu的专栏-CSDN博客_网页版windows远程桌面

不讲述网络通信框架,并不代表不重要。
其实从第六章的简单描述中,也可看到xdisp_virt程序里实现的网络通信框架是挺复杂的。
它全是基于TCP底层的连接通讯,使用11000默认端口,在此单一端口中可以同时处理普通http请求,
WebSocket请求,原生客户端程序请求,还包括SSL加密传输,单一TCP连接中传输各种类型数据包, 等等。
xdisp_virt发布到GITHUB上的程序,并没包括原生客户端程序,而是以浏览器作为客户端,
浏览器客户端实现的原理是:通过WebSocket接收各种数据包,包括图像和声音,
然后到浏览器端使用编译成wasm的ffmpeg解码,再然后使用WebGL渲染。
这种方式一直工作的很好,不过也有缺点:
1,就是浏览器js解码和WebGL渲染,实在太浪费资源,如果远程桌面不是经常运行视频和游戏,
      或者分辨率要求比较低(1080P或以下,对4K,8K没啥要求)
      FPS要求不高(只要求30FPS或以下,对60FPS以上没啥要求)
      或者浏览器的机器配置比较好,能轻松在浏览器中运行4K,60FPS的图像,
      这到无所谓。
2,xdisp_virt程序还附带一个xdisp_server程序,这个程序名叫中转服务器,顾名思义,中转服务器就真的是做中转,
     它中转连接上来的所有xdisp_virt程序,所有的数据包都通过它中转,通常把xdisp_server运行到公网,这样让内网的
      xdisp_virt能被访问。这就要求server需要很高的带宽,否则当xdisp_virt多起来就很卡。
      最好的办法就是采用P2P,尽量减少从中转服务器传输图像数据。
     
说到P2P传输,也不是新鲜玩意,已经存在很长时间了。
十多年前,就涌现了大批的P2P程序,比如有代表性的,bt,emule等等。
P2P, peer to peer, 就是点对点传输,传输协议不限,使用TCP,UDP,或者其他自个能想到的都成。
但是实际中以UDP协议居多。
实现目标就是处于网络世界中的普通两个节点之间能真正互相传输数据,
现实是: 这两个普通节点处于复杂的网络环境中,可能处于防火墙之后,NAT之后,各种代理之后,等等。
他们之间要互相通信,还真不是一件容易的事情。

但是我们能想到一个非常通用的解决办法:
我们在复杂环境中,首先尝试直接传输数据包,
如果不成功,那就以一个服务器为桥梁,先朝服务器传输包,
告知各自的可能的能够通信的地址(外网地址),然后再朝这个可能能通信的地址传输包·,这个通常称为”打洞“,
如果还不成功,那我们干脆就把这个服务器作为中转服务器,转发数据包。

虽然最坏的情况下,还是以服务器中转数据包的方式,但是不管怎样,通过这种步骤和思路,
也算是建立起来两个节点的互相通信。

很多软件都是基于以上思路实现的,比如聊天类软件, 视频会议类软件,  
比如teamviewer远程桌面软件,等等,几乎好像成了一个通用的处理规则。
    
既然那么通用,就没有标准化吗?
其实早就标准化了,比如STUN/TURN协议,ICE框架等等。

而今天要阐述的WebRTC,就是这些的集大成者。
它包含了,STUN/TURN,ICE,SRTP,DTLS,SCTP等等这些内容,
其中STUN/TURN,ICE用于建立P2P传输通道,SRTP用于音视频多媒体传输通道,
DTLS/SCTP用于数据传输通道。
以上只是列举了WebRTC包含的通信相关的一些协议。
它是google在2010年开始的一个项目,直到现在,几乎所有的浏览器都集成了WebRTC。
本文只关心WebRTC的通讯部分,也就是 RTCPeerConnection 和 RTCDataChannel,
不包括MediaAPI等媒体接口。

xdisp_virt软件开始于2017年,当时WebRTC应该已经成熟了啊,至于当初为何没有想到WebRTC,我自己也不清楚。
而正如在这系列文章第四章阐述的那样,在考虑浏览器显示远程桌面的时候,
Windows远程桌面实现之四(在现代浏览器中通过普通页面访问远程桌面)_fanxiushu的专栏-CSDN博客_浏览器访问远程桌面
在考虑图像渲染部分的时候,当时也考虑了很久,就是找不到什么好办法来非常实时的渲染图像,
然后才想到js软解码,WebGL渲染。
就是现在在考虑WebRTC之前,也只是先考虑有没有可能在浏览器中使用UDP传输,
我是想着不通过中转服务器,直接浏览器传输,
起因是购买的一个云服务器带宽实在太低,只有1M带宽,就想着浏览器通过UDP的P2P传输。
然后查找资料显示不大可能直接使用UDP的时候,然后WebRTC才进入我的眼帘。

本来不是做javascript这块的,现在逼的来都快成javascript专家了。
渲染图像的问题,其实还是跟原来一样的争论:
究竟是使用 html5的 video 标签渲染,还是使用WebGL渲染。
使用WebGL渲染,就意味着需要js解码,解码之后还得提交YUV图像数据给WebGL,这中间浪费的资源是比较高的。
如果使用video标签渲染,那解码和渲染都是浏览器自己处理,效率非常高,就跟原生程序一样的了。
当初考虑浏览器远程桌面渲染的时候,不是没考虑过video标签渲染,比如考虑HLS就是video标签渲染,只是HLS延时太高,
奈何video标签对实时流媒体的处理太弱了。
不过当初应该是没考虑到 WebRTC和MSE(Media Source Extension)的情况。
WebRTC是浏览器内部,在接收到媒体通道的RTP数据包的时候,直接交给video解码渲染了。
MSE则是另外一种技术,它把接收到音视频数据包,打包成 fMP4 格式,然后喂给video标签去渲染。
其目的都是把解码和显示,直接交给浏览器去处理。
目前WebRTC测试过,实时性比较好,
(某些时候比js解码WebGL显示实时性差些,也可能是某些参数没做调优,也可能WebRTC本来就这德性)
MSE目前还没去具体测试,
最好是做到每一帧都可以封装成 一个单独的fMP4,然后提供给video立马解码显示,
否则实时性也很糟糕而不能采用到远程桌面上。

我们先来看看javascript里实现WebRTC的代码例子:
是从正在开发xdisp_virt 的WebRTC功能中提取的,因为网页端是被动接收端,
在xdisp_virt端提供Offer SDP,js端接收OfferSDP,然后创建WebRTC并回复AnswerSDP以及ICE candidate。

function logError(text)
    console.error('Except: '+text);
    alert('Create WebRTC Error: '+text)

function create_webrtc_peer_connection(offer_sdp)

    if (pc != null)
        console.log('*** close last webrtc peer...');
        pc.close();
        pc = null;
   
 //   ice_svr_addr = iceServers: [ urls: 'stun:stun.l.google.com:19302' ]
    /
    pc = new RTCPeerConnection(ice_svr_addr);

    pc.setRemoteDescription(
        new RTCSessionDescription(JSON.parse(offer_sdp))).then(() =>
            ///create answer
            pc.createAnswer().then(desc=>
                console.log('desc=' + JSON.stringify(desc));

                /// send answer to server
                send_answer_sdp(desc);

                set local desc
                pc.setLocalDescription(desc).catch(logError);

            ).catch(logError);

            ///
        ).catch(logError);


    /// event
    pc.signalingstatechange = () =>
        console.log('Signaling state:', pc.signalingState);
    ;
    pc.oniceconnectionstatechange = () =>
        console.log('ICE connection state:', pc.iceConnectionState);
    ;
    pc.onicegatheringstatechange = () =>
        console.log('ICE gathering state:', pc.iceGatheringState);
    ;
    pc.onconnectionstatechange = () =>
        console.log('Connection state:', pc.connectionState);
    ;
    pc.onicecandidate = (event) =>
        console.log('ice candidate=' + JSON.stringify(event.candidate));
        ///
        if (event.candidate) send_ice_candidate(event.candidate);
    ;
    pc.onicecandidateerror = (event) =>
        console.error('ICE candidate error:', event);
    ;

    pc.ontrack = function (e)
        //console.log('---- pc.ontrack...');
        
        if (e.track.kind === "audio")
            console.log('++++ audio track');
       
        else if (e.track.kind === "video")
            console.log('+++ video track...');
            document.getElementById("video").srcObject = e.streams[0];
       
   

    pc.ondatachannel = function (ev)
        console.log('Data channel is created!');
        ev.channel.onopen = function ()
            console.log('Data channel is open and ready to be used.');
       
   


其中里边的 send_ice_candidate, send_answer_sdp函数,是把ice candidate,answer sdp发送给 xdisp_virt,
这里是使用websocket发送的,至于如何发送,没有什么规定,只要数据到达对方就行了。
js中使用webrtc其实是很简单的,首先 new RTCPeerConnection对象,如果是 提供OfferSDP的一端,
则调用 CreateOffer函数创建 OfferSDP,然后把OfferSDP通过某种方式传输给对端,
然后调用SetLocalDescription设置OfferSDP,调用此函数后浏览器内部就开始收集本地网卡信息,如果设置ICEServer,
也会获取自己的外网地址等信息或者中转服务器信息,这些信息都通过 onicecandidateerror  回调函数提供,
然后我们在这个回调函数中再把ice candidate发送给对端。
同时,对端也会回答 AnswerSDP,我们调用 SetRemoteDescription保存AnswerSDP,
对端同时也会发来它的IceCandidate,我们调用AddIceCandidate保存ICE。
两端根据对方的ICE信息,开始互相连通尝试,最终建立起连接,或者失败。

如果熟悉UDP的P2P传输,或者做个这方面的东西,就很好理解上面的过程了。
如果熟悉RTSP流协议(注意RTSP和SRTP是两个不同的协议,SRTP是加密版本的RTP ),
也很好理解SDP的格式,RTSP同样是使用RTP协议传输音视频数据的。在RTP协议外层在加一层加密就是SRTP了。
所以说WebRTC是各种相关通信协议的集合体。

如果是被动接收,则如上面代码所示。
当对方开启了音视频通信信道,浏览器端就会接收到 ontrack消息,如上面的代码。
如果是数据信道,则会接收到ondatachannel

所以在javascript使用webrtc是非常简洁的,甚至可以说是非常简单的。

然而,世界真这么简单就好了。
上面阐述的是javascript端,然而到了 xdisp_virt程序端如何处理WebRTC,
xdisp_virt不是js脚本,是真正的程序,C/C++语言开发的,如果自己去开发WebRTC,则是非常耗时。
虽然上面所说的P2P原理也就那样,不算复杂,但是真正实现起来则是另外一回事,
牵涉到的通讯协议太多,具体细节也很多,因此最好的办法,还是去找别人开发好的webRTC开源库。

最先想到的自然是google的webrtc源代码,可是那个代码体积之大,编译和集成复杂,
而且还需要C++17规范,我TNN的还在使用VS2015,顶多支持C++14规范,
同时在较老的linux系统以及嵌入式系统中编译,都是非常不利的。
经过一番折腾之后,放弃。
转而去寻找其他开源的,后来找到rawrtc,libdatachannel等,都只是集成了DataChannel,也就是数据通道,
如果客户端不是浏览器,而是自己的原生程序,使用数据通道就可以了,可偏偏是要解决 上面说的 video 图像渲染问题。
再说了,如果真的是自己的原生程序,我也就不大会去支持webrtc标准的P2P通讯,或者尽量简化,没必要搞得这么麻烦。
再然后找到 gstreamer, 1.14以上集成WebRTC功能,于是开始编译调试,也花了很多天时间,
总算是成功编译集成了,可是效果却非常糟糕,经常卡顿,延时非常高,
不知道是我改的太厉害了,还是gstreamer本身就做的不好,最后没办法放弃。
也打算放弃WebRTC了。
直到找到亚马逊的 kvswebrtc,把它集成进去之后,效果比较理想,才看到了希望。

下面具体描述上面两个失败的经历。
一, 首先是google的webrtc开源库,按照google官网的说法,编译步骤是比较简单的。
      1, 下载depot_tools ,然后把depot_tools路径加入PATH环境变量中,
              git clone https://chromium.googlesource.com/chromium/tools/depot_tools.git
       2,上面的depot_tools开发工具下载好并且路径加入PATH之后,下载webrtc源码,使用如下命令:
             mkdir webrtc-checkout 
             cd webrtc-checkout        
             fetch --nohooks webrtc  
             gclient sync     
             这样所有相关源码都下载和同步到webrtc-checkout目录中了。
        3,编译,
             gn gen out/default , 生成gn编译文件
             ninja -C out/default, 编译

       以上步骤是不是很简单,真的简单?
       首先,这是在中国,google网站基本是被屏蔽的,所以第一步源代码就无法同步下来。
       也许可以开个代理,然后代理去下载,但是问题是,这个源代码不是一般的大,是非常大,
       代理会非常不稳定,git下载万一中途断线,又的重新再来,谁能扛得住。
       10多个G,就为了实现一个WebRTC,而且也只需要里边的通讯部分,真是不值。。。

       目前比较好的下载编译办法,就是直接去购买一个国外的云服务器,香港的,美国的都可以。
       然后在云服务器上安装开发环境,拉取相关代码,然后编译等等一系列操作,
       然后再把编译结果传到本地来集成。
       我是把webrtc源代码拉取到云服务器中,然后再打包,打包之后都有10G,然后再想办法从云服务器上传到我本地电脑上。
       因为云服务器到我本地的带宽只有2M,所以基本上花了一天时间才传输完成
     (当然,得开启断点传输,否则中途断线那才叫欲哭无泪)。
       传输到本地后,打算编译,然后神奇的发现,最新版本的webrtc源码必须要安装vs2019,才能成功编译。
       本来对10多个G的源码就已经很不耐烦了。
       还必须安装vs2019,再后来又发现WebRTC源码中使用了C++17的规范的语法。
       真的是无语了,于是果断放弃。

       并不是排斥c++17规范,是因为我得考虑跨平台,甚至嵌入式平台,以及老系统(比如很早前的linux,winxp等)
       C++最近发展的有点猛了,越来越不认识了。本来C++的语法就很复杂,现在添加的更复杂。
       希望别朝作死的路上走!   

       本来我打算在xdisp_virt程序中集成WebRTC,就只需要它的网络通信部分,我只需要把已经编码好的音视频数据
       通过WebRTC进行传输,从而让浏览器能正常识别和使用而已。
       再怎么说也用不着这么庞大的东西。

二,放弃之后,开始寻找其他开源代码,最不济只实现数据通道好了,至少可以解决浏览器的P2P传输问题。
       所以先后找到rawrtc,libdatachannel等开源库,都是只有数据通道的,其中libdatachannel开源也是C++17规范,
       我的目的是小巧点的,能跨多个平台,甚至是比较老的平台下,也能正常编译和使用的。
       因此使用纯C语言的开源库是主要目标。   
       再然后居然发现gstreamer  也实现了 WebRTC的功能。
       在上一篇文中,讲述ONVIF的时候,打算替换我自己实现的RTSP服务端,也找到了gstreamer里边集成的RTSP服务端功能,
       当时觉得为了一个RTSP就使用gstreamer,等于牛刀杀鸡,所以才采用了live555.
       虽然gstreamer也比较大,但是比起google的webrtc小巧多了。
       只是编译有点麻烦,尤其是windows平台,gstreamer因为需要使用glib库,
       这是linux下的东西,在windows中天生就显得水土不服。
       在windows中先后编译了 1.16和1.18两个版本,然后把gstreamer中的webrtc集成到xdisp_virt程序中,
       结果是非常不理想,延迟很大不说,还经常卡死,就是浏览器上的图像运行几十秒或者一两分钟,就停止不动了,
       但是网络依然还在传输数据,然后过一会才会接着运动起来,不知道什么原因。这种效果的WebRTC有何用啊。
       
        首先来说说如何编译gstreamer吧,毕竟gstreamer和ffmpeg一样,都是多媒体行业中的重要框架。
        但是ffmpeg比起gstreamer更加容易编译,代码看起来也更加整洁和有规律。
        尤其是windows平台下的编译,ffmpeg就安装 MINGW环境稍微麻烦点,MINGW环境装好之后,
        做好相关配置,就可以直接编译ffmpeg了,可以说还是很容易的。
       
        gstreamer还得一边下载一边配置,并不是讨厌这种做法,
        而是这种做法很容易因为某个下载链接失效,或者屏蔽无法访问,而造成编译中断,然后还得一个一个的修改调整。
        一旦这种配置多起来,那种修改就非常痛苦。
        首先编译的是1.18版本,按照gstreamer官网的编译办法,首先下载安装python3,
        然后使用pip3 安装 meson和ninja,然后
        GStreamer / gst-build · GitLab
        再然后在gst-build目录中执行./gst-worktree.py add gst-build-1.18 origin/1.18
        表示我准备下载 1.18分支。
        然后进入到 gst-build-1.18目录中,准备按照官网说的,调用meson 构建配置。
        然而神奇的事情发生了,git一直等待在 各种plugins插件的 clone状态下,一直死等,就是下载不下来。
        后来实在没办法了,只好从它的官网把 1.85版本的各种模块单独下载下来放到指定位置,然后运行 meson 进行构建。
        你说它把每个版本的放到一起多好,还得分开来,分开就分开吧,还不让git正常下载,还得手动把他们下载集合到一起。
        以为这事情就结束了,其实不是,后面的事情更多,
        要成功编译,需要首先进入 的Visual Studio命令行编译环境, 然后meson 进行构建,
        需要设置 OPENSSL_ROOT_DIR环境变量,因为需要依赖 openssl库,
         我是静态编译的,因为需要完全集成到xdisp_virt程序中,不想要dll动态库的方式。
         静态编译这种编译需要修改的地方更多。
        完全不像官网说的 meson 加个 --default-library=static  编译选项就行了。
         反正我这边最终的meson编译选项如下:
         meson --default-library=static -Dbuildtype=release -Ddebug=false -Db_ndebug=true -Dc_args="-DGST_STATIC_COMPILATION      -DZLIB_WINAPI -DFFI_STATIC_BUILD" -Db_vscrt=mt -Dc_link_args="crypt32.lib" -Dlibnice=enabled -Dexamples=disabled -Ddevtools=disabled -Dges=disabled -Dgtk_doc=disabled -Dpython=disabled -Dvaapi=disabled -Dorc=disabled -Dlibav=disabled -Dgst-plugins-bad:glib-asserts=disabled -Dgst-plugins-bad:glib-checks=disabled -Dgst-plugins-base:glib-asserts=disabled -Dgst-plugins-base:glib-checks=disabled -Dgst-plugins-good:glib-asserts=disabled -Dgst-plugins-good:glib-checks=disabled -Dgstreamer:glib-asserts=disabled -Dgstreamer:glib-checks=disabled -Dgstreamer:option-parsing=false -Dgstreamer:registry=false -Dgstreamer:gst_debug=false -Dgstreamer:doc=disabled -Dgstreamer:examples=disabled -Dgst-plugins-bad:openh264=disabled  -Dgst-plugins-bad:d3d11=disabled -Dgst-plugins-bad:d3dvideosink=disabled -Dgst-plugins-bad:mediafoundation=disabled build
         
        采用ninja编译的时候,首先第一步,就是 glib编译问题,glib的test目录总是编译不过,
       后来也没办法,修改meson,忽略test目录的编译,反正也用不上,
        还有glib中出现多个DllMain,连接的时候会出问题,没办法,只好修改代码,全部注释掉。
        因此这里还得记得把glib初始化函数放到我程序中去调用,还有glib 的res资源也是重复的,也得删掉。
       反正也是改的够疯狂的。
        再然后就是dlts选项里边的meson配置也有问题,也得修改,
       再然后,我还得使用librtsp库,librtsp可以单独编译,但是windows中配置 pkgconfig实在麻烦,
       没办法, 我把librtsp仿照libnice那样添加到 gstreamer的subprojects中去编译,按照这种方式我也把opus库也添加进去了。
       因为是静态编译,还得修改meson配置文件,去掉GST_API_EXPORT定义,。。。
       。。。。 等等。
      反正是哪里有问题,就得相应修改,非常能折腾。
      最终编译成功,作为静态库集成到xdisp_virt程序中了,但是正因为是静态库,初始化的时候,需要多做一些内容,
      如下是静态编译的而且需要加载WebRTC以及相关模块的初始化代码:

extern "C"
//    GST_PLUGIN_STATIC_DECLARE(x264);
    GST_PLUGIN_STATIC_DECLARE(audiotestsrc);
    GST_PLUGIN_STATIC_DECLARE(rtp);
    GST_PLUGIN_STATIC_DECLARE(videoparsersbad);
//    GST_PLUGIN_STATIC_DECLARE(audioparsers);
    GST_PLUGIN_STATIC_DECLARE(app);
//    GST_PLUGIN_STATIC_DECLARE(opus);
    GST_PLUGIN_STATIC_DECLARE(nice);
    GST_PLUGIN_STATIC_DECLARE(dtls);
    GST_PLUGIN_STATIC_DECLARE(rtpmanager);
    GST_PLUGIN_STATIC_DECLARE(srtp);
    GST_PLUGIN_STATIC_DECLARE(webrtc);
    GST_PLUGIN_STATIC_DECLARE(coreelements);

    GST_PLUGIN_STATIC_DECLARE(sctp);

int webrtc_init()

    glib init , from DllMain
#ifdef WIN32
    g_clock_win32_init();
    g_thread_win32_init();
#endif

    glib_init();

    gobject_init();

    //
    GError *err = NULL;
    if (!gst_init_check(NULL, 0, &err))
        printf("*** gst_init_check err=%d\\n", err );
        return -1;
    
    
    /静态加载相关模块
//    GST_PLUGIN_STATIC_REGISTER(x264);
//    GST_PLUGIN_STATIC_REGISTER(audiotestsrc);
    GST_PLUGIN_STATIC_REGISTER(rtp);   // ->rtph264pay
    GST_PLUGIN_STATIC_REGISTER(videoparsersbad); // -> h264parse
//    GST_PLUGIN_STATIC_REGISTER(audioparsers);  // -> aacparse
    GST_PLUGIN_STATIC_REGISTER(app);   /// -> appsrc
//    GST_PLUGIN_STATIC_REGISTER(opus);
    GST_PLUGIN_STATIC_REGISTER(nice);
    GST_PLUGIN_STATIC_REGISTER(dtls);
    GST_PLUGIN_STATIC_REGISTER(rtpmanager);
    GST_PLUGIN_STATIC_REGISTER(srtp);
    GST_PLUGIN_STATIC_REGISTER(webrtc);   // ->webrtcbin
    GST_PLUGIN_STATIC_REGISTER(coreelements);

//    GST_PLUGIN_STATIC_REGISTER(sctp);
    /
    
    check_plugins();
    return 0;

      按照如下方式构建一个 PIPE:
      webrtcbin name=webrtcbin  appsrc name=videosrc is-live=true ! h264parse ! rtph264pay !
      application/x-rtp,media=video,encoding-name=H264,payload=97 ! webrtcbin.
     
      创建管道,各种创建都已经做好,
      通过appsrc ,把H264码流喂给 webrtcbin,程序也正常运行起来了,浏览器也能正常看到画面了。
      然后就是非常不靠谱的现象:
      运行几十秒,或者一两分钟,画面卡住不动,然后等一会又接着运动了,非常有规律的,
      但是网络通信流量也正常,也就是网络没有中断过。
      而且还发现,如果画面持续性运动,比如一直播放一个视频,那延迟效果还比较好,
      可是一旦画面停止不动,或者运动缓慢
     (远程桌面经常是这样的,帧率都是动态变化的,有可能几秒钟才会变化,刷出一个帧)
      然后高延迟就来了,浏览器上的画面很慢才反应过来,也就是延迟非常大。
      然后播放视频之后,它又能快速反应了。
      一度怀疑是WebRTC需要给他提供稳定帧率的,而且是连续不断的画面才能正常使用。
      这种垃圾效果,完全不能胜任实时性啊。。。
      也想尝试修改gstreamer代码,可是它使用glib,消息全使用 g_signal_emit_XXX提交,消息路由到哪里都搞不清楚,
       调试起来也麻烦,
       不想折腾了。。。
       打算放弃WebRTC了。。。

 然后又发现MSE(Media Source Extention)这个东西,可以把H264喂给video标签。
 其实以前也听说过MSE,但当时的影像是好像延迟也挺大,
 当时感觉封装 fMP4 的时候,需要一个关键帧一个关键帧的封装到一起,也就是跟ts分段那样,如果没有关键帧,ts不能正常使用。
如果真的的是这样,那肯定没戏,肯定无法达到实时性的效果。
不过这次好像发现可以单独一帧(不管是I帧还是P帧)封装到fMP4中,然后喂给video标签,如果真是这样,那解决实时性就有戏了。
具体等做好WebRTC之后再去尝试。

在编译,修改,折腾 gstreamer过程中(其实这个时间挺长的,估计得有20多天吧),
偶然发现还有另外一个webrtc开源库,那就是ksvwebrtc,是亚马逊的提供。
只是当时忙着折腾gstreamer了,无暇顾及。
折腾gstreamer失败,打算放弃的时候,抱着试试看看的心情,
反正kvswebrtc的编译也比gstreamer轻松得多了。
结果集成到xdisp_virt之后,效果还不错,实时性也比较好,完全没有使用gstreamer时候的那种垃圾效果,
关键是使用video标签,浏览器的CPU占有率降下来了。
于是打算继续进行 kvswebrtc 开发,完善xdisp_virt上的相关功能,
       
目前xdisp_virt 的WebRTC功能还在开发中,完成之后会发布到GITHUB上,有兴趣可以关注:
GitHub - fanxiushu/xdisp_virt: xfsredir file system

未完待续,下文继续介绍kvsWebRTC和MSE相关的内容。。。

以上是关于Windows远程桌面实现之十三:浏览器客户端使用WebRTC传输,以及WebRTC和MSE渲染显示的主要内容,如果未能解决你的问题,请参考以下文章

Windows远程桌面实现之十三:浏览器客户端使用WebRTC传输,TCP的TURN中转传输

Windows远程桌面实现之十三:浏览器客户端使用WebRTC传输,以及WebRTC和MSE渲染显示

Windows远程桌面实现之十三:浏览器客户端使用WebRTC传输,以及WebRTC和MSE渲染显示

Windows远程桌面实现之十三:浏览器客户端使用WebRTC传输,以及WebRTC和MSE渲染显示

Windows远程桌面实现之十三:浏览器客户端使用WebRTC传输,以及WebRTC和MSE渲染显示

Windows远程桌面实现之十二:桌面屏幕通过ONVIF协议与NVR等监控录像设备对接,以及进一步增强直播功能