Menu

  • Home
  • Work
    • Cloud
      • Virtualization
      • IaaS
      • PaaS
    • Java
    • Go
    • C
    • C++
    • JavaScript
    • PHP
    • Python
    • Architecture
    • Others
      • Assembly
      • Ruby
      • Perl
      • Lua
      • Rust
      • XML
      • Network
      • IoT
      • GIS
      • Algorithm
      • AI
      • Math
      • RE
      • Graphic
    • OS
      • Linux
      • Windows
      • Mac OS X
    • BigData
    • Database
      • MySQL
      • Oracle
    • Mobile
      • Android
      • IOS
    • Web
      • HTML
      • CSS
  • Life
    • Cooking
    • Travel
    • Gardening
  • Gallery
  • Video
  • Music
  • Essay
  • Home
  • Work
    • Cloud
      • Virtualization
      • IaaS
      • PaaS
    • Java
    • Go
    • C
    • C++
    • JavaScript
    • PHP
    • Python
    • Architecture
    • Others
      • Assembly
      • Ruby
      • Perl
      • Lua
      • Rust
      • XML
      • Network
      • IoT
      • GIS
      • Algorithm
      • AI
      • Math
      • RE
      • Graphic
    • OS
      • Linux
      • Windows
      • Mac OS X
    • BigData
    • Database
      • MySQL
      • Oracle
    • Mobile
      • Android
      • IOS
    • Web
      • HTML
      • CSS
  • Life
    • Cooking
    • Travel
    • Gardening
  • Gallery
  • Video
  • Music
  • Essay

HTML5视频监控技术预研

28
Aug
2017

HTML5视频监控技术预研

By Alex
/ in C++,Graphic,HTML,JavaScript
/ tags JSMpeg, MSE, Multimedia, WebRTC, 视频监控
7 Comments
引言

安防类项目中通常都有视频监控方面的需求。视频监控客户端主要是Native应用的形式,在Web端需要利用NPAPI、ActiveX之类的插件技术实现。

但是,IE式微,Chrome也放弃了NPAPI,另一方面,监控设备硬件厂商的视频输出格式则逐渐标准化。这让基于开放、标准化接口的Web视频监控成为可能。

本文讨论以HTML5及其衍生技术为基础的B/S架构实时视频监控解决方案。主要包括两方面的内容:

  1. 视频编码、流媒体基础知识,以及相关的库、框架的介绍
  2. 介绍可以用于视频监控的HTML5特性,例如媒体标签、MSE、WebRTC,以及相关的库、框架

本文仅仅简介若干种备选的解决方案,本站其它文章进行了更加深入的探讨:

  1. H.264学习笔记
  2. 实时通信协议族
  3. 基于Kurento搭建WebRTC服务器
  4. 基于Broadway的HTML5视频监控
音视频编码

音频、视频的编码(Codec,压缩)算法有很多,不同浏览器对音视频的编码算法的支持有差异。H264这样的监控设备常用的视频编码格式,主流浏览器都有某种程度的支持。

常见的音频编码算法包括: MP3, Vorbis, AAC;常见的视频编码算法包括: H.264, HEVC, VP8, VP9。

编码后的音频、视频通常被封装在一个比特流容器格式(container)中,这些格式中常见的有: MP4, FLV, WebM,  ASF, ISMA等。

JSMpeg

视频解码工作通常由浏览器本身负责,配合video实现视频播放。

现代浏览器的JS引擎性能较好,因此出现了纯粹由JS实现的解码器JSMpeg,它能够解码视频格式MPEG1、音频格式MP2。支持通过Ajax加载静态视频文件,支持低延迟(小于50ms)的流式播放(通过WebSocket)。JSMpeg包括以下组件:

  1. MPEG-TS分流器(demuxer)。muxer负责把视频、音频、字幕打包成一种容器格式,demuxer则作相反的工作
  2. MPEG1视频解码器
  3. MP2音频解码器
  4. WebGL渲染器、Canvas2D渲染器
  5. WebAudio音频输出组件

JSMpeg的优势在于兼容性好,几乎所有现代浏览器都能运行JSMpeg。

性能

JSMpeg不能使用硬件加速。在iPhone 5S这样的设备上,JSMpeg能够处理720p@30fps视频。

比起现代解码器,MPEG1压缩率较低,因而需要更大的带宽。720p的视频大概占用250KB/s的带宽。

示例

下面我们尝试利用ffmpeg编码本地摄像头视频,并通过JSMpeg播放。

创建一个NPM项目,安装依赖:

JavaScript
1
2
npm install jsmpeg --save
npm install ws --save

JSMpeg提供了一个中继器,能够把基于HTTP的MPEG-TS流转换后通过WebSocket发送给客户端。此脚本需要到Github下载。 下面的命令启动一个中继器:

Shell
1
2
3
4
node ./app/websocket-relay.js 12345 8800 8801
# Listening for incomming MPEG-TS Stream on http://127.0.0.1:8800/<secret>
# Awaiting WebSocket connections on ws://127.0.0.1:8801/
# 实际上在所有网络接口上监听,并非仅仅loopback

下面的命令捕获本地摄像头(Linux),并编码为MPEG1格式,然后发送到中继器:

Shell
1
2
3
4
5
       # 从摄像头/dev/video0以480的分辨率捕获原始视频流
ffmpeg -s 640x480 -f video4linux2 -i /dev/video0 \
       # 输出为原始MPEG-1视频(JSMpeg可用),帧率30fps,比特率800kbps
       -f mpegts -codec:v mpeg1video -b 800k -r 30 http://127.0.0.1:8800/12345
# 在我的机器上,上述ffmpeg私有内存占用18MB

上述命令执行后,中继器控制台上打印:

Shell
1
Stream Connected: ::ffff:127.0.0.1:42399

客户端代码:

JavaScript
1
2
3
4
var player = new JSMpeg.Player( 'ws://127.0.0.1:8801/', {
    canvas: document.getElementById( 'canvas' ),
    autoplay: true
} ); 
Broadway

Broadway是一个基于JavaScript的H.264解码器,其源码来自于Android的H.264解码器,利用Emscripten转译成了JavaScript,之后利用Google的Closure编译器优化,并针对WebGL进一步优化。

注意:Broadway仅仅支持Baseline这个H.264 Profile。

h264-live-player是基于Broadway实现的播放器,允许通过WebSocket来传输NAL单元(原始H.264帧),并在画布上渲染。我们运行一下它的示例应用:

Shell
1
2
3
git clone https://github.com/131/h264-live-player.git
cd h264-live-player
npm install

因为我的机器是Linux,所以修改h264-live-player/lib/ffmpeg.js, 把ffpmeg的参数改为:

JavaScript
1
2
3
4
5
6
7
8
9
10
11
12
13
14
var args = [
    "-f", "video4linux2",
    "-i",  "/dev/video0" ,
    "-framerate", this.options.fps,
    "-video_size", this.options.width + 'x' + this.options.height,
    '-pix_fmt',  'yuv420p',
    '-c:v',  'libx264',
    '-b:v', '600k',
    '-bufsize', '600k',
    '-vprofile', 'baseline',
    '-tune', 'zerolatency',
    '-f' ,'rawvideo',
    '-'
];

然后运行 node server-ffmpeg,打开http://127.0.0.1:8080/,可以看到自己摄像头传来的H.264码流,效果还不错。

服务器端技术
ffpmeg

老牌的编解码库,支持很多的音频、视频格式的编解码,支持多种容器格式,支持多种流协议。关于ffpmeg的详细介绍参见Linux命令知识集锦。

ffpmeg除了提供开发套件之外,还有一个同名的命令行工具,直接使用它就可以完成很多编解码、流转换的工作。

类似的库是libav,ffpmeg和它的功能非常相似,特性更多一些。

x264

官网自称是最好的H.264编码器。特性包括:

  1. 提供一流的性能、压缩比。特别是性能方面,可以在普通PC上并行编码4路或者更多的1080P流
  2. 提供最好的视频质量,具有最高级的心理视觉优化
  3. 支持多种不同应用程序所需要的特性,例如电视广播、蓝光低延迟视频应用、Web视频
流媒体技术

有了上面介绍的HTML5标签、合理编码的视频格式,就可以实现简单的监控录像回放了。但是,要进行实时监控画面预览则没有这么简单,必须依赖流媒体技术实现。

流媒体

所谓多媒体(Multimedia)是指多种内容形式 —— 文本、音频、视频、图片、动画等的组合。

所谓流媒体,就是指源源不断的由提供者产生,并持续的被终端用户接收、展示的多媒体,就像水流一样。现实世界中的媒体,有些天生就是流式的,例如电视、广播,另外一些则不是,例如书籍、CD。

流媒体技术(从传递媒体角度来看)可以作为文件下载的替代品。

流媒体技术关注的是如何传递媒体,而不是如何编码媒体,具体的实现就是各种流媒体协议。封装后的媒体比特流(容器格式)由流媒体服务器递送到流媒体客户端。流媒体协议可能对底层容器格式、编码格式有要求,也可能没有任何要求。

直播

直播流(Live streaming)和静态文件播放的关键差异:

  1. 点播的目标文件通常位于服务器上,具有一定的播放时长、文件大小。浏览器可以使用渐进式下载,一边下载一边播放
  2. 直播不存在播放起点、终点。它表现为一种流的形式,源源不断的从视频采集源通过服务器,传递到客户端
  3. 直播流通常是自适应的(adaptive),其码率随着客户端可用带宽的变化,可能变大、变小,以尽可能消除延迟

流媒体技术不但可以用于监控画面预览,也可以改善录像播放的用户体验,比起简单的静态文件回放,流式回放具有以下优势:

  1. 延迟相对较低,播放能够尽快开始
  2. 自适应流可以避免卡顿
流协议

主流的用于承载视频流的流媒体协议包括:

协议 说明
HLS

HTTP实时流(HTTP Live Streaming),由苹果开发,基于HTTP协议

HLS的工作原理是,把整个流划分成一个个较小的文件,客户端在建立流媒体会话后,基于HTTP协议下载流片段并播放。客户端可以从多个服务器(源)下载流。

在建立会话时,客户端需要下载extended M3U (m3u8) 播放列表文件,其中包含了MPEG-2 TS(Transport Stream)容器格式的视频的列表。在播放完列表中的文件后,需要再次下载m3u8,如此循环

此协议在移动平台上支持较好,目前的Android、iOS版本都支持

此协议的重要缺点是高延迟(5s以上通常),要做到低延迟会导致频繁的缓冲(下载新片段)并对服务器造成压力,不适合视频监控

播放HLS流的HTML代码片段:

XHTML
1
<video src="http://movie.m3u8" height="329" width="480"></video>
RTMP

实时消息协议(Real Time Messaging Protocol),由Macromedia(Adobe)开发。此协议实时性很好,需要Flash插件才能在客户端使用,但是Adobe已经打算在不久的将来放弃对Flash的支持了

有一个开源项目HTML5 FLV Player,它支持在没有Flash插件的情况下,播放Flash的视频格式FLV。此项目依赖于MSE,支持以下特性:

  1. 支持H.264 + AAC/MP3编码的FLV容器格式的播放
  2. 分段(segmented)视频播放
  3. 基于HTTP的FLV低延迟实时流播放
  4. 兼容主流浏览器
  5. 资源占用低,可以使用客户端的硬件加速
RTSP

实时流协议(Real Time Streaming Protocol),由RealNetworks等公司开发。此协议负责控制通信端点(Endpoint)之间的媒体会话(media sessions) —— 例如播放、暂停、录制。通常需要结合:实时传输协议(Real-time Transport Protocol)、实时控制协议(Real-time Control Protocol)来实现视频流本身的传递

大部分浏览器没有对RTSP提供原生的支持

RTSP 2.0版本目前正在开发中,和旧版本不兼容

MPEG-DASH

基于HTTP的动态自适应流(Dynamic Adaptive Streaming over HTTP),它类似于HLS,也是把流切分为很小的片段。DASH为支持为每个片段提供多种码率的版本,以满足不同客户带宽

协议的客户端根据自己的可用带宽,选择尽可能高(避免卡顿、重新缓冲)的码率进行播放,并根据网络状况实时调整码率

DASH不限制编码方式,你可以使用H.265, H.264, VP9等视频编码算法

Chrome 24+、Firefox 32+、Chrome for Android、IE 10+支持此格式

类似于HLS的高延迟问题也存在

WebRTC

WebRTC是一整套API,为浏览器、移动应用提供实时通信(RealTime Communications)能力。它包含了流媒体协议的功能,但是不是以协议的方式暴露给开发者的

WebRTC支持Chrome 23+、Firefox 22+、Chrome for Android,提供Java / Objective-C绑定

WebRTC主要有三个职责:

  1. 捕获客户端音视频,对应接口MediaStream(也就是getUserMedia)
  2. 音视频传输,对应接口RTCPeerConnection
  3. 任意数据传输,对应接口RTCDataChannel

WebRTC内置了点对点的支持,也就是说流不一定需要经过服务器中转

服务器端技术

视频监控通常都是CS模式(而非P2P),在服务器端,你需要部署流媒体服务。

GStreamer

这是一个开源的跨平台多媒体框架。通过它你可以构建各种各样的媒体处理组件,包括流媒体组件。通过插件机制,GStreamer支持上百种编码格式,包括MPEG-1, MPEG-2, MPEG-4, H.261, H.263, H.264, RealVideo, MP3, WMV, FLV

Kurento、Flumotion是基于GStreamer构建的流媒体服务器软件。

Live555

Live555是流媒体服务开发的基础库,支持 RTP/RTCP/RTSP/SIP等协议,适合在硬件资源受限的情况下使用(例如嵌入式设备)。

基于Live555的软件包括:

  1. Live555媒体服务器,完整的RTSP服务器
  2. openRTSP,一个命令行程序,支持提供RTSP流、接收RTSP流、把RTSP流中的媒体录像到磁盘
  3. playSIP,可以进行VoIP通话
  4. liveCaster,支持组播的MP3流媒体服务
其它

流媒体服务实现有很多,它们中的一些在最初针对特定的流协议,大部分都走向多元化。例如,Red5是一个RTMP流媒体服务器,Wowza是一个综合的流媒体服务器,支持WebRTC的流媒体服务在后面的章节介绍。

HTML5媒体标签

HTML5支持 <audio>和 <video>标签(两者都对应了HTMLMediaElement的子类型)以实现视频、音频的播放。

<audio>

此标签用于在浏览器中创建一个纯音频播放器。播放静态文件的示例:

XML
1
2
3
4
5
6
7
<audio controls preload="auto">
    <source src="song.mp3" type="audio/mpeg">
    <!-- 备选格式,如果浏览器不支持mp3 -->
    <source src="song.ogg" type="audio/ogg">
    <!-- 如果浏览器不支持audio标签,显示下面的连接 -->
    <a href="audiofile.mp3">download audio</a>
</audio>
<video>

此标签用于在浏览器中创建一个视频播放器。播放静态文件的示例:

XHTML
1
2
3
4
5
6
7
8
9
10
<!-- poster指定预览图,autoplay自动播放,muted静音 -->
<video controls width="640" height="480" poster="movie.png" autoplay muted>
  <source src="movie.mp4" type="video/mp4">
  <!-- 备选格式,如果浏览器不支持mp4 -->
  <source src="movie.webm" type="video/webm">
  <!-- 可以附带字幕 -->
  <track src="subtitles_en.vtt" kind="subtitles" srclang="en" label="English">
  <!-- 如果浏览器不支持video标签,显示下面的连接 -->
  <a href="videofile.mp4">download video</a>
</video>
<canvas>

在画布中,你可以进行任意的图形绘制,当然可以去逐帧渲染视频内容。

编程方式创建

音频、视频播放器标签也可以利用JavaScript编程式的创建,示例代码:

JavaScript
1
2
3
4
5
6
7
8
9
var video = document.createElement( 'video' );
if ( video.canPlayType( 'video/mp4' ) ) {
    video.setAttribute( 'src', 'movie.mp4' );
}
else if ( video.canPlayType( 'video/webm' ) ) {
    video.setAttribute( 'src', 'movie.webm' );
}
video.width = 640;
video.height = 480; 
MSE

媒体源扩展(Media Source Extensions,MSE)是一个W3C草案,桌面浏览器对MSE的支持较好。MSE扩展流video/audio元素的能力,允许你通过JavaScript来生成(例如从服务器抓取)媒体流供video/audio元素播放。使用MSE你可以:

  1. 通过JavaScript来构建媒体流,不管媒体是如何捕获的
  2. 处理自适应码流、广告插入、时间平移(time-shifting,回看)、视频编辑等应用场景
  3. 最小化JavaScript中处理媒体解析的代码

MSE定义支持的(你生成的)媒体格式,只有符合要求的容器格式、编码格式才能被MSE处理。通常容器格式是ISO BMFF(MP4),也就是说你需要生成MP4的片断,然后Feed给MSE进行播放。

MediaSource对象作为video/audio元素的媒体来源,它可以具有多个SourceBuffer对象。应用程序把数据片段(segment)附加到SourceBuffer中,并可以根据系统性能对数据片段的质量进行适配。SourceBuffer中包含多个track buffer —— 分别对应音频、视频、文本等可播放数据。这些数据被音频、视频解码器解码,然后在屏幕上显示、在扬声器中播放:

 pipeline_model

要把MediaSource提供给video/audio播放,调用:

JavaScript
1
video.src = URL.createObjectURL(mediaSource);
基于MSE的框架
wfs

wfs是一个播放原始H.264帧的HTML5播放器,它的工作方式是把H.264 NAL单元封装为 ISO BMFF(MP4)片,然后Feed给MSE处理。

flv.js

flv.js是一个HTML5 Flash视频播放器,基于纯JS,不需要Flash插件的支持。此播放器将FLV流转换为ISO BMFF(MP4)片断,然后把MP4片断提供给video元素使用。

flv.js支持Chrome 43+, FireFox 42+, Edge 15.15048+以上版本的直播流 。

Streamedian

Streamedian是一个HTML5的RTSP播放器。实现了RTSP客户端功能,你可以利用此框架直接播放RTSP直播流。此播放器把RTP协议下的H264/AAC在转换为ISO BMFF供video元素使用。Streamedian支持Chrome 23+, FireFox 42+, Edge 13+,以及Android 5.0+。不支持iOS和IE。

在服务器端,你需要安装Streamedian提供的代理(此代理收费),此代理将RTSP转换为WebSocket。Streamedian处理视频流的流程如下:streamedian

WebRTC

WebRTC是一整套API,其中一部分供Web开发者使用,另外一部分属于要求浏览器厂商实现的接口规范。WebRTC解决诸如客户端流媒体发送、点对点通信、视频编码等问题。桌面浏览器对WebRTC的支持较好,WebRTC也很容易和Native应用集成。

使用MSE时,你需要自己构建视频流。使用WebRTC时则可以直接捕获客户端视频流。

使用WebRTC时,大部分情况下流量不需要依赖于服务器中转,服务器的作用主要是:

  1. 在信号处理时,转发客户端的数据
  2. 配合实现NAT/防火墙穿透
  3. 在点对点通信失败时,作为中继器使用
架构

webrtcarchitecture

流捕获
捕获视频

主要是捕获客户端摄像头、麦克风。在视频监控领域用处不大,这里大概了解一下。流捕获通过navigator.getUserMedia调用实现: 

XHTML
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<script type="text/javascript">
    navigator.getUserMedia = navigator.webkitGetUserMedia || navigator.getUserMedia;
    var success = function ( stream ) {
        var video = document.getElementById( 'camrea' );
        // 把MediaStream对象转换为Blob URL,提供给video播放
        video.src = URL.createObjectURL( stream );
        video.play();
    }
    var error = function ( err ) {
        console.log( err )
    }
    // 调用成功后,得到MediaStream对象
    navigator.getUserMedia( { video: true, audio: true }, success, error );
</script>
<video id="camrea" width="640" height="480"/>

三个调用参数分别是:

  1. 约束条件,你可以指定媒体类型、分辨率、帧率 
  2. 成功后的回调,你可以在回调中解析出URL提供给video元素播放
  3. 失败后的回调
捕获音频

捕获音频类似:

JavaScript
1
2
3
4
5
6
7
8
9
navigator.getUserMedia( { audio: true }, function ( stream ) {
    var audioContext = new AudioContext();
 
    // 从捕获的音频流创建一个媒体源管理
    var streamSource = audioContext.createMediaStreamSource( stream );
 
    // 把媒体源连接到目标(默认是扬声器)
    streamSource.connect( audioContext.destination );
}, error );
MediaStream

MediaStream对象提供以下方法:

  1. getAudioTracks(),音轨列表
  2. getVideoTracks(),视轨列表

每个音轨、视轨都有个label属性,对应其设备名称。

Camera.js

Camera.js是对getUserMedia的简单封装,简化了API并提供了跨浏览器支持:

JavaScript
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
camera.init( {
    width: 640,
    height: 480,
    fps: 30, // 帧率
    mirror: false,  // 是否显示为镜像
    targetCanvas: document.getElementById( 'webcam' ), // 默认null,如果设置了则在画布中渲染
 
    onFrame: function ( canvas ) {
        // 每当新的帧被捕获,调用此回调
    },
 
    onSuccess: function () {
        // 流成功获取后
    },
 
    onError: function ( error ) {
        // 如果初始化失败
    },
 
    onNotSupported: function () {
        // 当浏览器不支持camera.js时
    }
} );
// 暂停
camera.pause();
// 恢复
camera.start();

掠食者视觉是基于Camera实现的一个好玩的例子(移动侦测)。

信号处理

在端点之间(Peer)发送流之前,需要进行通信协调、发送控制消息,即所谓信号处理(Signaling),信号处理牵涉到三类信息:

  1. 会话控制信息:初始化、关闭通信,报告错误
  2. 网络配置:对于其它端点来说,本机的IP和端口是什么
  3. 媒体特性:本机能够处理什么音视频编码、多高的分辨率。本机发送什么样的音视频编码

WebRTC没有对信号处理规定太多,我们可以通过Ajax/WebSocket通信,以SIP、Jingle、ISUP等协议完成信号处理。点对点连接设立后,流的传输并不需要服务器介入。信号处理的示意图如下:

jsep

示例代码

下面的代表片段包含了一个视频电话的信号处理过程:

JavaScript
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
// 信号处理通道,底层传输方式和协议自定义
var signalingChannel = createSignalingChannel();
var conn;
 
// 信号通过此回调送达本地,可能分多次送达
signalingChannel.onmessage = function ( evt ) {
    if ( !conn ) start( false );
 
    var signal = JSON.parse( evt.data );
    // 会话描述协议(Session Description Protocol),用于交换媒体配置信息(分辨率、编解码能力)
    if ( signal.sdp )
    // 设置Peer的RTCSessionDescription
        conn.setRemoteDescription( new RTCSessionDescription( signal.sdp ) );
    else
    // 添加Peer的Candidate信息
        conn.addIceCandidate( new RTCIceCandidate( signal.candidate ) );
};
 
// 调用此方法启动WebRTC,获取本地流并显示,侦听连接上的事件并处理
function start( isCaller ) {
    conn = new RTCPeerConnection( { /**/ } );
 
    // 把地址/端口信息发送给其它Peer。所谓Candidate就是基于ICE框架获得的本机可用地址/端口
    conn.onicecandidate = function ( evt ) {
        signalingChannel.send( JSON.stringify( { "candidate": evt.candidate } ) );
    };
 
    // 当远程流到达后,在remoteView元素中显示
    conn.onaddstream = function ( evt ) {
        remoteView.src = URL.createObjectURL( evt.stream );
    };
 
    // 获得本地流
    navigator.getUserMedia( { "audio": true, "video": true }, function ( stream ) {
        // 在remoteView元素中显示
        localView.src = URL.createObjectURL( stream );
        // 添加本地流,Peer将接收到onaddstream事件
        conn.addStream( stream );
 
 
        if ( isCaller )
        // 获得本地的RTCSessionDescription
            conn.createOffer( gotDescription );
        else
        // 针对Peer的RTCSessionDescription生成兼容的本地SDP
            conn.createAnswer( conn.remoteDescription, gotDescription );
 
        function gotDescription( desc ) {
            // 设置自己的RTCSessionDescription
            conn.setLocalDescription( desc );
            // 把自己的RTCSessionDescription发送给Peer
            signalingChannel.send( JSON.stringify( { "sdp": desc } ) );
        }
    } );
}
 
// 通信发起方调用:
start( true );
流转发

主要牵涉到的接口是RTCPeerConnection,上面的例子中已经包含了此接口的用法。WebRTC在底层做很多复杂的工作,这些工作对于JavaScript来说是透明的: 

  1. 执行解码
  2. 屏蔽丢包的影响
  3. 点对点通信:WebRTC引入流交互式连接建立(Interactive Connectivity Establishment,ICE)框架。ICE负责建立点对点链路的建立:
    1. 首先尝试直接
    2. 不行的话尝试STUN(Session Traversal Utilities for NAT)协议。此协议通过一个简单的保活机制确保NAT端口映射在会话期间有效
    3. 仍然不行尝试TURN(Traversal Using Relays around NAT)协议。此协议依赖于部署在公网上的中继服务器。只要端点可以访问TURN服务器就可以建立连接
  4. 通信安全
  5. 带宽适配
  6. 噪声抑制
  7. 动态抖动缓冲(dynamic jitter buffering),抖动是由于网络状况的变化,缓冲用于收集、存储数据,定期发送
任意数据交换

通过RTCDataChannel完成,允许点对点之间任意的数据交换。RTCPeerConnection连接创建后,不但可以传输音视频流,还可以打开多个信道(RTCDataChannel)进行任意数据的交换。RTCDataChanel的特点是:

  1. 类似于WebSocket的API
  2. 支持带优先级的多通道
  3. 超低延迟,因为不需要通过服务器中转
  4. 支持可靠/不可靠传输语义。支持SCTP、DTLS、UDP几种传输协议
  5. 内置安全传输(DTLS)
  6. 内置拥塞控制

使用RTCDataChannel可以很好的支持游戏、远程桌面、实时文本聊天、文件传输、去中心化网络等业务场景。

adapter.js

WebRTC adapter是一个垫片库,使用它开发WebRTC应用时,不需要考虑不同浏览器厂商的API前缀差异。

WebRTC示例

本节列出一些WebRTC的代码示例,这些例子都使用adapter.js。

限定分辨率
JavaScript
1
2
3
4
5
6
7
8
// 指定分辨率
// adapter.js 支持Promise
navigator.mediaDevices.getUserMedia( { video: { width: { exact: 640 }, height: { exact: 480 } } } ).then( stream => {
    let video = document.createElement( 'video' );
    document.body.appendChild( video );
    video.srcObject = stream;
    video.play();
} ).catch( err => console.log( err ) );
在画布中截图
JavaScript
1
2
// video为video元素
canvas.getContext('2d').drawImage(video, 0, 0, canvas.width, canvas.height);
WebRTC框架
框架 说明
PeerJS

简化WebRTC的点对点通信、视频、音频调用

提供云端的PeerServer,你也可以自己搭建服务器

Sharefest 基于Web的P2P文件共享
webRTC.io

WebRTC的一个抽象层,同时提供了客户端、服务器端Node.js组件。服务器端组件抽象了STUN

类似的框架还有SimpleWebRTC、easyrtc

OpenWebRTC

允许你构建能够和遵循WebRTC标准的浏览器进行通信的Native应用程序,支持Java绑定

NextRTC

基于Java实现的WebRTC信号处理服务器

Janus

这是一个WebRTC网关,纯服务器端组件,目前仅仅支持Linux环境下安装

Janus本身实现了到浏览器的WebRTC连接机制,支持以JSON格式交换数据,支持在服务器端应用逻辑 - 浏览器之间中继RTP/RTCP和消息。特殊化的功能有服务器端插件完成

官网地址:https://janus.conf.meetecho.com

Kurento

这是一个开源的WebRTC媒体服务器

备选方案一:从RTSP开始

我们首先尝试的方案是直接使用RTSP源,原因是海康、大华主流厂商的较新的IP摄像头均支持暴露标准化的RTSP流。

尝试播放

使用VLC播放器,打开网络串流:rtsp://admin:12345@192.168.0.196:554/ch1/main/av_stream,视频源为公司门口的海康摄像头的主码流(main,子码流为sub)。

发现可以正常播放,说明视频格式应该是标准的。VLC菜单 Tool ⇨ Codec Info查看,编码格式为H264。

浏览器无法直接使用RTSP协议,因此,需要有服务器端来处理视频源的RTSP,将其转换为:

  1. 通过WebSocket发送的视频片断,由客户端的:
    1. JSMpeg/Broadway直接解码,渲染到画布
    2. 或者,构造MP4片断Feed给MSE播放
  2. 或者,通过WebRTC网关,转换后提供给客户端的WebRTC代码处理
  3. 或者,使用浏览器插件机制,例如Chrome的NaCl
实现方式一:MSE

Streamedian的服务器端需要授权,我们选用了另外一个实现。

H5S是一个基于live555实现的开源的HTML5 RTSP网关,支持将RTSP/H264流输入转换为HTML5 MSE支持的H264,客户端基于MSE。

服务器

尝试在容器中运行H5S:

Shell
1
2
3
4
5
6
7
8
9
10
11
12
docker create --name ubuntu-16.04 -h ubuntu-16 --network local --dns 172.21.0.1 --ip 172.21.0.6 -it docker.gmem.cc/ubuntu:16.04 bash
docker start ubuntu-16.04
docker exec -it ubuntu-16.04 bash
 
apt update && apt install wget
wget https://raw.githubusercontent.com/veyesys/release/master/h5stream/H5S-r1.0.1128.16-Ubuntu-16.04-64bit.tar.gz
tar xzf H5S-r1.0.1128.16-Ubuntu-16.04-64bit.tar.gz && mv H5S-r1.0.1128.16-Ubuntu-16.04-64bit h5s-1.0
 
cd h5s-1.0
export LD_LIBRARY_PATH=`pwd`/lib/:$LD_LIBRARY_PATH
# 指定两次密码,可能H5S存在bug,不这样报身份验证失败
./h5ss rtsp://admin:12345@192.168.0.196:554/ch1/sub/av_stream admin 12345
客户端

使用H5S自带的基于MSE的客户端代码 + Chrome 49,播放后发现画面静止。控制它查看发现解码错误。打开chrome://media-internals/,发现错误Media segment did not begin with key frame. Support for such segments will be available in a future version。看样子是提供给SourceBuffer的数据不是以关键帧开始导致,未来版本的Chrome可能取消此限制。

换成Chrome 50,可以正常播放,但是流畅度较差,播放一段时间后出现卡死的情况。

小结

H5S实现不完善,在不修改源码的情况下,服务器端只能接入一路视频输入。客户端也存在不流畅、卡死的问题,不适合生产环境。

实现方式二:JSMpeg
转码进程

在上文中我们已经成功尝试了利用JSMpege + WebSocket的方式,在网页中显示摄像头捕获的视频。ffmpeg转换RTSP也是非常简单的:

Shell
1
ffmpeg -i rtsp://admin:12345@192.168.0.196:554/ch1/main/av_stream -s 427x240 -f mpegts -vcodec mpeg1video -b 800k -r 30 http://127.0.0.1:8800/12345
服务器

可以使用JSMpeg自带的简单Node.js服务器测试:

Shell
1
node ./app/websocket-relay.js 12345 8800 8801 
客户端

下面是客户端代码,默认JSMpeg会基于WebGL渲染,但是我的机器最多开到8画面,开9画面时出现警告:

Too many active WebGL contexts. Oldest context will be lost,且第一画面丢失,简单的通融方法是,第9画面使用Canvas2D渲染:

JavaScript
1
2
3
4
5
6
new JSMpeg.Player( 'ws://127.0.0.1:8801/', {
    canvas: document.getElementById( 'canvas9' ),
    autoplay: true,
    // 浏览器对WebGL context的数量有限制
    disableGl: true
} ); 

渲染截图:

jsmpeg-s9

小结

这种方式客户端解码压力较大,同时开9画面的352x288视频,我的机器上CPU占用率大概到40%左右,画面变化较为剧烈的时候会出现卡顿现象。

实现方式三:Broadway

与JSMpeg类似,Broadway也是JavaScript解码工具。关键之处是,Broadway支持的视频编码是H.264,意味着可能免去消耗服务器资源的视频重编码。

最初的尝试并不顺利,根据IP摄像头的RTSP Describe应答(SDP),我们推断其H.264 Profile为Baseline,但是不转码的情况下Broadway根本无法播放。后来查看ffmpeg的日志输出,发现其实际上使用的Profile是Main。进一步尝试,发现摄像头是可以配置为Baseline的:

hk-config

只需要把编码复杂度设置为低,H.264的Profile就从Main变为Baseline。

设置完毕后,仍然基于h264-live-player的Demo进行测试,使用如下命令行抽取原始H.264帧:

Shell
1
ffmpeg -i rtsp://admin:12345@192.168.0.196:554/ch1/main/av_stream -c:v copy -f rawvideo  -

即可免转码的进行实时视频预览了。 

此实现方式更多细节信息请参考基于Broadway的HTML5视频监控。

实现方式四:NaCl

Chrome放弃NPAPI之后,插件开发需要使用PPAPI /NaCl。目前能找到的实现有VXG Chrome Plugin,这是一个商业产品,需要授权。除了RTSP之外,还支持RTMP、HLS等协议。

插件方案的缺点是,需要安装,而且仅仅针对单种浏览器。优势则是灵活性高,理论上性能可以做的很好。

实现方式五:WebRTC

WebRTC相关的框架非常多,经过简单的比较,我们决定从Kurento入手。主要原因是:

  1. 容易扩展的模块化设计
  2. 提供Java客户端、JS客户端
  3. 可以在服务器端合成多画面,这样可以减轻客户端解码压力,特别是那些低配置的客户端
  4. 内置对RTSP协议的支持

基于Kurento搭建WebRTC服务器一文详细讨论了这种实现方式。

备选方案二:从设备SDK开始

这里的设备,主要包括:网络硬盘录像机(NVR)、视频服务器、IP摄像头。为了便于二次开发,硬件厂商都为这些设备配置的相应的SDK套件。这些SDK通常都提供了:实时码流预览、录像文件回放、播放控制(如:暂停、单帧前进、单帧后退)、获取码流基本信息、播放截图等功能。

我们的基本目标是,通过SDK得到标准化的码流,例如H264格式。具体如何操作,得看厂商的SDK,但是思路基本是:

  1. 如果SDK直接支持获取标准格式的流,例如RTSP,那么备选方案一就可以直接用上
  2. 如果SDK支持获取标准编码的视频帧,例如H264,那我们只需要将其包装为合适的容器格式,再通过RTSP/HTTP的方式发送出去
  3. 如果SDK支持获取解码后的原始图像数据,例如RGB、YV12,我们可以基于H264再次编码,然后按第2步方式处理。这种方式对服务器性能要求比较高,CPU压力较大,PC机处理不了多少个通道
  4. 如果都不支持,只提供了封装好的播放控件 —— 这个就比较悲催了,不过通过OS底层API,例如Windows的GDI应该也是可以实现,否则那些屏幕录像软件怎么做的呢?
海康SDK

根据Linux版本的海康设备网络编程指南的描述,我们应该可以:

  1. 调用NET_DVR_Init进行SDK初始化
  2. 调用NET_DVR_Login登陆到目标设备
  3. 调用NET_DVR_RealPlay进行播放,此时返回一个实时播放句柄
    1. 如果设备支持RTSP协议取流:针对上述句柄调用NET_DVR_SetStandardDataCallBack,可以设置一个标准的数据回调函数,此回调会接受到标准码流,这对应上面的第1种思路
    2. 如果设备不支持RTSP协议取流:针对上述句柄调用NET_DVR_SetRealDataCallBack,然后通过PlayM4播放库中的PlayM4_SetDecCallBack回调得到yv12格式的原始图像。这对应上面的第3种思路
示例代码

cmake构建配置:

CMakeLists.txt
1
2
3
4
5
6
7
8
cmake_minimum_required(VERSION 3.6)
project(hikvision)
 
include_directories(/home/alex/CPP/lib/hcnedsdk/include)
 
set(SOURCE_FILES getstream.cpp)
add_executable(getstream ${SOURCE_FILES})
target_link_libraries(getstream /home/alex/CPP/lib/hcnedsdk/lib/libhcnetsdk.so)

 C++代码:

getstream.cpp
C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
#include <HCNetSDK.h>
#include <stdio.h>
#include <cstring>
#include <unistd.h>
 
// RTSP协议取流
void CALLBACK cbStdData( LONG lRealHandle, DWORD dwDataType, BYTE *pBuffer, DWORD dwBufSize, DWORD dwUser ) {
    switch ( dwDataType ) {
        case NET_DVR_SYSHEAD:        // 系统头数据,回调的第一个包是40字节的文件头
            break;
        case NET_DVR_STREAMDATA:     // 基于私有协议时:视频流数据(包括复合流和音视频分开的视频流数据)
            break;
        case NET_DVR_STD_VIDEODATA:  // 基于标准协议时:标准视频流数据(RTP包)
            break;
        case NET_DVR_STD_AUDIODATA:  // 基于标准协议时:标准音频流数据
            break;
        case NET_DVR_SDP:            // SDP信息(RTSP传输时有效)
            break;
        case NET_DVR_PRIVATE_DATA:   // 私有数据,包括智能信息叠加等
            break;
    }
}
 
int main() {
    // SDK初始化
    BOOL result = NET_DVR_Init();
    if ( !result ) return 1;
 
    // 同步登陆
    NET_DVR_USER_LOGIN_INFO struLoginInfo = { 0 };
    struLoginInfo.bUseAsynLogin = 0;
    strcpy( struLoginInfo.sDeviceAddress, "192.168.0.196" );
    struLoginInfo.wPort = 8000;
    strcpy( struLoginInfo.sUserName, "admin" );
    strcpy( struLoginInfo.sPassword, "12345" );
    NET_DVR_DEVICEINFO_V40 struDevInfo = { 0 };
    LPNET_DVR_DEVICEINFO_V30 lpDevInfo30;
    long lUserID = NET_DVR_Login_V40( &struLoginInfo, &struDevInfo );
    if ( lUserID < 0 ) {
        printf( "登陆失败,错误码 %d\n", NET_DVR_GetLastError());
        NET_DVR_Cleanup();
        return 1;
    } else {
        lpDevInfo30 = &struDevInfo.struDeviceV30;
        printf( "成功登陆到设备:%s\n", lpDevInfo30->sSerialNumber );
        printf( "SDK字符串编码方式(1 GB2312,2 GBK,3 BIG5,6 UTF-8):%d\n", struDevInfo.byCharEncodeType );
        printf( "设备类型(31 高清网络摄像机):%d\n", lpDevInfo30->wDevType );
        printf( "模拟通道起始号:%d,模拟通道个数%d,数字通道起始号:%d,数字通道个数%d\n", lpDevInfo30->byStartChan, lpDevInfo30->byChanNum,
                lpDevInfo30->byStartDChan, lpDevInfo30->byIPChanNum + lpDevInfo30->byHighDChanNum << 8 );
        printf( "主码流是否支持RTSP方式:%s,子码流是否支持RTSP方式:%s\n", lpDevInfo30->byMainProto > 0 ? "是" : "否",
                lpDevInfo30->bySubProto > 0 ? "是" : "否" );
    }
 
    // 启动预览
    NET_DVR_PREVIEWINFO struPrevInfo = { 0 };
    struPrevInfo.hPlayWnd = NULL;    // Linux 64 位系统不支持软解码功能
    struPrevInfo.lChannel = 1;       // 预览通道号
    struPrevInfo.dwStreamType = 0;   // 0-主码流, 1-子码流, 2-码流 3, 3-码流 4,以此类推
    struPrevInfo.dwLinkMode = 0;     // 0- TCP 方式, 1- UDP 方式, 2- 组播方式, 3- RTP 方式, 4-RTP/RTSP, 5-RSTP/HTTP
    struPrevInfo.bBlocked = 1;       // 0- 非阻塞取流, 1- 阻塞取流
    struPrevInfo.byProtoType = 1;    // 应用层取流协议使用RTSP
    LONG lRealHandle = NET_DVR_RealPlay_V40( lUserID, &struPrevInfo, NULL, NULL );
    if ( lRealHandle == -1 ) {
        printf( "启动预览失败,错误码 %d\n", NET_DVR_GetLastError());
        NET_DVR_Logout( lUserID );
        NET_DVR_Cleanup();
        return 1;
    }
 
    if ( lpDevInfo30->byMainProto ) {
        printf( "设置获取标准码流的回调\n" );
        // 仅支持对 支持RTSP协议取流的设备的 标准码流回调
        NET_DVR_SetStandardDataCallBack( lRealHandle, cbStdData, NULL );
    }
 
    sleep( 120 );
    // 停止预览
    NET_DVR_StopRealPlay( lRealHandle );
    // 登出
    NET_DVR_Logout( lUserID );
    // SDK清理
    NET_DVR_Cleanup();
    return 0;
}

运行脚本:

Shell
1
2
3
export HKLIB_HOME=/home/alex/CPP/lib/hcnedsdk/lib
export LD_LIBRARY_PATH=$HKLIB_HOME:$HKLIB_HOME/HCNetSDKCom
./getstream 

此程序运行后,会自动获取到基于RTSP协议的媒体流,回调函数会反复被调用:

  1. 第一次调用为40字节的头,不太清楚有什么用
  2. 第二次调用传递了SDP
  3. 后续调用传递标准音视频数据,其内容是RTP封包
总结

基于HTM5的视频监控,媒体流从采集设备到浏览器,主要路径如下图所示:

h5vs-dataflow

对上图的说明如下:

  1. 在设备层,需要以某种方式获得码流,以流协议的方式发送出去。最常用的方式是RTSP/RTP。流的可能获取路径为:
    1. 设备直接暴露RTSP协议端点,并且发送标准码流
    2. 设备SDK允许获取标准码流,需要自己以RTSP协议发送
    3. 设备SDK允许获得解码后的逐帧,需要直接编码为H264,然后以RTSP发送
  2. 流媒体层通常需要引入专门的流媒体服务器,这类服务器能够在内部进行各种流协议的转换,可以解除客户端对特定流协议的依赖
  3. 客户端和服务器端的传输方式,可以有TCP、HTTP、P2P(WebRTC)、WebSocket等多种。其中
    1. 直接的TCP协议浏览器是不支持的,这意味着RTSP/RTMP等协议,在浏览器端必须要有插件才可以使用
    2. WebSocket通常配合JSMpeg或者MSE使用,由程序向JSMpeg/MSE不断Feed视频帧
  4. 客户端解码展示的技术主要有三类:
    1. 浏览器内置的解码能力,主要通过video标签,MSE属于此类
    2. JavaScript软解码,主要是JSMpeg、Broadway
    3. 插件机制,例如Chrome的NaCl

能够免于引入流媒体层的方案,需要:设备能直接暴露标准码流的RTSP端点,并且安装浏览器插件。缺点也很明显,一个是设备的访问密码暴露给了客户端,第二个是目前没有成熟、开源的插件可用。我相信主要原因是合理技术方向不在于此,没人愿意去开发。

直接使用设备层的RTSP端点,可能存在兼容性问题。一个是它发送的码流是否标准化,第二个是市场上有多少设备没有暴露RTSP端点。

客户端方面,JSMpeg是兼容性较好的方案,WebRTC/MSE都有部分平台不支持(但是桌面级的浏览器大部分支持)。JSMpeg的缺点是:

  1. 如果基于WebGL渲染,受限于浏览器WebGL上下文最大数量,多画面可能无法渲染。某些流媒体服务器支持在服务器端合成多画面Grid,可以规避此缺点
  2. 如果基于Canvas2D渲染,画质较差(我的机器上还有莫名其妙的斜线)
  3. 对码流格式要求严格,仅仅支持MPEG-TS,此格式压缩比差,网络带宽占用大
  4. 性能相对较差,尽管使用了MPEG-TS这种简单的视频格式,基于JavaScript解码渲染仍然使客户端压力较大。我的机器(i7-4940MX / Quadro K5100M / Ubuntu 14.04 LTS)上会出现卡顿情况

和JSMpeg类似的库是Broadway,后者能够进行Baseline的H.264解码。如果设备支持Baseline H.264输出,使用Broadway可以很好的解决服务器端转码导致的资源消耗问题。

附录
参考资料
  1. Audio and Video Delivery
  2. W3C Recommendation - Media Source Extensions™
  3. WebRTC Project Home
  4. HTML5 视频直播(三)
← 2017年8月坝上草原
使用Jansson处理JSON →
7 Comments On This Topic
  1. 回复
    jsonwu5
    2018/11/07

    这篇文章总结得很棒!最近也在研究HTML5播放实时流的相关技术,这篇文章可以说几乎囊括了目前主流的解决方案,每一个展开都能单独写一篇文章。对于想要了解HTML5播放实时流的人来说受益匪浅。

    • 回复
      Alex
      2018/11/07

      :D

  2. 回复
    SouthernPL
    2018/11/25

    你好,streamedian提供了浏览器端的解码编码插件,可以将通过websocket传输的rtsp数据包转化成mp4格式然后通过video播放。请问这种方式也属于文中提及的MSE类吗? 这种方式在播放性能上是不是也有类似于jsmpeg的弊端?期待回复 3430733697@qq.com

    • 回复
      Alex
      2018/11/26

      你好,jsmpeg是通过JavaScript代码,对MPEG进行解码,然后逐帧绘制到canvas上实现播放,性能肯定是相对较弱的
      rtsp/rtmp之类的流媒体性能对播放性能没有什么影响
      streamedian是基于MSE实现的,MSE依赖于浏览器自身实现的解码器,性能和Native应用程序无异。

  3. 回复
    myseemylife
    2019/08/15

    目前来说websocket + +wasm + MSE处理 h264 在PC上 已经几乎可以达到浏览器插件的效果。尴尬的是MSE对HEVC的支持。

  4. 回复
    骆飞
    2019/12/27

    大佬太强了,看了你所有关于视频监控的文章,受益匪浅,现在做安防监控,对接视频厂家 jsmpeg, FFmoeg,kurento,rtmp flasher播放都试过了 但是效果都不太理想,方便加个qq吗 有问题请教

    • 回复
      Alex
      2019/12/27

      这两年完全脱离相关领域了呢,实在不好意思:(

Leave a Reply Cancel reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code class="" title="" data-url=""> <del datetime=""> <em> <i> <q cite=""> <strike> <strong> <pre class="" title="" data-url=""> <span class="" title="" data-url="">

Related Posts

  • 基于Kurento搭建WebRTC服务器
  • 基于Broadway的HTML5视频监控
  • 基于MinGW的海康视频监控开发
  • 实时通信协议族
  • 基于AngularJS开发Web应用

Recent Posts

  • Investigating and Solving the Issue of Failed Certificate Request with ZeroSSL and Cert-Manager
  • A Comprehensive Study of Kotlin for Java Developers
  • 背诵营笔记
  • 利用LangChain和语言模型交互
  • 享学营笔记
ABOUT ME

汪震 | Alex Wong

江苏淮安人,现居北京。目前供职于腾讯云,专注容器方向。

GitHub:gmemcc

Git:git.gmem.cc

Email:gmemjunk@gmem.cc@me.com

ABOUT GMEM

绿色记忆是我的个人网站,域名gmem.cc中G是Green的简写,MEM是Memory的简写,CC则是我的小天使彩彩名字的简写。

我在这里记录自己的工作与生活,同时和大家分享一些编程方面的知识。

GMEM HISTORY
v2.00:微风
v1.03:单车旅行
v1.02:夏日版
v1.01:未完成
v0.10:彩虹天堂
v0.01:阳光海岸
MIRROR INFO
Meta
  • Log in
  • Entries RSS
  • Comments RSS
  • WordPress.org
Recent Posts
  • Investigating and Solving the Issue of Failed Certificate Request with ZeroSSL and Cert-Manager
    In this blog post, I will walk ...
  • A Comprehensive Study of Kotlin for Java Developers
    Introduction Purpose of the Study Understanding the Mo ...
  • 背诵营笔记
    Day 1 Find Your Greatness 原文 Greatness. It’s just ...
  • 利用LangChain和语言模型交互
    LangChain是什么 从名字上可以看出来,LangChain可以用来构建自然语言处理能力的链条。它是一个库 ...
  • 享学营笔记
    Unit 1 At home Lesson 1 In the ...
  • K8S集群跨云迁移
    要将K8S集群从一个云服务商迁移到另外一个,需要解决以下问题: 各种K8S资源的迁移 工作负载所挂载的数 ...
  • Terraform快速参考
    简介 Terraform用于实现基础设施即代码(infrastructure as code)—— 通过代码( ...
  • 草缸2021
    经过四个多月的努力,我的小小荷兰景到达极致了状态。

  • 编写Kubernetes风格的APIServer
    背景 前段时间接到一个需求做一个工具,工具将在K8S中运行。需求很适合用控制器模式实现,很自然的就基于kube ...
  • 记录一次KeyDB缓慢的定位过程
    环境说明 运行环境 这个问题出现在一套搭建在虚拟机上的Kubernetes 1.18集群上。集群有三个节点: ...
  • eBPF学习笔记
    简介 BPF,即Berkeley Packet Filter,是一个古老的网络封包过滤机制。它允许从用户空间注 ...
  • IPVS模式下ClusterIP泄露宿主机端口的问题
    问题 在一个启用了IPVS模式kube-proxy的K8S集群中,运行着一个Docker Registry服务 ...
  • 念爷爷
      今天是爷爷的头七,十二月七日、阴历十月廿三中午,老人家与世长辞。   九月初,回家看望刚动完手术的爸爸,发

  • 6 杨梅坑

  • liuhuashan
    深圳人才公园的网红景点 —— 流花山

  • 1 2020年10月拈花湾

  • 内核缺陷触发的NodePort服务63秒延迟问题
    现象 我们有一个新创建的TKE 1.3.0集群,使用基于Galaxy + Flannel(VXLAN模式)的容 ...
  • Galaxy学习笔记
    简介 Galaxy是TKEStack的一个网络组件,支持为TKE集群提供Overlay/Underlay容器网 ...
TOPLINKS
  • Zitahli's blue 91 people like this
  • 梦中的婚礼 64 people like this
  • 汪静好 61 people like this
  • 那年我一岁 36 people like this
  • 为了爱 28 people like this
  • 小绿彩 26 people like this
  • 彩虹姐姐的笑脸 24 people like this
  • 杨梅坑 6 people like this
  • 亚龙湾之旅 1 people like this
  • 汪昌博 people like this
  • 2013年11月香山 10 people like this
  • 2013年7月秦皇岛 6 people like this
  • 2013年6月蓟县盘山 5 people like this
  • 2013年2月梅花山 2 people like this
  • 2013年淮阴自贡迎春灯会 3 people like this
  • 2012年镇江金山游 1 people like this
  • 2012年徽杭古道 9 people like this
  • 2011年清明节后扬州行 1 people like this
  • 2008年十一云龙公园 5 people like this
  • 2008年之秋忆 7 people like this
  • 老照片 13 people like this
  • 火一样的六月 16 people like this
  • 发黄的相片 3 people like this
  • Cesium学习笔记 90 people like this
  • IntelliJ IDEA知识集锦 59 people like this
  • 基于Kurento搭建WebRTC服务器 38 people like this
  • Bazel学习笔记 37 people like this
  • PhoneGap学习笔记 32 people like this
  • NaCl学习笔记 32 people like this
  • 使用Oracle Java Mission Control监控JVM运行状态 29 people like this
  • Ceph学习笔记 27 people like this
  • 基于Calico的CNI 27 people like this
Tag Cloud
ActiveMQ AspectJ CDT Ceph Chrome CNI Command Cordova Coroutine CXF Cygwin DNS Docker eBPF Eclipse ExtJS F7 FAQ Groovy Hibernate HTTP IntelliJ IO编程 IPVS JacksonJSON JMS JSON JVM K8S kernel LB libvirt Linux知识 Linux编程 LOG Maven MinGW Mock Monitoring Multimedia MVC MySQL netfs Netty Nginx NIO Node.js NoSQL Oracle PDT PHP Redis RPC Scheduler ServiceMesh SNMP Spring SSL svn Tomcat TSDB Ubuntu WebGL WebRTC WebService WebSocket wxWidgets XDebug XML XPath XRM ZooKeeper 亚龙湾 单元测试 学习笔记 实时处理 并发编程 彩姐 性能剖析 性能调优 文本处理 新特性 架构模式 系统编程 网络编程 视频监控 设计模式 远程调试 配置文件 齐塔莉
Recent Comments
  • qg on Istio中的透明代理问题
  • heao on 基于本地gRPC的Go插件系统
  • 黄豆豆 on Ginkgo学习笔记
  • cloud on OpenStack学习笔记
  • 5dragoncon on Cilium学习笔记
  • Archeb on 重温iptables
  • C/C++编程:WebSocketpp(Linux + Clion + boostAsio) – 源码巴士 on 基于C/C++的WebSocket库
  • jerbin on eBPF学习笔记
  • point on Istio中的透明代理问题
  • G on Istio中的透明代理问题
  • 绿色记忆:Go语言单元测试和仿冒 on Ginkgo学习笔记
  • point on Istio中的透明代理问题
  • 【Maven】maven插件开发实战 – IT汇 on Maven插件开发
  • chenlx on eBPF学习笔记
  • Alex on eBPF学习笔记
  • CFC4N on eBPF学习笔记
  • 李运田 on 念爷爷
  • yongman on 记录一次KeyDB缓慢的定位过程
  • Alex on Istio中的透明代理问题
  • will on Istio中的透明代理问题
  • will on Istio中的透明代理问题
  • haolipeng on 基于本地gRPC的Go插件系统
  • 吴杰 on 基于C/C++的WebSocket库
©2005-2025 Gmem.cc | Powered by WordPress | 京ICP备18007345号-2