IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 开发工具 -> Windows远程桌面实现之十三:浏览器客户端使用WebRTC传输,以及WebRTC和MSE渲染显示(二) -> 正文阅读

[开发工具]Windows远程桌面实现之十三:浏览器客户端使用WebRTC传输,以及WebRTC和MSE渲染显示(二)

by fanxiushu 2022-03-12 转载或引用请注明原作者。
接上文。
我们先来编译kvswebrtc开源代码。
首先得从github下载 ksvwebrtc源码,
分别需要 ?amazon-kinesis-video-streams-pic, amazon-kinesis-video-streams-producer-c,
amazon-kinesis-video-streams-webrtc-sdk-c 三个,其中amazon-kinesis-video-streams-producer-c其实只需要头文件即可,不必编译。

另外还必须要 usrsctp,libsrtp 两个开源库。openssl 开源库也是必须的。这些开源库都可以直接从github下载。
openssl如何编译这里就不罗嗦了,网上很多介绍。
先来看usrsctp和libsrtp如何编译
(这里以windows的编译为例,其他平台下尤其linux下,这些开源库都是非常容易编译的)
首先在windows中安装最新的cmake程序,
然后打开 cmake-gui程序,直接在cmake界面中操作即可,直接编译成 visual studio 的sln工程文件,
(libsrtp需要 openssl ,因此在 cmake-gui中需要配置 OPENSSL_ROOT_DIR 等变量)
打开sln,直接通过vs编译成lib静态库。编译生成 srtp2.lib 和 usrsctp.lib两个静态库。
就这么简单。
同样的,对于kvswebrtc的上面提到三个开源代码(其实只需要编译两个)
使用cmake生成amazon-kinesis-video-streams-pic 的sln工程文件的时候,需要注意在cmake中设置 BUILD_DEPENDENCIES=OFF,
这样就不会自动下载和编译关联项,因为我们只需要 amazon-kinesis-video-streams-pic,
编译成功之后,会生成 kvspic.lib, kvspicClient.lib, kvspicState.lib, kvspicUtils.lib 四个lib静态库。

接下来就是稍微麻烦点的 amazon-kinesis-video-streams-webrtc-sdk-c 编译问题。
首先,我们得把 各种头文件复制到某个公共目录中,比如新建一个 include 目录,
把 usrsctp, libsrtp, amazon-kinesis-video-streams-pic,amazon-kinesis-video-streams-producer-c ,
amazon-kinesis-video-streams-webrtc-sdk-c 里边的头文件复制到 include目录中,
同时,我们必须手工修改 amazon-kinesis-video-streams-webrtc-sdk-c? 的CMakeList.txt文件。
把kvsWebrtcSignalingClient 相关工程部分屏蔽掉,因为只需要webrtc标准通信部分,至于信令我们自己实现即可。
编译 amazon-kinesis-video-streams-webrtc-sdk-c 的时候 cmake除了添加 BUILD_DEPENDENCIES=OFF,
也必须添加 OPENSSL_ROOT_DIR,
生成sln工程,使用VS打开sln之后,把上面的include目录加入到包含目录中,
还得修改某些代码,主要是屏蔽 libwebsocket的调用和注释掉kvsWebrtcSignalingClient 相关部分。
编译会生成 kvsWebrtcClient.lib 库。

所以,最终连接到我们程序中的就是上面编译成功的7个lib静态库文件,当然还包括openssl生成的两个静态库文件。

不管怎么说,这些编译和修改比起gstreamer的编译和修改(上一篇文件中描述编译gstreamer过程),简直是太简单了。
也比谷歌自己的WebRTC编译简单的多,更重要的是kvswebrtc全部使用纯C语言开发,在各种平台下编译都变得很友好。

接下来,我们再来介绍如何使用 kvswebrtc的API接口,其实也是非常简洁的。
API接口的调用方式接近javascript的webRTC接口.
首先,在程序开始的地方调用 initKvsWebRtc 函数,初始化 kvswebrtc。
调用 createPeerConnection 函数创建 RtcPeerConnection ,
再然后调用 peerConnectionOnIceCandidate 和peerConnectionOnConnectionStateChange 设置 回调函数,
调用 addTransceiver 设置媒体通道的传输 Transceiver,
如果是数据通道,调用 createDataChannel 创建数据通道,接着调用 dataChannelOnOpen打开数据通道,
调用 dataChannelOnMessage? 设置数据通道接收回调函数。

接着调用createOffer 创建OfferSDP,setLocalDescription 函数把OfferSDP设置到本地,调用此函数之后,
kvswebrtc开始收集本网络和ICE等信息,通过 peerConnectionOnIceCandidate? 设置的回调函数返回给调用者。
然后我们把OfferSDP通过我们自己的通信协议发给浏览器客户端,同时也把生成的 IceCandidate等信息发给浏览器客户端。
当接收到浏览器客户端的AnswerSDP之后,调用setRemoteDescription 设置到本地,
接收到浏览器客户端的IceCandidate之后,调用addIceCandidate 添加到本地。
当成功建立webrtc连接之后(具体通过peerConnectionOnConnectionStateChange 设置的回调函数指示是否成功建立WebRTC)
我们要把已经编码成H264的视频流传输给浏览器客户端,直接调用kvswebrtc提供的writeFrame 函数即可。
于是整个kvswebrtc就这样跑通了。

是不是比起谷歌的WebRTC和gstreamer的调用方式简单得太多,而且我测试的使用效果也并不差。
真是没有比较就没有伤害啊。
具体如何使用kvswebrtc,请去查阅kvswebrtc提供的例子代码。

上一篇文章中说过了,在开发WebRTC中,偶然发现 MSE 也可以提供低延迟,实时性的基于video标签的渲染。
于是接下来我们再来研究MSE的实现方式。
开始之前,先来张已经在xdisp_virt中实现了webRTC和MSE的截图用于提神:



上图中,video标签渲染部分就是xdisp_virt最新实现的WebRTC和MSE的功能,
在最新实现中,声音编码依然是通过原来的方式进行处理,只有视频编码才通过WebRTC或MSE进行处理。
下面的 “通用(WebGL渲染)”,就是xdisp_virt原先实现的功能,并且在其中还新添加了WebRTC数据通道传输音视频数据。
最新版本请关注GITHUB上的更新,最近会把新版本xdisp_virt发布上去。

我们接着再来阐述如何实现MSE功能,
MSE需要生成 fMP4 格式的流,然后再喂给 MSE,这样才能正常使用。
现在关键是如何生成 fMP4 的格式流,
并不打算在xdisp_virt程序端生成 fMP4,因为这样改动较大,还得专门增加一个协议来传输fMP4流,
于是最好的办法,保持现有的传输方式不变(WebSocket传输 H264 编码),直接在javascript端把H264编码流转成 fMP4 流。

现在的问题是如何使用javascript把H264编码转成 fMP4 流。
其实网上也有一些直接使用js实现的 转 fMP4 的代码,比如 jmuxer等,但使用并不理想,
主要是因为我这边xdisp_virt有自己的一套实现方式,而且更主要的当使用jmuxer运行MSE渲染,
然后把电脑从浏览器切换到其他程序,
再然后切换回来,结果要么浏览器上的画面卡死,要么就是非常神奇的画面快速播放,在极速清空切换后没有播放的画面。
也懒得去修改jmuxer了,况且可能会越改越乱。

于是再次想到了 ffmpeg 这个神器。既然前面实现中,统一使用生成wasm的ffmpeg来解码各种图像和音频编码。
现在再增加一个 生成 fMP4 的接口,自然也没任何问题。事实上也确实这样。

如何使用 ffmpeg框架,把 H264 编码流转成 fMP4 流呢?
其实跟 转成 本地MP4,flv,mkv这些视频等没啥区别。
还记得我很早前的文章中,有专门描述过如何利用 ffmpeg 转成RTSP,RTMP以及本地视频文件。地址:
Windows远程桌面实现之五(FFMPEG实现桌面屏幕RTSP,RTMP推流及本地保存)_fanxiushu的专栏-CSDN博客_ffmpeg 桌面推流
具体就是讲述远程桌面文章系列的第五篇文章所描述的内容,
并且在GITHUB上还提供了 stream_push 开源代码与此文相对应。
地址:https://github.com/fanxiushu/stream_push

现在,我们只需要把 stream_push 的源码稍微做些修改,就可以把 AnnexB格式的H264裸流 转成 fMP4 流。
1,首先在调用 avformat_write_header 写头之前,需要设置 fMP4属性,
? ? ? av_dict_set(&options, "movflags", "empty_moov+default_base_moof+frag_keyframe", 0);
????? avformat_write_header(ofm_ctx, options ? &options : NULL);
? ? ? 这样就能确保 生成 fMP4 格式的流。
2,当然需要删除stream_push里边某些不必要的代码,因为只需要生成 MP4 格式的视频。
? ? ?因此在stream_push工程的open函数中,也就是创建 AVFormatContext的过程中,按照如下方式创建:
? ??? avioc_buffer_size = 8 * 1024 * 1024;
?? ?? avioc_buffer = (uint8_t *)av_malloc(avioc_buffer_size);
? ? ? ofm_ctx = avformat_alloc_context();
? ? ? ofm_ctx->oformat = av_guess_format("mp4", NULL, NULL);
? ? ? ofm_ctx->pb = avio_alloc_context(avioc_buffer, avioc_buffer_size, AVIO_FLAG_WRITE, this, NULL, write_packet, NULL);
? ? 其中write_packet是个回调函数,表示当ffmpeg 生成fMP4流的时候,这个回调函数就会调用,
? ? ?通常是调用 av_write_frame 之后,或者ffmpeg缓存满了需要写出数据的时候调用。
? ? 相当于是已经生成了fMP4流数据,按照普通方式是直接写到本地文件中,而在这里,我们需要这个数据,因此直接通过回调函数来获取。
3,在上一篇文中讲述过,因为我们需要实时性的 fMP4 格式流,也就是意味着每来一帧 H264编码帧,都应该立即刷出 fMP4 流。
? ? ? 这在 ffmpeg的实现中,是可以做到的。
? ? ?具体就是在 stream_push工程的 send_packet 函数中,在调用完成? av_interleaved_write_frame 函数之后,
? ? ?再次调用 av_write_frame(ofm_ctx, NULL); 记住最后一个参数是NULL,
? ?? 这样就会让 ffmpeg 把av_interleaved_write_frame 写入的数据帧,立即刷新出来,也就是 write_packet回调函数立即会被调用。

以上就是利用stream_push工程改造生成 fMP4 流的需要注意的三个要点。
有兴趣可以自己使用 stream_push去实现 fMP4 流的功能。
当然,最后再使用 emscripten 工具,编译生成 wasm,提供给js前端调用。

接下来,浏览器前端已经获取到H264转成的 fMP4 流,如何喂给MSE呢?
这个其实也是挺简单的,我们不要使用各种开源框架,直接使用MSE的接口函数就可以了。

按照如下方法创建:
? ? var mimeCodec = 'video/mp4; codecs="avc1.42E01E"'; //是否支持 H264

??? if ('MediaSource' in window && MediaSource.isTypeSupported(mimeCodec)) {
??? }
??? else {
??????? alert('Can not Supported MSE(Media Source Extensions)');
??????? return;
??? }

??? /
??? media_source = new MediaSource();
??? canvas.src = URL.createObjectURL(media_source);
??? media_source.addEventListener('sourceopen', function () {
?????? ?
??????? media_source.duration = Infinity; ///
???????
??????? media_buffer = media_source.addSourceBuffer(mimeCodec);

??????? media_buffer.mode = 'sequence';
??? });

这样在每次到来 fMP4流的时候,调用 ?media_buffer.appendBuffer( fmp4_stream_data ) 添加到MSE中。
真是要多简单就有多简单。

不过这里需要注意一些问题,应该在media_buffer 没有updating的时候添加fmp4流,否则会添加失败,这样会造成丢帧。
因此我们实际上是先把 fmp4 流数据拼接到 某个队列中,然后判断updating,再决定是否添加到 media_buffer 中
具体如下:
var media_queue = new Uint8Array();
function media_source_push_data(data)
{
??? var t = new Uint8Array(media_queue.length + data.length);
??? t.set(media_queue, 0);
??? t.set(data, media_queue.length);
??? media_queue = t;
??? /
??? if( media_buffer && media_queue.length > 0 && !media_buffer.updating){
??????? media_buffer.appendBuffer(media_queue);
?????? ?
??????? media_queue = new Uint8Array(); //reset
??? }
??? /
}

其次,因为我们需要实时性的MSE,因此一个非常重要做的事情,就是得不断清空多余的缓存,
我也不清楚现在的浏览器实现MSE的时候,为何总是喜欢缓存,缓存一旦变大,很大的延迟就会来了,
这对于远程桌面这类需求(需要非常高的实时性)是无法容忍的。
具体做法如下,(下面的canvas是 video标签的变量值)
设置一个每间隔100毫秒就执行的定时清理任务:
setInterval(function () {
??????? /// flush
??????? if (media_buffer && media_queue.length > 0 && !media_buffer.updating) {
??????????? console.log('-- setInterval flush media queue.len=' + media_queue.length);
???????????
??????????? media_buffer.appendBuffer(media_queue);
?????????? ?
??????????? media_queue = new Uint8Array(); //reset
??????? }

??????? ///剔除缓存太多的帧,目的是为了实现实时性,否则造容易成长期缓存,
? ? ? ? ///这么做的结果就是对于MSE的实时性支持差的浏览器,非常卡顿。
??????? if (canvas.buffered && canvas.buffered.length > 0 && !canvas.seeking) {
??????????? const end = canvas.buffered.end(0);
??????????? if ( (end - canvas.currentTime )*1000 >= 200 ) { // > 200 ms
???????????????
??????????????? console.log('video delay seek to end, curr=' + canvas.currentTime +', end='+end );
??????????????? canvas.currentTime = end - 0.001;
??????????? }
??????? }

???????
??? }, 100);

按照如上方式实现的MSE,在chrome内核浏览器上运行,实时性的效果是比较理想的,
不过比起 webRTC和WebGL渲染,依然有一些逊色。
至于在其他内核浏览器(其实主要是WebKit和Firefox浏览器,现在主流的浏览器内核无非就这三种)
则是非常卡顿,卡顿的原因是上面代码中,超过200毫秒的缓存就会被清除掉。
也就是说其他两个内核版本的浏览器,对低延迟的MSE,支持得比较差。

最后,gif图片简单演示一下xdisp_virt最新实现的 WebRTC和MSE功能的效果:

演示中,还可以看到xdisp_virt实现了 FrameBuffer截屏,
也就是可以截取到登录纯字符界面的linux。

因为上传图片大小有限,因此压缩的很厉害。
实际体验请去GITHUB下载最新版本的xdisp_virt ,
稍后会把添加WebRTC和MSE功能的xdisp_virt发布到GITHUB上:
https://github.com/fanxiushu/xdisp_virt

?

?

  开发工具 最新文章
Postman接口测试之Mock快速入门
ASCII码空格替换查表_最全ASCII码对照表0-2
如何使用 ssh 建立 socks 代理
Typora配合PicGo阿里云图床配置
SoapUI、Jmeter、Postman三种接口测试工具的
github用相对路径显示图片_GitHub 中 readm
Windows编译g2o及其g2o viewer
解决jupyter notebook无法连接/ jupyter连接
Git恢复到之前版本
VScode常用快捷键
上一篇文章      下一篇文章      查看所有文章
加:2022-03-15 22:47:55  更:2022-03-15 22:48:29 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/26 6:38:47-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码