HTML5 播放 RTSP 视频
<h1 class="post__title">HTML5 播放 RTSP 视频</h1><p>github地址:https://github.com/littlebaozi/rtsp-web</p>
<div class="post__content">
<p>目前大多数网络摄像头都是通过 RTSP 协议传输视频流的,但是 HTML 并不标准支持 RTSP 流。除了 Firefox 浏览器可以直接播放 RTSP 流之外,几乎没有其他浏览器可以直接播放 RTSP 流。Electron 应用是基于 Chromium 内核的,因此也不能直接播放 RTSP 流。</p>
<p>在借助一定工具的情况下,可以实现在 Web 页面上播放 RTSP 流。本文介绍的方法可以应用于传统 Web 应用和 Electron 应用中,唯一的区别是将 Electron 应用的主进程当作传统 Web 应用的服务器。</p>
<h1 id="目前已有-RTSP-播放方案的对比">目前已有 RTSP 播放方案的对比</h1>
<p>既然是做直播,就需要延迟较低。当摄像头掉线时,也应当有一定的事件提示。处于这两点,对目前已有的已经实现、无需购买许可证的 RTSP 播放方案进行对比(处于原理阶段的暂时不分析)。</p>
<table>
<thead>
<tr><th>方案</th><th>协议</th><th>视频格式</th><th>延迟</th><th>离线事件汇报</th><th>最小端口占用</th><th>依赖</th></tr>
</thead>
<tbody>
<tr>
<td>1</td>
<td>HLS</td>
<td>ogg</td>
<td>网络延迟较高,可达10秒以上</td>
<td>难</td>
<td>n</td>
<td>VLC + video.js</td>
</tr>
<tr>
<td>2</td>
<td>RTMP</td>
<td>flv</td>
<td>网络延迟较低,5秒左右</td>
<td>难</td>
<td>n</td>
<td>ffmpeg + nginx + flash + video.js</td>
</tr>
<tr>
<td>3</td>
<td>WebSocket</td>
<td>mpegts</td>
<td>网络延迟很低,渲染速度慢</td>
<td>易</td>
<td>1</td>
<td>ffmpeg + express + jsmpeg</td>
</tr>
<tr>
<td>4</td>
<td>HTTP-FLV</td>
<td>flv</td>
<td>网络延迟很低,渲染速度快</td>
<td>易</td>
<td>1</td>
<td>ffmpeg + express + flv.js</td>
</tr>
</tbody>
</table>
<p>我对这四种方式都进行了实现,整体效果最好的还是第4种方案,占用端口少,延迟低,渲染速度快,而且离线事件易于处理。</p>
<h1 id="基于-flv-js-的-RTSP-播放方案">基于 flv.js 的 RTSP 播放方案</h1>
<p>flv.js 是 Bilibili 开源的一款 HTML5 浏览器。依赖于 Media Source Extension 进行视频播放,视频通过 HTTP-FLV 或 WebSocket-FLV 协议传输,视频格式需要为 FLV 格式。</p>
<h2 id="服务器端(主进程)">服务器端(主进程)</h2>
<p>服务器端采用 express + express-ws 框架进行编写,当有 HTTP 请求发送到指定的地址时,启动 ffmpeg 串流程序,直接将 RTSP 流封装成 FLV 格式的视频流,推送到指定的 WebSocket 响应流中。</p>
<table>
<tbody>
<tr>
<td class="gutter">
<pre><span class="line">1<br><span class="line">2<br><span class="line">3<br><span class="line">4<br><span class="line">5<br><span class="line">6<br><span class="line">7<br><span class="line">8<br><span class="line">9<br><span class="line">10<br><span class="line">11<br><span class="line">12<br><span class="line">13<br><span class="line">14<br><span class="line">15<br><span class="line">16<br><span class="line">17<br><span class="line">18<br><span class="line">19<br><span class="line">20<br><span class="line">21<br><span class="line">22<br><span class="line">23<br><span class="line">24<br><span class="line">25<br><span class="line">26<br><span class="line">27<br><span class="line">28<br><span class="line">29<br><span class="line">30<br><span class="line">31<br><span class="line">32<br><span class="line">33<br><span class="line">34<br><span class="line">35<br><span class="line">36<br><span class="line">37<br><span class="line">38<br><span class="line">39<br><span class="line">40<br><span class="line">41<br><span class="line">42<br><span class="line">43<br><span class="line">44<br><span class="line">45<br><span class="line">46<br><span class="line">47<br><span class="line">48<br><span class="line">49<br><span class="line">50<br><span class="line">51<br></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></pre>
</td>
<td class="code">
<pre><span class="line"><span class="keyword">import * <span class="keyword">as express <span class="keyword">from <span class="string">"express";<br><span class="line"><span class="keyword">import * <span class="keyword">as expressWebSocket <span class="keyword">from <span class="string">"express-ws";<br><span class="line"><span class="keyword">import ffmpeg <span class="keyword">from <span class="string">"fluent-ffmpeg";<br><span class="line"><span class="keyword">import webSocketStream <span class="keyword">from <span class="string">"websocket-stream/stream";<br><span class="line"><span class="keyword">import WebSocket <span class="keyword">from <span class="string">"websocket-stream";<br><span class="line"><span class="keyword">import * <span class="keyword">as http <span class="keyword">from <span class="string">"http";<br><span class="line"><br><span class="line"><span class="function"><span class="keyword">function <span class="title">localServer(<span class="params">) {<br><span class="line"> <span class="keyword">let app = express();<br><span class="line"> app.use(express.static(__dirname));<br><span class="line"> expressWebSocket(app, <span class="literal">null, {<br><span class="line"> perMessageDeflate: <span class="literal">true<br><span class="line"> });<br><span class="line"> app.ws(<span class="string">"/rtsp/:id/", rtspRequestHandle)<br><span class="line"> app.listen(<span class="number">8888);<br><span class="line"> <span class="built_in">console.log(<span class="string">"express listened")<br><span class="line">}<br><span class="line"><br><span class="line"><span class="function"><span class="keyword">function <span class="title">rtspRequestHandle(<span class="params">ws, req) {<br><span class="line"> <span class="built_in">console.log(<span class="string">"rtsp request handle");<br><span class="line"> <span class="keyword">const stream = webSocketStream(ws, {<br><span class="line"> binary: <span class="literal">true,<br><span class="line"> browserBufferTimeout: <span class="number">1000000<br><span class="line"> }, {<br><span class="line"> browserBufferTimeout: <span class="number">1000000<br><span class="line"> });<br><span class="line"> <span class="keyword">let url = req.query.url;<br><span class="line"> <span class="built_in">console.log(<span class="string">"rtsp url:", url);<br><span class="line"> <span class="built_in">console.log(<span class="string">"rtsp params:", req.params);<br><span class="line"> <span class="keyword">try {<br><span class="line"> ffmpeg(url)<br><span class="line"> .addInputOption(<span class="string">"-rtsp_transport", <span class="string">"tcp", <span class="string">"-buffer_size", <span class="string">"102400")<span class="comment">// 这里可以添加一些 RTSP 优化的参数<br><span class="line"> .on(<span class="string">"start", <span class="function"><span class="keyword">function (<span class="params">) {<br><span class="line"> <span class="built_in">console.log(url, <span class="string">"Stream started.");<br><span class="line"> })<br><span class="line"> .on(<span class="string">"codecData", <span class="function"><span class="keyword">function (<span class="params">) {<br><span class="line"> <span class="built_in">console.log(url, <span class="string">"Stream codecData.")<br><span class="line"> <span class="comment">// 摄像机在线处理<br><span class="line"> })<br><span class="line"> .on(<span class="string">"error", <span class="function"><span class="keyword">function (<span class="params">err) {<br><span class="line"> <span class="built_in">console.log(url, <span class="string">"An error occured: ", err.message);<br><span class="line"> })<br><span class="line"> .on(<span class="string">"end", <span class="function"><span class="keyword">function (<span class="params">) {<br><span class="line"> <span class="built_in">console.log(url, <span class="string">"Stream end!");<br><span class="line"> <span class="comment">// 摄像机断线的处理<br><span class="line"> })<br><span class="line"> .outputFormat(<span class="string">"flv").videoCodec(<span class="string">"copy").noAudio().pipe(stream);<br><span class="line"> } <span class="keyword">catch (error) {<br><span class="line"> <span class="built_in">console.log(error);<br><span class="line"> }<br><span class="line">}<br></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></pre>
</td>
</tr>
</tbody>
</table>
<p>当然这个实现还比较粗糙。当有多个相同地址的请求时,应当增加 ffmpeg 的输出,而不是启动一个新的 ffmpeg 进程串流。</p>
<h2 id="浏览器端(渲染进程)">浏览器端(渲染进程)</h2>
<p>示例使用 Vue 框架进行页面设计。</p>
<table>
<tbody>
<tr>
<td class="gutter">
<pre><span class="line">1<br><span class="line">2<br><span class="line">3<br><span class="line">4<br><span class="line">5<br><span class="line">6<br><span class="line">7<br><span class="line">8<br><span class="line">9<br><span class="line">10<br><span class="line">11<br><span class="line">12<br><span class="line">13<br><span class="line">14<br><span class="line">15<br><span class="line">16<br><span class="line">17<br><span class="line">18<br><span class="line">19<br><span class="line">20<br><span class="line">21<br><span class="line">22<br><span class="line">23<br><span class="line">24<br><span class="line">25<br><span class="line">26<br><span class="line">27<br><span class="line">28<br><span class="line">29<br><span class="line">30<br><span class="line">31<br><span class="line">32<br><span class="line">33<br><span class="line">34<br><span class="line">35<br><span class="line">36<br><span class="line">37<br><span class="line">38<br><span class="line">39<br><span class="line">40<br><span class="line">41<br><span class="line">42<br><span class="line">43<br><span class="line">44<br><span class="line">45<br><span class="line">46<br><span class="line">47<br><span class="line">48<br><span class="line">49<br><span class="line">50<br><span class="line">51<br><span class="line">52<br></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></pre>
</td>
<td class="code">
<pre><span class="line"><template><br><span class="line"> <div><br><span class="line"> <video class="demo-video" ref="player"></video><br><span class="line"> </div><br><span class="line"></template><br><span class="line"><br><span class="line"><script><br><span class="line">import flvjs from "flv.js";<br><span class="line">export default {<br><span class="line"> props: {<br><span class="line"> rtsp: String,<br><span class="line"> id: String<br><span class="line"> },<br><span class="line"> /**<br><span class="line"> * @returns {{player: flvjs.Player}}<br><span class="line"> */<br><span class="line"> data () {<br><span class="line"> return {<br><span class="line"> player: null<br><span class="line"> }<br><span class="line"> },<br><span class="line"> mounted () {<br><span class="line"> if (flvjs.isSupported()) {<br><span class="line"> let video = this.$refs.player;<br><span class="line"> if (video) {<br><span class="line"> this.player = flvjs.createPlayer({<br><span class="line"> type: "flv",<br><span class="line"> isLive: true,<br><span class="line"> url: `ws://localhost:8888/rtsp/${this.id}/?url=${this.rtsp}`<br><span class="line"> });<br><span class="line"> this.player.attachMediaElement(video);<br><span class="line"> try {<br><span class="line"> this.player.load();<br><span class="line"> this.player.play();<br><span class="line"> } catch (error) {<br><span class="line"> console.log(error);<br><span class="line"> };<br><span class="line"> }<br><span class="line"> }<br><span class="line"> },<br><span class="line"> beforeDestroy () {<br><span class="line"> this.player.destory();<br><span class="line"> }<br><span class="line">}<br><span class="line"></script><br><span class="line"><br><span class="line"><style><br><span class="line"> .demo-video {<br><span class="line"> max-width: 480px; <br><span class="line"> max-height: 360px;<br><span class="line"> }<br><span class="line"></style><br></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></pre>
</td>
</tr>
</tbody>
</table>
<h1 id="效果展示"> </h1>
</div><br><br>
来源:https://www.cnblogs.com/Jishuyang/p/17010337.html
頁:
[1]