webrtc-streamer实现网页直播摄像头rstp

刚做完一个web端操作巡检机器人项目,其中机器人主要包含海康摄像头设备,红外和可见光两种,官方3.2web插件比较复杂,而且有一定的兼容性问题,经过调研webrtc-streamer是一个相对比较好的实现方式,经过测试延迟低毫秒级别。

本组件还集成了视频框选截图功能。

于是开始铺坑。首先网上搜集资料,查看官方git

一、启动webrtc服务

使用webrtc-streamer组件首先需要启动一个本地rtc服务,window系统比较简单,从这里https://github.com/mpromonet/webrtc-streamer/releases
下载适合项目需求的webrtc-streamer源码包,本文以webrtc-streamer-v0.7.0-Linux-x86_64-Release.tar.gz为例,解压后直接运行webrtc-streamer.exe即可,默认服务8000端口。

使用docker:

1
2
3
4
# docker中获取webrtc-streamer
docker pull mpromonet/webrtc-streamer
# 启动webrtc-streamer镜像
docker run -itd -p 8000:8000 --name webrtc-streamer mpromonet/webrtc-streamer

docker部署可能会遇到各种问题,可以找运维同学帮忙。

二、网页连接海康视频

以下是以vue项目为例

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
<template>
<div class="wrap">
<div class="w-top">
<video
:id="videoName"
:ref="videoName"
controls
autoplay
autobuffer
muted
preload='auto'
allowfullscreen="true"
:width="vWidth"
:height="vHeight"
style="position: absolute;"
></video>
<canvas
v-if="isCropper"
id="canvasId"
ref="canvasId"
@mousedown="onMousedown"
@mouseup="onMouseup"
@mousemove="onMousemove"
@dblclick="onDblclick"
style="position: absolute;"
:width="vWidth"
:height="vHeight"
></canvas>
<div style="clear: both;"></div>
</div>
<canvas
id="canvasShot"
ref="canvasShot"
style="display: none;"
:width="vWidth"
:height="vHeight"
></canvas>
</div>
</template>

canvas是用来视频截图用的。

核心js代码:

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
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
<script>
import WebRtcStreamer from "@/utils/webrtcstreamer"
export default {
name: "WebRtcStreamer",
props: {
serveUrl: {
type: String,
default: ''
},
rtspUrl: {
type: String,
default: ''
},
vWidth: {
type: Number,
default: 764
},
vHeight: {
type: Number,
default: 430
},
isCropper: {
type: Boolean,
default: false
},
// index 多个组件必填 否则会只显示一个视频
index: {
type: String,
default: ''
},
strokeColor: {
type: String,
default: '#00FF89'
},
// 框选起始位置 边界值
xLeft: {
type: Number,
default: 0
},
yTop: {
type: Number,
default: 0
},
// 视频大小与实际比
xRate: {
type: Number,
default: 1
},
yRate: {
type: Number,
default: 1
},
},
data(){
return{
ctx: '',
_x1: '',
_y1: '',
_x2: '',
_y2: '',
drawing: false,
picSizeW: '',
picSizeH: '',
picUrl: '',
webRtcServer: null,
videoName: ''
}
},
created(){
this.videoName = 'video'+this.index
},
mounted(){
this.init()
if(this.isCropper){
this.draw()
}
},
watch: {
strokeColor(){
this.draw()
}
},
methods:{
init(url){
console.log(url,'333')
let serveUrl = this.serveUrl || process.env.VUE_APP_WEBRTC_SERVER_URL
if(this.webRtcServer){this.webRtcServer.disconnect();}
this.webRtcServer = new WebRtcStreamer(this.videoName, serveUrl);
this.webRtcServer.connect(url || this.rtspUrl);
},
draw() {
const cid = this.$refs.canvasId
this.ctx = cid.getContext("2d")
this.ctx.lineWidth = 2
this.ctx.strokeStyle = this.strokeColor
},
// 双击全屏
onDblclick(){
// const videoEl = this.$refs[this.videoName]
// videoEl.requestFullscreen()
},
onMousedown(e){
if (this.drawing) {
this.drawing = false
return
}
this._x1 = e.layerX < this.xLeft ? this.xLeft : e.layerX
this._y1 = e.layerY < this.yTop ? this.yTop : e.layerY
this.drawing = true
},
onMouseup(e){
this.drawing = false
if (this._x2 < this._x1) {
let t = this._x2
this._x2 = this._x1
this._x1 = t
}
if (this._y2 < this._y1) {
var t = this._y2
this._y2 = this._y1
this._y1 = t
}
this.picSizeW = this._x2 - this._x1
this.picSizeH = this._y2 - this._y1
if (this.picSizeW > 2 && this.picSizeH > 2) {
console.info('图片宽高均大于2px,认为可以执行截屏操作')
this.getShot()
} else {
this.ctx.clearRect(0, 0, this.vWidth, this.vHeight);
}
},
onMousemove(e){
this._x2 = e.layerX < this.xLeft ? this.xLeft : e.layerX
this._y2 = e.layerY < this.yTop ? this.yTop : e.layerY
this.render()
},
// 绘制矩形
render() {
if (!this.drawing) return
this.ctx.clearRect(0, 0, this.vWidth, this.vHeight);
// ctx.drawImage(img, 0, 0, 800, 300)
this.ctx.fillStyle = 'rgba(225,225,225,0.5)';
this.ctx.fillRect(0, 0, this.vWidth, this.vHeight);
let {_x1, _y1, _x2, _y2} = this
let w = _x2 - _x1
let h = _y2 - _y1
this.ctx.clearRect(_x1, _y1, w, h);
this.ctx.strokeRect(_x1, _y1, w, h);
},
// 绘制新canvas并生成图片
getShot() {
const canvasShotId = this.$refs.canvasShot
const csId_ctx = canvasShotId.getContext('2d')
const vId = this.$refs[this.videoName]
canvasShotId.width = this.vWidth;
canvasShotId.height = this.vHeight;
csId_ctx.drawImage(vId, 0, 0, this.vWidth, this.vHeight)
let {_x1, _y1, _x2, _y2} = this
// 获取并存储框选图
const picSizeW = _x2 - _x1
const picSizeH = _y2 - _y1
var imageData = csId_ctx.getImageData(_x1+1, _y1+1, picSizeW-2, picSizeH-2)
// 重新绘制框选后的整体cancas
csId_ctx.lineWidth = 2
csId_ctx.strokeStyle = this.strokeColor
csId_ctx.fillStyle = 'rgba(225,225,225,0.5)';
csId_ctx.fillRect(0, 0, this.vWidth, this.vHeight);
let w = _x2 - _x1
let h = _y2 - _y1
csId_ctx.clearRect(_x1, _y1, w, h);
csId_ctx.strokeRect(_x1, _y1, w, h);
// 设置框选图标并放到新canvas
csId_ctx.putImageData(imageData, _x1+1, _y1+1)
// 新canvas转成图片
let picUrl = canvasShotId.toDataURL('image/jpeg')
let {xRate,yRate} = this
let opts = {
picUrl: picUrl,
topLeft: [Math.round(_x1*xRate), Math.round(_y1*yRate)],
topRight: [Math.round(_x2*xRate), Math.round(_y1*yRate)],
bottomRight: [Math.round(_x2*xRate), Math.round(_y2*yRate)],
bottomLeft: [Math.round(_x1*xRate), Math.round(_y2*yRate)]
}
console.log(opts,'000----')
this.$emit('onchange', opts)
},
clear(){
this.ctx.clearRect(0, 0, this.vWidth, this.vHeight);
}

},
destroyed(){
this.webRtcServer.disconnect();
}
}
</script>

@/utils/webrtcstreamer是组件核心代码可以从git下载,也可以通过npm库里下。

props参数说明
serveUrl:rtc服务,最开始启动的8000端口服务地址。
rtspUrl:海康摄像头地址rtsp格式。
isCropper:是否开始框选截图功能。

直接看init方法初始化链接:

1
2
this.webRtcServer = new WebRtcStreamer(this.videoName, serveUrl);
this.webRtcServer.connect(url || this.rtspUrl);

this.videoName是video标签id,因为一个页面有调用多个组件情况所以这里做成动态。

draw()方法是启动canvas视频截图用的。其中代码和逻辑稍多但是不难,可根据实际需求修改。

getShot()方法是把最终截图后的图片url和坐标点以参数形式打印出来给父组件用。

特别注意:海康后台视频设置格式一定改成h264,否则视频显示不出来。

父组件调用:

1
2
3
4
5
6
7
8
<webRtc-streamer
ref="cameraRef1"
rtspUrl=""
:vWidth="764"
:vHeight="430"
:isCropper="false"
index="1"
/>

磕磕绊绊总算实现了功能,最后部署会发现wertc服务cpu拉满问题,经过测试超过3个人同时访问就会卡死,解决办法是docker启动时候加 -o 。

最后一句话是我的经验很重要:遇到问题发现网上能查到的资料和解决方法并不多,webrtc-streamer遇到问题就去git官网issues里找答案,海康摄像头遇到问题查不到的话就去海康官网找技术客服咨询。