<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>绿色记忆 &#187; JSMpeg</title>
	<atom:link href="https://blog.gmem.cc/tag/jsmpeg/feed" rel="self" type="application/rss+xml" />
	<link>https://blog.gmem.cc</link>
	<description></description>
	<lastBuildDate>Mon, 06 Apr 2026 02:15:06 +0000</lastBuildDate>
	<language>en-US</language>
		<sy:updatePeriod>hourly</sy:updatePeriod>
		<sy:updateFrequency>1</sy:updateFrequency>
	<generator>http://wordpress.org/?v=3.9.14</generator>
	<item>
		<title>HTML5视频监控技术预研</title>
		<link>https://blog.gmem.cc/research-on-html5-video-surveillance</link>
		<comments>https://blog.gmem.cc/research-on-html5-video-surveillance#comments</comments>
		<pubDate>Mon, 28 Aug 2017 05:49:57 +0000</pubDate>
		<dc:creator><![CDATA[Alex]]></dc:creator>
				<category><![CDATA[C++]]></category>
		<category><![CDATA[Graphic]]></category>
		<category><![CDATA[HTML]]></category>
		<category><![CDATA[JavaScript]]></category>
		<category><![CDATA[JSMpeg]]></category>
		<category><![CDATA[MSE]]></category>
		<category><![CDATA[Multimedia]]></category>
		<category><![CDATA[WebRTC]]></category>
		<category><![CDATA[视频监控]]></category>

		<guid isPermaLink="false">https://blog.gmem.cc/?p=15526</guid>
		<description><![CDATA[<p>引言 安防类项目中通常都有视频监控方面的需求。视频监控客户端主要是Native应用的形式，在Web端需要利用NPAPI、ActiveX之类的插件技术实现。 但是，IE式微，Chrome也放弃了NPAPI，另一方面，监控设备硬件厂商的视频输出格式则逐渐标准化。这让基于开放、标准化接口的Web视频监控成为可能。 本文讨论以HTML5及其衍生技术为基础的B/S架构实时视频监控解决方案。主要包括两方面的内容： 视频编码、流媒体基础知识，以及相关的库、框架的介绍 介绍可以用于视频监控的HTML5特性，例如媒体标签、MSE、WebRTC，以及相关的库、框架 本文仅仅简介若干种备选的解决方案，本站其它文章进行了更加深入的探讨： H.264学习笔记 实时通信协议族 基于Kurento搭建WebRTC服务器 基于Broadway的HTML5视频监控 音视频编码 音频、视频的编码（Codec，压缩）算法有很多，不同浏览器对音视频的编码算法的支持有差异。H264这样的监控设备常用的视频编码格式，主流浏览器都有某种程度的支持。 常见的音频编码算法包括： MP3, Vorbis, AAC；常见的视频编码算法包括： H.264, HEVC, VP8, VP9。 编码后的音频、视频通常被封装在一个比特流容器格式（container）中，这些格式中常见的有： <a class="read-more" href="https://blog.gmem.cc/research-on-html5-video-surveillance">[...]</a></p>
<p>The post <a rel="nofollow" href="https://blog.gmem.cc/research-on-html5-video-surveillance">HTML5视频监控技术预研</a> appeared first on <a rel="nofollow" href="https://blog.gmem.cc">绿色记忆</a>.</p>
]]></description>
				<content:encoded><![CDATA[<div class="wri_content_clear_both"><div class="blog_h1"><span class="graybg">引言</span></div>
<p>安防类项目中通常都有视频监控方面的需求。视频监控客户端主要是Native应用的形式，在Web端需要利用NPAPI、ActiveX之类的插件技术实现。</p>
<p>但是，IE式微，Chrome也放弃了NPAPI，另一方面，监控设备硬件厂商的视频输出格式则逐渐标准化。这让基于开放、标准化接口的Web视频监控成为可能。</p>
<p>本文讨论以HTML5及其衍生技术为基础的B/S架构实时视频监控解决方案。主要包括两方面的内容：</p>
<ol>
<li>视频编码、流媒体基础知识，以及相关的库、框架的介绍</li>
<li>介绍可以用于视频监控的HTML5特性，例如媒体标签、MSE、WebRTC，以及相关的库、框架</li>
</ol>
<p>本文仅仅简介若干种备选的解决方案，本站其它文章进行了更加深入的探讨：</p>
<ol>
<li><a href="/h264-study-note">H.264学习笔记</a></li>
<li><a href="/realtime-communication-protocols">实时通信协议族</a></li>
<li><a href="/webrtc-server-basedon-kurento">基于Kurento搭建WebRTC服务器</a></li>
<li><a href="/html5-vs-with-broadway">基于Broadway的HTML5视频监控</a></li>
</ol>
<div class="blog_h1"><span class="graybg">音视频编码</span></div>
<p>音频、视频的编码（Codec，压缩）算法有很多，不同浏览器对音视频的编码算法的支持有<a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Supported_media_formats#Browser_compatibility">差异</a>。H264这样的监控设备常用的视频编码格式，主流浏览器都有某种程度的支持。</p>
<p>常见的音频编码算法包括： MP3, Vorbis, AAC；常见的视频编码算法包括： H.264, HEVC, VP8, VP9。</p>
<p>编码后的音频、视频通常被封装在一个比特流容器格式（container）中，这些格式中常见的有： MP4, FLV, WebM,  ASF, ISMA等。</p>
<div class="blog_h2"><span class="graybg">JSMpeg</span></div>
<p>视频解码工作通常由浏览器本身负责，配合video实现视频播放。</p>
<p>现代浏览器的JS引擎性能较好，因此出现了纯粹由JS实现的解码器<a href="https://github.com/phoboslab/jsmpeg">JSMpeg</a>，它能够解码视频格式MPEG1、音频格式MP2。支持通过Ajax加载静态视频文件，支持低延迟（小于50ms）的流式播放（通过WebSocket）。JSMpeg包括以下组件：</p>
<ol>
<li>MPEG-TS分流器（demuxer）。muxer负责把视频、音频、字幕打包成一种容器格式，demuxer则作相反的工作</li>
<li>MPEG1视频解码器</li>
<li>MP2音频解码器</li>
<li>WebGL渲染器、Canvas2D渲染器</li>
<li>WebAudio音频输出组件</li>
</ol>
<p>JSMpeg的优势在于兼容性好，几乎所有现代浏览器都能运行JSMpeg。</p>
<div class="blog_h3"><span class="graybg">性能</span></div>
<p>JSMpeg不能使用硬件加速。在iPhone 5S这样的设备上，JSMpeg能够处理720p@30fps视频。</p>
<p>比起现代解码器，MPEG1压缩率较低，因而需要更大的带宽。720p的视频大概占用250KB/s的带宽。</p>
<div class="blog_h3"><span class="graybg">示例</span></div>
<p>下面我们尝试利用ffmpeg编码本地摄像头视频，并通过JSMpeg播放。</p>
<p>创建一个NPM项目，安装依赖：</p>
<pre class="crayon-plain-tag">npm install jsmpeg --save
npm install ws --save</pre>
<p>JSMpeg提供了一个中继器，能够把基于HTTP的MPEG-TS流转换后通过WebSocket发送给客户端。此脚本需要<a href="https://github.com/phoboslab/jsmpeg/blob/master/websocket-relay.js">到Github下载</a>。 下面的命令启动一个中继器：</p>
<pre class="crayon-plain-tag">node ./app/websocket-relay.js 12345 8800 8801
# Listening for incomming MPEG-TS Stream on http://127.0.0.1:8800/&lt;secret&gt;
# Awaiting WebSocket connections on ws://127.0.0.1:8801/
# 实际上在所有网络接口上监听，并非仅仅loopback</pre>
<p>下面的命令捕获本地摄像头（Linux），并编码为MPEG1格式，然后发送到中继器：</p>
<pre class="crayon-plain-tag"># 从摄像头/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</pre>
<p>上述命令执行后，中继器控制台上打印：</p>
<pre class="crayon-plain-tag">Stream Connected: ::ffff:127.0.0.1:42399</pre>
<p>客户端代码：</p>
<pre class="crayon-plain-tag">var player = new JSMpeg.Player( 'ws://127.0.0.1:8801/', {
    canvas: document.getElementById( 'canvas' ),
    autoplay: true
} ); </pre>
<div class="blog_h2"><span class="graybg">Broadway</span></div>
<p><a href="https://github.com/mbebenita/Broadway">Broadway</a>是一个基于JavaScript的H.264解码器，其源码来自于Android的H.264解码器，利用Emscripten转译成了JavaScript，之后利用Google的Closure编译器优化，并针对WebGL进一步优化。</p>
<p>注意：Broadway仅仅支持Baseline这个H.264 Profile。</p>
<p><a href="https://github.com/131/h264-live-player">h264-live-player</a>是基于Broadway实现的播放器，允许通过WebSocket来传输NAL单元（原始H.264帧），并在画布上渲染。我们运行一下它的示例应用：</p>
<pre class="crayon-plain-tag">git clone https://github.com/131/h264-live-player.git
cd h264-live-player
npm install</pre>
<p>因为我的机器是Linux，所以修改h264-live-player/lib/ffmpeg.js， 把ffpmeg的参数改为：</p>
<pre class="crayon-plain-tag">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',
    '-'
];</pre>
<p>然后运行<pre class="crayon-plain-tag">node server-ffmpeg</pre>，打开http://127.0.0.1:8080/，可以看到自己摄像头传来的H.264码流，效果还不错。</p>
<div class="blog_h2"><span class="graybg">服务器端技术</span></div>
<div class="blog_h3"><span class="graybg">ffpmeg</span></div>
<p>老牌的编解码库，支持很多的音频、视频格式的编解码，支持多种容器格式，支持多种流协议。关于ffpmeg的详细介绍参见<a href="/linux-command-faq#ffmpeg">Linux命令知识集锦</a>。</p>
<p>ffpmeg除了提供开发套件之外，还有一个同名的命令行工具，直接使用它就可以完成很多编解码、流转换的工作。</p>
<p>类似的库是libav，ffpmeg和它的功能非常相似，特性更多一些。</p>
<div class="blog_h3"><span class="graybg">x264</span></div>
<p>官网自称是最好的H.264编码器。特性包括：</p>
<ol>
<li>提供一流的性能、压缩比。特别是性能方面，可以在普通PC上并行编码4路或者更多的1080P流</li>
<li>提供最好的视频质量，具有最高级的心理视觉优化</li>
<li>支持多种不同应用程序所需要的特性，例如电视广播、蓝光低延迟视频应用、Web视频</li>
</ol>
<div class="blog_h1"><span class="graybg">流媒体技术</span></div>
<p>有了上面介绍的HTML5标签、合理编码的视频格式，就可以实现简单的监控录像回放了。但是，要进行实时监控画面预览则没有这么简单，必须依赖流媒体技术实现。</p>
<div class="blog_h2"><span class="graybg">流媒体</span></div>
<p>所谓多媒体（Multimedia）是指多种内容形式 —— 文本、音频、视频、图片、动画等的组合。</p>
<p>所谓流媒体，就是指源源不断的由提供者产生，并持续的被终端用户接收、展示的多媒体，就像水流一样。现实世界中的媒体，有些天生就是流式的，例如电视、广播，另外一些则不是，例如书籍、CD。</p>
<p>流媒体技术（从传递媒体角度来看）可以作为文件下载的替代品。</p>
<p><span style="background-color: #c0c0c0;">流媒体技术关注的是如何传递媒体，而不是如何编码媒体</span>，具体的实现就是各种流媒体协议。封装后的媒体比特流（容器格式）由流媒体服务器递送到流媒体客户端。流媒体协议可能对底层容器格式、编码格式有要求，也可能没有任何要求。</p>
<div class="blog_h2"><span class="graybg">直播</span></div>
<p>直播流（Live streaming）和静态文件播放的关键差异：</p>
<ol>
<li>点播的目标文件通常位于服务器上，具有一定的播放时长、文件大小。浏览器可以使用渐进式下载，一边下载一边播放</li>
<li>直播不存在播放起点、终点。它表现为一种流的形式，源源不断的从视频采集源通过服务器，传递到客户端</li>
<li>直播流通常是自适应的（adaptive），其码率随着客户端可用带宽的变化，可能变大、变小，以尽可能消除延迟</li>
</ol>
<p>流媒体技术不但可以用于监控画面预览，也可以改善录像播放的用户体验，比起简单的静态文件回放，流式回放具有以下优势：</p>
<ol>
<li>延迟相对较低，播放能够尽快开始</li>
<li>自适应流可以避免卡顿</li>
</ol>
<div class="blog_h2"><span class="graybg">流协议</span></div>
<p>主流的用于承载视频流的流媒体协议包括：</p>
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 18%; text-align: center;">协议</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>HLS</td>
<td>
<p>HTTP实时流（HTTP Live Streaming），由苹果开发，基于HTTP协议</p>
<p>HLS的工作原理是，把整个流划分成一个个较小的文件，客户端在建立流媒体会话后，基于HTTP协议下载流片段并播放。客户端可以从多个服务器（源）下载流。</p>
<p>在建立会话时，客户端需要下载extended M3U (m3u8) 播放列表文件，其中包含了MPEG-2 TS（Transport Stream）容器格式的视频的列表。在播放完列表中的文件后，需要再次下载<span style="color: #444444;">m3u8，如此循环</span></p>
<p>此协议在移动平台上支持较好，目前的Android、iOS版本都支持</p>
<p>此协议的重要缺点是高延迟（5s以上通常），要做到低延迟会导致频繁的缓冲（下载新片段）并对服务器造成压力，不适合视频监控</p>
<p>播放HLS流的HTML代码片段：</p>
<pre class="crayon-plain-tag">&lt;video src="http://movie.m3u8" height="329" width="480"&gt;&lt;/video&gt;</pre>
</td>
</tr>
<tr>
<td>RTMP</td>
<td>
<p>实时消息协议（Real Time Messaging Protocol），由Macromedia（Adobe）开发。此协议实时性很好，需要Flash插件才能在客户端使用，但是Adobe已经打算在不久的将来放弃对Flash的支持了
<p>有一个开源项目<a href="https://github.com/Bilibili/flv.js">HTML5 FLV Player</a>，它支持在没有Flash插件的情况下，播放Flash的视频格式FLV。此项目依赖于<a href="https://w3c.github.io/media-source/">MSE</a>，支持以下特性：</p>
<ol>
<li>支持H.264 + AAC/MP3编码的FLV容器格式的播放</li>
<li>分段（segmented）视频播放</li>
<li>基于HTTP的FLV低延迟实时流播放</li>
<li>兼容主流浏览器</li>
<li>资源占用低，可以使用客户端的硬件加速</li>
</ol>
</td>
</tr>
<tr>
<td>RTSP</td>
<td>
<p>实时流协议（Real Time Streaming Protocol），由RealNetworks等公司开发。此协议负责控制通信端点（Endpoint）之间的媒体会话（media sessions） —— 例如播放、暂停、录制。通常需要结合：实时传输协议（Real-time Transport Protocol）、实时控制协议（Real-time Control Protocol）来实现视频流本身的传递</p>
<p>大部分浏览器没有对RTSP提供原生的支持</p>
<p>RTSP 2.0版本目前正在开发中，和旧版本不兼容</p>
</td>
</tr>
<tr>
<td>MPEG-DASH</td>
<td>
<p>基于HTTP的动态自适应流（Dynamic Adaptive Streaming over HTTP），它类似于HLS，也是把流切分为很小的片段。DASH为支持为每个片段提供多种码率的版本，以满足不同客户带宽</p>
<p>协议的客户端根据自己的可用带宽，选择尽可能高（避免卡顿、重新缓冲）的码率进行播放，并根据网络状况实时调整码率</p>
<p>DASH不限制编码方式，你可以使用H.265, H.264, VP9等视频编码算法</p>
<p>Chrome 24+、Firefox 32+、Chrome for Android、IE 10+支持此格式</p>
<p>类似于HLS的高延迟问题也存在</p>
</td>
</tr>
<tr>
<td>WebRTC</td>
<td>
<p>WebRTC是一整套API，为浏览器、移动应用提供实时通信（RealTime Communications）能力。它包含了流媒体协议的功能，但是不是以协议的方式暴露给开发者的</p>
<p>WebRTC支持Chrome 23+、Firefox 22+、Chrome for Android，提供Java / Objective-C绑定</p>
<p>WebRTC主要有三个职责：</p>
<ol>
<li>捕获客户端音视频，对应接口MediaStream（也就是getUserMedia）</li>
<li>音视频传输，对应接口RTCPeerConnection</li>
<li>任意数据传输，对应接口RTCDataChannel</li>
</ol>
<p>WebRTC内置了点对点的支持，也就是说流不一定需要经过服务器中转</p>
</td>
</tr>
</tbody>
</table>
<div class="blog_h2"><span class="graybg">服务器端技术</span></div>
<p>视频监控通常都是CS模式（而非P2P），在服务器端，你需要部署流媒体服务。</p>
<div class="blog_h3"><span class="graybg">GStreamer</span></div>
<p><a href="https://gstreamer.freedesktop.org/">这是</a>一个开源的跨平台多媒体框架。通过它你可以构建各种各样的媒体处理组件，包括流媒体组件。通过插件机制，GStreamer支持上百种编码格式，包括MPEG-1, MPEG-2, MPEG-4, H.261, H.263, H.264, RealVideo, MP3, WMV, FLV</p>
<p><a href="https://www.kurento.org/">Kurento</a>、<a href="http://www.flumotion.net/features/">Flumotion</a>是基于GStreamer构建的流媒体服务器软件。</p>
<div class="blog_h3"><span class="graybg">Live555</span></div>
<p><a href="http://www.live555.com/">Live555</a>是流媒体服务开发的基础库，支持 RTP/RTCP/RTSP/SIP等协议，适合在硬件资源受限的情况下使用（例如嵌入式设备）。</p>
<p>基于Live555的软件包括：</p>
<ol>
<li>Live555媒体服务器，完整的RTSP服务器</li>
<li>openRTSP，一个命令行程序，支持提供RTSP流、接收RTSP流、把RTSP流中的媒体录像到磁盘</li>
<li>playSIP，可以进行VoIP通话</li>
<li>liveCaster，支持组播的MP3流媒体服务</li>
</ol>
<div class="blog_h3"><span class="graybg">其它</span></div>
<p>流媒体服务实现有很多，它们中的一些在最初针对特定的流协议，大部分都走向多元化。例如，Red5是一个RTMP流媒体服务器，Wowza是一个综合的流媒体服务器，支持WebRTC的流媒体服务在后面的章节介绍。</p>
<div class="blog_h1"><span class="graybg">HTML5媒体标签</span></div>
<p>HTML5支持<pre class="crayon-plain-tag">&lt;audio&gt;</pre>和<pre class="crayon-plain-tag">&lt;video&gt;</pre>标签（两者都对应了HTMLMediaElement的子类型）以实现视频、音频的播放。</p>
<div class="blog_h2"><span class="graybg">&lt;audio&gt;</span></div>
<p>此标签用于在浏览器中创建一个纯音频播放器。播放静态文件的示例：</p>
<pre class="crayon-plain-tag">&lt;audio controls preload="auto"&gt;
    &lt;source src="song.mp3" type="audio/mpeg"&gt;
    &lt;!-- 备选格式，如果浏览器不支持mp3 --&gt;
    &lt;source src="song.ogg" type="audio/ogg"&gt;
    &lt;!-- 如果浏览器不支持audio标签，显示下面的连接 --&gt;
    &lt;a href="audiofile.mp3"&gt;download audio&lt;/a&gt;
&lt;/audio&gt;</pre>
<div class="blog_h2"><span class="graybg">&lt;video&gt;</span></div>
<p>此标签用于在浏览器中创建一个视频播放器。播放静态文件的示例：</p>
<pre class="crayon-plain-tag">&lt;!-- poster指定预览图，autoplay自动播放，muted静音 --&gt;
&lt;video controls width="640" height="480" poster="movie.png" autoplay muted&gt;
  &lt;source src="movie.mp4" type="video/mp4"&gt;
  &lt;!-- 备选格式，如果浏览器不支持mp4 --&gt;
  &lt;source src="movie.webm" type="video/webm"&gt;
  &lt;!-- 可以附带字幕 --&gt;
  &lt;track src="subtitles_en.vtt" kind="subtitles" srclang="en" label="English"&gt;
  &lt;!-- 如果浏览器不支持video标签，显示下面的连接 --&gt;
  &lt;a href="videofile.mp4"&gt;download video&lt;/a&gt;
&lt;/video&gt;</pre>
<div class="blog_h2"><span class="graybg">&lt;canvas&gt;</span></div>
<p>在画布中，你可以进行任意的图形绘制，当然可以去逐帧渲染视频内容。</p>
<div class="blog_h2"><span class="graybg">编程方式创建</span></div>
<p>音频、视频播放器标签也可以利用JavaScript编程式的创建，示例代码：</p>
<pre class="crayon-plain-tag">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; </pre>
<div class="blog_h1"><span class="graybg">MSE</span></div>
<p>媒体源扩展（Media Source Extensions，MSE）是一个W3C草案，<a href="http://caniuse.com/#feat=mediasource">桌面浏览器对MSE的支持较好</a>。MSE扩展流video/audio元素的能力，允许你<span style="background-color: #c0c0c0;">通过JavaScript来生成（例如从服务器抓取）媒体流供video/audio元素播放</span>。使用MSE你可以：</p>
<ol>
<li>通过JavaScript来构建媒体流，不管媒体是如何捕获的</li>
<li>处理自适应码流、广告插入、时间平移（time-shifting，回看）、视频编辑等应用场景</li>
<li>最小化JavaScript中处理媒体解析的代码</li>
</ol>
<p>MSE定义支持的（你生成的）<a href="https://www.w3.org/TR/media-source/">媒体格式</a>，只有符合要求的容器格式、编码格式才能被MSE处理。通常容器格式是<span style="color: #24292e;">ISO BMFF（MP4），也就是说你需要生成MP4的片断，然后Feed给MSE进行播放。</span></p>
<p>MediaSource对象作为video/audio元素的媒体来源，它可以具有多个SourceBuffer对象。应用程序把数据片段（segment）附加到SourceBuffer中，并可以根据系统性能对数据片段的质量进行适配。SourceBuffer中包含多个track buffer —— 分别对应音频、视频、文本等可播放数据。这些数据被音频、视频解码器解码，然后在屏幕上显示、在扬声器中播放：</p>
<p> <img class="aligncenter size-large wp-image-15564" src="https://blog.gmem.cc/wp-content/uploads/2017/08/pipeline_model.png" alt="pipeline_model" width="710" height="516" /></p>
<p>要把MediaSource提供给video/audio播放，调用：</p>
<pre class="crayon-plain-tag">video.src = URL.createObjectURL(mediaSource);</pre>
<div class="blog_h2"><span class="graybg">基于MSE的框架</span></div>
<div class="blog_h3"><span class="graybg">wfs</span></div>
<p><a href="https://github.com/ChihChengYang/wfs.js">wfs</a>是一个播放原始H.264帧的HTML5播放器，它的工作方式是把H.264 NAL单元封装为 ISO BMFF（MP4）片，然后Feed给MSE处理。</p>
<div class="blog_h3"><span class="graybg">flv.js</span></div>
<p><a href="https://github.com/Bilibili/flv.js">flv.js</a>是一个HTML5 Flash视频播放器，基于纯JS，不需要Flash插件的支持。此播放器将FLV流转换为ISO BMFF（MP4）片断，然后把MP4片断提供给video元素使用。</p>
<p>flv.js支持Chrome 43+, FireFox 42+, Edge 15.15048+以上版本的直播流 。</p>
<div class="blog_h3"><span class="graybg">Streamedian</span></div>
<p><a href="https://github.com/Streamedian/html5_rtsp_player/wiki/HTML5-RTSP-Player">Streamedian</a>是一个HTML5的RTSP播放器。实现了RTSP客户端功能，你可以利用此框架直接播放RTSP直播流。此播放器把RTP协议下的H264/AAC在转换为ISO BMFF供video元素使用。Streamedian支持Chrome 23+, FireFox 42+, Edge 13+，以及Android 5.0+。不支持iOS和IE。</p>
<p>在服务器端，你需要安装Streamedian提供的代理（此代理收费），此代理将RTSP转换为WebSocket。Streamedian处理视频流的流程如下：<img class="aligncenter size-large wp-image-15609" src="https://blog.gmem.cc/wp-content/uploads/2017/08/streamedian-1024x391.png" alt="streamedian" width="710" height="271" /></p>
<div class="blog_h1"><span class="graybg">WebRTC</span></div>
<p>WebRTC是一整套API，其中一部分供Web开发者使用，另外一部分属于要求浏览器厂商实现的接口规范。WebRTC解决诸如客户端流媒体发送、点对点通信、视频编码等问题。<a href="http://iswebrtcreadyyet.com/legacy.html">桌面浏览器对WebRTC的支持较好</a>，WebRTC也很容易和Native应用集成。</p>
<p>使用MSE时，你需要自己构建视频流。使用WebRTC时则可以直接捕获客户端视频流。</p>
<p>使用WebRTC时，大部分情况下流量不需要依赖于服务器中转，服务器的作用主要是：</p>
<ol>
<li>在信号处理时，转发客户端的数据</li>
<li>配合实现NAT/防火墙穿透</li>
<li>在点对点通信失败时，作为中继器使用</li>
</ol>
<div class="blog_h2"><span class="graybg">架构</span></div>
<p><img class="aligncenter size-full wp-image-15576" src="https://blog.gmem.cc/wp-content/uploads/2017/08/webrtcArchitecture.png" alt="webrtcarchitecture" width="100%" /></p>
<div class="blog_h2"><span class="graybg">流捕获</span></div>
<div class="blog_h3"><span class="graybg">捕获视频</span></div>
<p>主要是捕获客户端摄像头、麦克风。在视频监控领域用处不大，这里大概了解一下。流捕获通过navigator.getUserMedia调用实现： </p>
<pre class="crayon-plain-tag">&lt;script type="text/javascript"&gt;
    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 );
&lt;/script&gt;
&lt;video id="camrea" width="640" height="480"/&gt;</pre>
<p>三个调用参数分别是：</p>
<ol>
<li><a href="http://io13webrtc.appspot.com/#22">约束条件</a>，你可以指定媒体类型、分辨率、帧率 </li>
<li>成功后的回调，你可以在回调中解析出URL提供给video元素播放</li>
<li>失败后的回调</li>
</ol>
<div class="blog_h3"><span class="graybg">捕获音频</span></div>
<p>捕获音频类似：</p>
<pre class="crayon-plain-tag">navigator.getUserMedia( { audio: true }, function ( stream ) {
    var audioContext = new AudioContext();

    // 从捕获的音频流创建一个媒体源管理
    var streamSource = audioContext.createMediaStreamSource( stream );

    // 把媒体源连接到目标（默认是扬声器）
    streamSource.connect( audioContext.destination );
}, error );</pre>
<div class="blog_h3"><span class="graybg">MediaStream</span></div>
<p>MediaStream对象提供以下方法：</p>
<ol>
<li>getAudioTracks()，音轨列表</li>
<li>getVideoTracks()，视轨列表</li>
</ol>
<p>每个音轨、视轨都有个label属性，对应其设备名称。</p>
<div class="blog_h3"><span class="graybg">Camera.js</span></div>
<p><a href="https://github.com/idevelop/camera.js">Camera.js</a>是对getUserMedia的简单封装，简化了API并提供了跨浏览器支持：</p>
<pre class="crayon-plain-tag">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();</pre>
<p><a href="https://idevelop.ro/predator-vision/">掠食者视觉</a>是基于Camera实现的一个好玩的例子（移动侦测）。</p>
<div class="blog_h2"><span class="graybg">信号处理</span></div>
<p>在端点之间（Peer）发送流之前，需要进行通信协调、发送控制消息，即所谓信号处理（Signaling），信号处理牵涉到三类信息：</p>
<ol>
<li>会话控制信息：初始化、关闭通信，报告错误</li>
<li>网络配置：对于其它端点来说，本机的IP和端口是什么</li>
<li>媒体特性：本机能够处理什么音视频编码、多高的分辨率。本机发送什么样的音视频编码</li>
</ol>
<p>WebRTC没有对信号处理规定太多，我们可以通过Ajax/WebSocket通信，以SIP、Jingle、ISUP等协议完成信号处理。点对点连接设立后，流的传输并不需要服务器介入。信号处理的示意图如下：</p>
<p><img class="aligncenter wp-image-15587 size-full" src="https://blog.gmem.cc/wp-content/uploads/2017/08/jsep.png" alt="jsep" width="100%" /></p>
<div class="blog_h3"><span class="graybg">示例代码</span></div>
<p>下面的代表片段包含了一个视频电话的信号处理过程：</p>
<pre class="crayon-plain-tag">// 信号处理通道，底层传输方式和协议自定义
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 );</pre>
<div class="blog_h2"><span class="graybg">流转发</span></div>
<p>主要牵涉到的接口是RTCPeerConnection，上面的例子中已经包含了此接口的用法。WebRTC在底层做很多复杂的工作，这些工作对于JavaScript来说是透明的： </p>
<ol>
<li>执行解码</li>
<li>屏蔽丢包的影响</li>
<li>点对点通信：WebRTC引入流交互式连接建立（Interactive Connectivity Establishment，ICE）框架。ICE负责建立点对点链路的建立：
<ol>
<li>首先尝试直接</li>
<li>不行的话尝试STUN（Session Traversal Utilities for NAT）协议。此协议通过一个简单的保活机制确保NAT端口映射在会话期间有效</li>
<li>仍然不行尝试TURN（Traversal Using Relays around NAT）协议。此协议依赖于部署在公网上的中继服务器。只要端点可以访问TURN服务器就可以建立连接</li>
</ol>
</li>
<li>通信安全</li>
<li>带宽适配</li>
<li>噪声抑制</li>
<li>动态抖动缓冲（dynamic jitter buffering），抖动是由于网络状况的变化，缓冲用于收集、存储数据，定期发送</li>
</ol>
<div class="blog_h2"><span class="graybg">任意数据交换</span></div>
<p>通过RTCDataChannel完成，允许点对点之间任意的数据交换。RTCPeerConnection连接创建后，不但可以传输音视频流，还可以打开多个信道（RTCDataChannel）进行任意数据的交换。RTCDataChanel的特点是：</p>
<ol>
<li>类似于WebSocket的API</li>
<li>支持带优先级的多通道</li>
<li>超低延迟，因为不需要通过服务器中转</li>
<li>支持可靠/不可靠传输语义。支持SCTP、DTLS、UDP几种传输协议</li>
<li>内置安全传输（DTLS）</li>
<li>内置拥塞控制</li>
</ol>
<p>使用RTCDataChannel可以很好的支持游戏、远程桌面、实时文本聊天、文件传输、去中心化网络等业务场景。</p>
<div class="blog_h2"><span class="graybg">adapter.js</span></div>
<p><a href="https://github.com/webrtc/adapter">WebRTC adapter</a>是一个垫片库，使用它开发WebRTC应用时，不需要考虑不同浏览器厂商的<a href="https://webrtc.org/web-apis/interop/">API前缀差异</a>。</p>
<div class="blog_h2"><span class="graybg">WebRTC示例</span></div>
<p>本节列出一些WebRTC的代码示例，这些例子都使用adapter.js。</p>
<div class="blog_h3"><span class="graybg">限定分辨率</span></div>
<pre class="crayon-plain-tag">// 指定分辨率
// adapter.js 支持Promise
navigator.mediaDevices.getUserMedia( { video: { width: { exact: 640 }, height: { exact: 480 } } } ).then( stream =&gt; {
    let video = document.createElement( 'video' );
    document.body.appendChild( video );
    video.srcObject = stream;
    video.play();
} ).catch( err =&gt; console.log( err ) );</pre>
<div class="blog_h3"><span class="graybg">在画布中截图</span></div>
<pre class="crayon-plain-tag">// video为video元素
canvas.getContext('2d').drawImage(video, 0, 0, canvas.width, canvas.height);</pre>
<div class="blog_h2"><span class="graybg">WebRTC框架</span></div>
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 20%; text-align: center;">框架</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td><a href="http://peerjs.com/">PeerJS </a></td>
<td>
<p>简化WebRTC的点对点通信、视频、音频调用</p>
<p>提供云端的PeerServer，你也可以自己搭建服务器</p>
</td>
</tr>
<tr>
<td><a href="https://github.com/peer5/sharefest">Sharefest</a></td>
<td>基于Web的P2P文件共享</td>
</tr>
<tr>
<td><a href="https://github.com/webRTC-io/webRTC.io">webRTC.io</a></td>
<td>
<p>WebRTC的一个抽象层，同时提供了客户端、服务器端Node.js组件。服务器端组件抽象了STUN</p>
<p>类似的框架还有<a href="https://github.com/andyet/SimpleWebRTC">SimpleWebRTC</a>、<a href="https://github.com/priologic/easyrtc">easyrtc</a></p>
</td>
</tr>
<tr>
<td><a href="https://www.openwebrtc.org/">OpenWebRTC</a></td>
<td>
<p>允许你构建能够和遵循WebRTC标准的浏览器进行通信的Native应用程序，支持Java绑定</p>
</td>
</tr>
<tr>
<td><a href="https://nextrtc.org/">NextRTC</a></td>
<td>
<p>基于Java实现的WebRTC信号处理服务器</p>
</td>
</tr>
<tr>
<td><a href="https://github.com/meetecho/janus-gateway">Janus</a></td>
<td>
<p>这是一个WebRTC网关，纯服务器端组件，目前仅仅支持Linux环境下安装</p>
<p>Janus本身实现了到浏览器的WebRTC连接机制，支持以JSON格式交换数据，支持在服务器端应用逻辑 - 浏览器之间中继RTP/RTCP和消息。特殊化的功能有服务器端插件完成</p>
<p>官网地址：<a href="https://janus.conf.meetecho.com/index.html">https://janus.conf.meetecho.com</a></p>
</td>
</tr>
<tr>
<td><a href="https://www.kurento.org">Kurento</a></td>
<td>
<p>这是一个开源的WebRTC媒体服务器</p>
</td>
</tr>
</tbody>
</table>
<div class="blog_h1"><span class="graybg">备选方案一：从RTSP开始</span></div>
<p>我们首先尝试的方案是直接使用RTSP源，原因是海康、大华主流厂商的较新的IP摄像头均支持暴露标准化的RTSP流。</p>
<div class="blog_h2"><span class="graybg">尝试播放</span></div>
<p>使用VLC播放器，打开网络串流：rtsp://admin:12345@192.168.0.196:554/ch1/main/av_stream，视频源为公司门口的海康摄像头的主码流（main，子码流为sub）。</p>
<p>发现可以正常播放，说明视频格式应该是标准的。VLC菜单 Tool ⇨ Codec Info查看，编码格式为H264。</p>
<p>浏览器无法直接使用RTSP协议，因此，需要有服务器端来处理视频源的RTSP，将其转换为：</p>
<ol>
<li>通过WebSocket发送的视频片断，由客户端的：
<ol>
<li>JSMpeg/Broadway直接解码，渲染到画布</li>
<li>或者，构造MP4片断Feed给MSE播放</li>
</ol>
</li>
<li>或者，通过WebRTC网关，转换后提供给客户端的WebRTC代码处理</li>
<li>或者，使用浏览器插件机制，例如Chrome的NaCl</li>
</ol>
<div class="blog_h2"><span class="graybg">实现方式一：MSE</span></div>
<p>Streamedian的服务器端需要授权，我们选用了另外一个实现。</p>
<p><span style="color: #24292e;"><a href="https://github.com/veyesys/h5stream">H5S</a>是一个基于live555实现的开源的HTML5 RTSP网关，支持将RTSP/H264流输入转换为HTML5 MSE支持的H264，客户端基于MSE。</span></p>
<div class="blog_h3"><span class="graybg">服务器</span></div>
<p>尝试在容器中运行H5S：</p>
<pre class="crayon-plain-tag">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 &amp;&amp; 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 &amp;&amp; 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</pre>
<div class="blog_h3"><span class="graybg">客户端</span></div>
<p>使用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可能取消此限制。</p>
<p>换成Chrome 50，可以正常播放，但是流畅度较差，播放一段时间后出现卡死的情况。</p>
<div class="blog_h3"><span class="graybg">小结</span></div>
<p>H5S实现不完善，在不修改源码的情况下，服务器端只能接入一路视频输入。客户端也存在不流畅、卡死的问题，不适合生产环境。</p>
<div class="blog_h2"><span class="graybg">实现方式二：JSMpeg</span></div>
<div class="blog_h3"><span class="graybg">转码进程</span></div>
<p>在上文中我们已经成功尝试了利用JSMpege + WebSocket的方式，在网页中显示摄像头捕获的视频。ffmpeg转换RTSP也是非常简单的：</p>
<pre class="crayon-plain-tag">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</pre>
<div class="blog_h3"><span class="graybg">服务器</span></div>
<p>可以使用JSMpeg自带的简单Node.js服务器测试：</p>
<pre class="crayon-plain-tag">node ./app/websocket-relay.js 12345 8800 8801 </pre>
<div class="blog_h3"><span class="graybg">客户端</span></div>
<p>下面是客户端代码，默认JSMpeg会基于WebGL渲染，但是我的机器最多开到8画面，开9画面时出现警告：</p>
<p>Too many active WebGL contexts. Oldest context will be lost，且第一画面丢失，简单的通融方法是，第9画面使用Canvas2D渲染：</p>
<pre class="crayon-plain-tag">new JSMpeg.Player( 'ws://127.0.0.1:8801/', {
    canvas: document.getElementById( 'canvas9' ),
    autoplay: true,
    // 浏览器对WebGL context的数量有限制
    disableGl: true
} ); </pre>
<p>渲染截图：</p>
<p><img class="aligncenter size-large wp-image-15669" src="https://blog.gmem.cc/wp-content/uploads/2017/08/jsmpeg-s9-1024x621.png" alt="jsmpeg-s9" width="100%" /></p>
<div class="blog_h3"><span class="graybg">小结</span></div>
<p>这种方式客户端解码压力较大，同时开9画面的352x288视频，我的机器上CPU占用率大概到40%左右，画面变化较为剧烈的时候会出现卡顿现象。</p>
<div class="blog_h2"><span class="graybg">实现方式三：Broadway</span></div>
<p>与JSMpeg类似，Broadway也是JavaScript解码工具。关键之处是，Broadway支持的视频编码是H.264，意味着可能免去消耗服务器资源的视频重编码。</p>
<p><a id="hk-av-config"></a>最初的尝试并不顺利，根据IP摄像头的RTSP Describe应答（SDP），我们推断其H.264 Profile为Baseline，但是不转码的情况下Broadway根本无法播放。后来查看ffmpeg的日志输出，发现其实际上使用的Profile是Main。进一步尝试，发现摄像头是可以配置为Baseline的：</p>
<p><img class="aligncenter size-full wp-image-16196" src="https://blog.gmem.cc/wp-content/uploads/2017/08/hk-config.png" alt="hk-config" width="707" height="465" /></p>
<p>只需要把编码复杂度设置为低，H.264的Profile就从Main变为Baseline。</p>
<p>设置完毕后，仍然基于h264-live-player的Demo进行测试，使用如下命令行抽取原始H.264帧：</p>
<pre class="crayon-plain-tag">ffmpeg -i rtsp://admin:12345@192.168.0.196:554/ch1/main/av_stream -c:v copy -f rawvideo  -</pre>
<p>即可免转码的进行实时视频预览了。 </p>
<p>此实现方式更多细节信息请参考<a href="/html5-vs-with-broadway">基于Broadway的HTML5视频监控</a>。</p>
<div class="blog_h2"><span class="graybg">实现方式四：NaCl</span></div>
<p>Chrome放弃NPAPI之后，插件开发需要使用PPAPI /NaCl。目前能找到的实现有<a href="https://www.videoexpertsgroup.com/vxg-chrome-plugin/">VXG Chrome Plugin</a>，这是一个商业产品，需要授权。除了RTSP之外，还支持RTMP、HLS等协议。</p>
<p>插件方案的缺点是，需要安装，而且仅仅针对单种浏览器。优势则是灵活性高，理论上性能可以做的很好。</p>
<div class="blog_h2"><span class="graybg">实现方式五：WebRTC</span></div>
<p>WebRTC相关的框架非常多，经过简单的比较，我们决定从Kurento入手。主要原因是：</p>
<ol>
<li>容易扩展的模块化设计</li>
<li>提供Java客户端、JS客户端</li>
<li>可以在服务器端合成多画面，这样可以减轻客户端解码压力，特别是那些低配置的客户端</li>
<li>内置对RTSP协议的支持</li>
</ol>
<p><a href="/webrtc-server-basedon-kurento#vs">基于Kurento搭建WebRTC服务器</a>一文详细讨论了这种实现方式。</p>
<div class="blog_h1"><span class="graybg">备选方案二：从设备SDK开始</span></div>
<p>这里的设备，主要包括：网络硬盘录像机（NVR）、视频服务器、IP摄像头。为了便于二次开发，硬件厂商都为这些设备配置的相应的SDK套件。这些SDK通常都提供了：实时码流预览、录像文件回放、播放控制（如：暂停、单帧前进、单帧后退）、获取码流基本信息、播放截图等功能。</p>
<p>我们的基本目标是，通过SDK得到标准化的码流，例如H264格式。具体如何操作，得看厂商的SDK，但是思路基本是：</p>
<ol>
<li>如果SDK直接支持获取标准格式的流，例如RTSP，那么备选方案一就可以直接用上</li>
<li>如果SDK支持获取标准编码的视频帧，例如H264，那我们只需要将其包装为合适的容器格式，再通过RTSP/HTTP的方式发送出去</li>
<li>如果SDK支持获取解码后的原始图像数据，例如RGB、YV12，我们可以基于H264再次编码，然后按第2步方式处理。这种方式对服务器性能要求比较高，CPU压力较大，PC机处理不了多少个通道</li>
<li>如果都不支持，只提供了封装好的播放控件 —— 这个就比较悲催了，不过通过OS底层API，例如Windows的GDI应该也是可以实现，否则那些屏幕录像软件怎么做的呢？</li>
</ol>
<div class="blog_h2"><span class="graybg">海康SDK</span></div>
<p>根据Linux版本的海康设备网络编程指南的描述，我们应该可以：</p>
<ol>
<li>调用NET_DVR_Init进行SDK初始化</li>
<li>调用NET_DVR_Login登陆到目标设备</li>
<li>调用NET_DVR_RealPlay进行播放，此时返回一个实时播放句柄
<ol>
<li>如果设备支持RTSP协议取流：针对上述句柄调用NET_DVR_SetStandardDataCallBack，可以设置一个标准的数据回调函数，此回调会接受到标准码流，这对应上面的第1种思路</li>
<li>如果设备不支持RTSP协议取流：针对上述句柄调用NET_DVR_SetRealDataCallBack，然后通过PlayM4播放库中的PlayM4_SetDecCallBack回调得到<a href="/image-processing-faq#yv12">yv12</a>格式的原始图像。这对应上面的第3种思路</li>
</ol>
</li>
</ol>
<div class="blog_h3"><span class="graybg">示例代码</span></div>
<p>cmake构建配置：</p>
<pre class="crayon-plain-tag">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)</pre>
<p> C++代码：</p>
<pre class="crayon-plain-tag">#include &lt;HCNetSDK.h&gt;
#include &lt;stdio.h&gt;
#include &lt;cstring&gt;
#include &lt;unistd.h&gt;

// 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( &amp;struLoginInfo, &amp;struDevInfo );
    if ( lUserID &lt; 0 ) {
        printf( "登陆失败，错误码 %d\n", NET_DVR_GetLastError());
        NET_DVR_Cleanup();
        return 1;
    } else {
        lpDevInfo30 = &amp;struDevInfo.struDeviceV30;
        printf( "成功登陆到设备：%s\n", lpDevInfo30-&gt;sSerialNumber );
        printf( "SDK字符串编码方式（1 GB2312，2 GBK，3 BIG5，6 UTF-8）：%d\n", struDevInfo.byCharEncodeType );
        printf( "设备类型（31 高清网络摄像机）：%d\n", lpDevInfo30-&gt;wDevType );
        printf( "模拟通道起始号：%d，模拟通道个数%d，数字通道起始号：%d，数字通道个数%d\n", lpDevInfo30-&gt;byStartChan, lpDevInfo30-&gt;byChanNum,
                lpDevInfo30-&gt;byStartDChan, lpDevInfo30-&gt;byIPChanNum + lpDevInfo30-&gt;byHighDChanNum &lt;&lt; 8 );
        printf( "主码流是否支持RTSP方式：%s，子码流是否支持RTSP方式：%s\n", lpDevInfo30-&gt;byMainProto &gt; 0 ? "是" : "否",
                lpDevInfo30-&gt;bySubProto &gt; 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, &amp;struPrevInfo, NULL, NULL );
    if ( lRealHandle == -1 ) {
        printf( "启动预览失败，错误码 %d\n", NET_DVR_GetLastError());
        NET_DVR_Logout( lUserID );
        NET_DVR_Cleanup();
        return 1;
    }

    if ( lpDevInfo30-&gt;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;
}</pre>
<p>运行脚本：</p>
<pre class="crayon-plain-tag">export HKLIB_HOME=/home/alex/CPP/lib/hcnedsdk/lib
export LD_LIBRARY_PATH=$HKLIB_HOME:$HKLIB_HOME/HCNetSDKCom
./getstream </pre>
<p>此程序运行后，会自动获取到基于RTSP协议的媒体流，回调函数会反复被调用：</p>
<ol>
<li>第一次调用为40字节的头，不太清楚有什么用</li>
<li>第二次调用传递了SDP</li>
<li>后续调用传递标准音视频数据，其内容是RTP封包</li>
</ol>
<div class="blog_h1"><span class="graybg">总结</span></div>
<p>基于HTM5的视频监控，媒体流从采集设备到浏览器，主要路径如下图所示：</p>
<p><img class="aligncenter size-full wp-image-15824" src="https://blog.gmem.cc/wp-content/uploads/2017/08/h5vs-dataflow.png" alt="h5vs-dataflow" width="100%" /></p>
<p>对上图的说明如下：</p>
<ol>
<li>在设备层，需要以某种方式获得码流，以流协议的方式发送出去。最常用的方式是RTSP/RTP。流的可能获取路径为：
<ol>
<li>设备直接暴露RTSP协议端点，并且发送标准码流</li>
<li>设备SDK允许获取标准码流，需要自己以RTSP协议发送</li>
<li>设备SDK允许获得解码后的逐帧，需要直接编码为H264，然后以RTSP发送</li>
</ol>
</li>
<li>流媒体层通常需要引入专门的流媒体服务器，这类服务器能够在内部进行各种流协议的转换，可以解除客户端对特定流协议的依赖</li>
<li>客户端和服务器端的传输方式，可以有TCP、HTTP、P2P（WebRTC）、WebSocket等多种。其中
<ol>
<li>直接的TCP协议浏览器是不支持的，这意味着RTSP/RTMP等协议，在浏览器端必须要有插件才可以使用</li>
<li>WebSocket通常配合JSMpeg或者MSE使用，由程序向JSMpeg/MSE不断Feed视频帧</li>
</ol>
</li>
<li>客户端解码展示的技术主要有三类：
<ol>
<li>浏览器内置的解码能力，主要通过video标签，MSE属于此类</li>
<li>JavaScript软解码，主要是JSMpeg、Broadway</li>
<li>插件机制，例如Chrome的NaCl</li>
</ol>
</li>
</ol>
<p>能够免于引入流媒体层的方案，需要：设备能直接暴露标准码流的RTSP端点，并且安装浏览器插件。缺点也很明显，一个是设备的访问密码暴露给了客户端，第二个是目前没有成熟、开源的插件可用。我相信主要原因是合理技术方向不在于此，没人愿意去开发。</p>
<p>直接使用设备层的RTSP端点，可能存在兼容性问题。一个是它发送的码流是否标准化，第二个是市场上有多少设备没有暴露RTSP端点。</p>
<p>客户端方面，JSMpeg是兼容性较好的方案，WebRTC/MSE都有部分平台不支持（但是桌面级的浏览器大部分支持）。JSMpeg的缺点是：</p>
<ol>
<li>如果基于WebGL渲染，受限于浏览器WebGL上下文最大数量，多画面可能无法渲染。某些流媒体服务器支持在服务器端合成多画面Grid，可以规避此缺点</li>
<li>如果基于Canvas2D渲染，画质较差（我的机器上还有莫名其妙的斜线）</li>
<li>对码流格式要求严格，仅仅支持MPEG-TS，此格式压缩比差，网络带宽占用大</li>
<li>性能相对较差，尽管使用了MPEG-TS这种简单的视频格式，基于JavaScript解码渲染仍然使客户端压力较大。我的机器（i7-4940MX / Quadro K5100M / Ubuntu 14.04 LTS）上会出现卡顿情况</li>
</ol>
<p>和JSMpeg类似的库是Broadway，后者能够进行Baseline的H.264解码。如果设备支持Baseline H.264输出，使用Broadway可以很好的解决服务器端转码导致的资源消耗问题。</p>
<div class="blog_h1"><span class="graybg">附录</span></div>
<div class="blog_h2"><span class="graybg">参考资料</span></div>
<ol>
<li><a href="https://developer.mozilla.org/en-US/Apps/Fundamentals/Audio_and_video_delivery">Audio and Video Delivery</a></li>
<li><a href="https://www.w3.org/TR/media-source/">W3C Recommendation - Media Source Extensions™</a></li>
<li><a href="https://webrtc.org">WebRTC Project Home</a></li>
<li><a href="https://imququ.com/post/html5-live-player-3.html">HTML5 视频直播（三）</a></li>
</ol>
</div><p>The post <a rel="nofollow" href="https://blog.gmem.cc/research-on-html5-video-surveillance">HTML5视频监控技术预研</a> appeared first on <a rel="nofollow" href="https://blog.gmem.cc">绿色记忆</a>.</p>
]]></content:encoded>
			<wfw:commentRss>https://blog.gmem.cc/research-on-html5-video-surveillance/feed</wfw:commentRss>
		<slash:comments>7</slash:comments>
		</item>
	</channel>
</rss>
