網路監視器直播
三月 10, 2019
網路監視器直播
對於網絡攝像機做視頻預覽這塊, 本身其實是非常陌生的, 當時接到這個需求也是相當的頭疼(對於當時一年經驗不到的我來說).當時我們的應用場景是: 多路網絡攝像機通過局域網連接, PC 端能夠實時預覽監控畫面並且畫質達到720p, 延遲不能超過10秒, 多個攝像機能夠切換查看. 由於後端只提供一個RTSP 的直播協議, 所以所有的方案都是圍繞著RTSP這個關鍵詞.
整體想法
經過一波調研(google)之後, 知道瀏覽器對RTSP 協議並不友善, 也就是說我們必須要自己轉碼再提供給瀏覽器使用, 找到的解決思路大概是:
- 轉碼: FFmpeg是一個老牌的轉碼工具,非常強大
- Node.js 用作中轉站接收客戶端發來的攝像機信息及控制FFmpeg 推流
- 最終客戶端接收視頻流
準備
安裝FFmpeg 轉碼工具
window 平台
下載FFmpeg解壓後應該是已經編譯好的文件- 將解壓好的文件放入C 盤根目錄(也可以自行放入其他盤符)下重命名為ffmpeg(方便以後找)
- 設置環境變量, 我的電腦-> 屬性-> 高級系統設置-> 環境變量-> 系統變量-> 新增, 路徑選擇剛剛C 盤下的ffmpeg文件夾中的bin 文件夾
- 註銷或重啟電腦讓環境變量生效
- 測試,在cmd中輸入ffmpeg -version,如果出現版本號之類的東西則成功.
Mac平台
- Mac下可以直接通過Homebrew安裝最為簡單.
- 安裝Homebrew,在終端中輸入ruby -e “$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"
- 安裝FFmpeg,直接在終端中輸入brew install ffmpeg即可
- 測試,同樣在終端中輸入ffmpeg -version查看版本號
安裝Node.js
這個就不展開詳細說了,每個有在工作都會吧…
jsmpeg
這個庫還算比較不錯的了,也是通過websocket來轉發,看官方的例子是在終端中啟動ffmpeg -> websocket ->客戶端通過jsmpeg.min.js解碼在canvas中播放.因為這裡只是實現了播放,然後又去找了一個基於jsmpeg的庫node-rtsp-stream .這個庫只是做了一些封裝讓我們不用自己在終端中手動啟用ffmpeg,在此之上我再加上重啟就能滿足現在的需求.
改造 node-rtsp-stream
node-rtsp-stream/mpeg1muxer.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// Generated by CoffeeScript 2.3.2
(function() {
var Mpeg1Muxer, child_process, events, util;
child_process = require('child_process');
util = require('util');
events = require('events');
Mpeg1Muxer = function(options) {
var key, self;
self = this;
this.url = options.url;
this.ffmpegOptions = options.ffmpegOptions;
this.additionalFlags = [];
if (this.ffmpegOptions) {
for (key in this.ffmpegOptions) {
this.additionalFlags.push(key, String(this.ffmpegOptions[key]));
}
}
this.spawnOptions = [
"-rtsp_transport",
"tcp",
"-i",
this.url,
'-f',
'mpeg1video',
// additional ffmpeg options go here
...this.additionalFlags,
'-'
];
this.stream = child_process.spawn("ffmpeg", this.spawnOptions, {
detached: false
});
this.inputStreamStarted = true;
this.stream.stdout.on('data', function(data) {
return self.emit('mpeg1data', data);
});
this.stream.stderr.on('data', function(data) {
return self.emit('ffmpegError', data);
});
this.stream.stop = function() {
// console.log(self.stream.pid)
self.stream.stdin.pause();
self.stream.kill()
console.log('ffmpeg is be kill')
};
this.stream.on('exit', function(code) {
return self.emit('ffmpegClose', code)
});
return this;
};
util.inherits(Mpeg1Muxer, events.EventEmitter);
module.exports = Mpeg1Muxer;
}).call(this);node-rtsp-stream/videoStream.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
// Generated by CoffeeScript 2.3.2
(function() {
var Mpeg1Muxer, STREAM_MAGIC_BYTES, VideoStream, events, util, ws;
ws = require('ws');
util = require('util');
events = require('events');
Mpeg1Muxer = require('./mpeg1muxer');
STREAM_MAGIC_BYTES = "jsmp"; // Must be 4 bytes
VideoStream = function(options) {
this.options = options;
this.name = options.name;
this.streamUrl = options.streamUrl;
this.width = options.width;
this.height = options.height;
this.wsPort = options.wsPort;
this.inputStreamStarted = false;
this.stream = void 0;
this.startMpeg1Stream();
this.pipeStreamToSocketServer();
return this;
};
util.inherits(VideoStream, events.EventEmitter);
VideoStream.prototype.stop = function() {
this.wsServer.close();
this.stream.kill();
this.inputStreamStarted = false;
return this;
};
// 重新連線
VideoStream.prototype.restart = function() {
if (this.mpeg1Muxer) {
this.mpeg1Muxer.stream.stop()
console.log('ffmpeg is restart')
this.inputStreamStarted = false;
this.stream = void 0;
this.startMpeg1Stream();
// 檢查 ffmpeg 是否壞掉或關閉
this.mpeg1Muxer.on('ffmpegClose', function(code) {
console.log('ffmpeg closed on ' + code)
})
}
}
VideoStream.prototype.startMpeg1Stream = function() {
var gettingInputData, gettingOutputData, inputData, outputData, self;
this.mpeg1Muxer = new Mpeg1Muxer({
ffmpegOptions: this.options.ffmpegOptions,
url: this.streamUrl
});
this.stream = this.mpeg1Muxer.stream;
self = this;
if (this.inputStreamStarted) {
return;
}
this.mpeg1Muxer.on('mpeg1data', function(data) {
return self.emit('camdata', data);
});
gettingInputData = false;
inputData = [];
gettingOutputData = false;
outputData = [];
this.mpeg1Muxer.on('ffmpegError', function(data) {
var size;
data = data.toString();
if (data.indexOf('Input #') !== -1) {
gettingInputData = true;
}
if (data.indexOf('Output #') !== -1) {
gettingInputData = false;
gettingOutputData = true;
}
if (data.indexOf('frame') === 0) {
gettingOutputData = false;
}
if (gettingInputData) {
inputData.push(data.toString());
size = data.match(/\d+x\d+/);
if (size != null) {
size = size[0].split('x');
if (self.width == null) {
self.width = parseInt(size[0], 10);
}
if (self.height == null) {
return self.height = parseInt(size[1], 10);
}
}
}
});
this.mpeg1Muxer.on('ffmpegError', function(data) {
return global.process.stderr.write(data);
});
return this;
};
VideoStream.prototype.pipeStreamToSocketServer = function() {
var self;
self = this;
this.wsServer = new ws.Server({
port: this.wsPort
});
this.wsServer.on("connection", function(socket) {
return self.onSocketConnect(socket);
});
this.wsServer.broadcast = function(data, opts) {
var i, results;
results = [];
for (i in this.clients) {
if (this.clients[i].readyState === 1) {
results.push(this.clients[i].send(data, opts));
} else {
results.push(console.log("Error: Client (" + i + ") not connected."));
}
}
return results;
};
return this.on('camdata', function(data) {
return self.wsServer.broadcast(data);
});
};
VideoStream.prototype.onSocketConnect = function(socket) {
var self, streamHeader;
// Send magic bytes and video size to the newly connected socket
// struct { char magic[4]; unsigned short width, height;}
self = this;
streamHeader = new Buffer(8);
streamHeader.write(STREAM_MAGIC_BYTES);
streamHeader.writeUInt16BE(this.width, 4);
streamHeader.writeUInt16BE(this.height, 6);
socket.send(streamHeader, {
binary: true
});
console.log(`${this.name}: New WebSocket Connection (` + this.wsServer.clients.length + " total)");
return socket.on("close", function(code, message) {
return console.log(`${this.name}: Disconnected WebSocket (` + self.wsServer.clients.length + " total)");
});
};
module.exports = VideoStream;
}).call(this);node 端使用
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
31Stream = require('node-rtsp-stream');
class streams {
constructor() {
this.stream;
this.restartStream.bind(this)
}
start() {
this.stream = new Stream({
name: 'cameraStream',
streamUrl: 'rtsp://192.168.2.236/user=admin&password=&channel=1&stream=0.sdp',
wsPort: 9999,
reconnect: true,
ffmpegOptions: {
'-reconnect' :1,
'-reconnect_at_eof':1,
'-reconnect_streamed' : 1,
//'-hide_banner': '',
//'-loglevel': 'panic'
}
});
this.stream.mpeg1Muxer.on('ffmpegClose', (code) => {
console.log('ffmpeg closed on need restart ' + code);
this.restartStream();
})
}
restartStream () {
console.log('restart stream')
this.stream.restart()
}
}
module.exports = streams; // stream;index.js
引入1
2
3
4
5const Stream = require('./cameraStream');
//cameraStream
const streams = new Stream();
streams.start();Client端
1
2
3
4
5
6
7
8
9
10<canvas id='can'></canvas>
<script src='jsmpeg.min.js'></script>
<script>
let canvas = document.querySelector('#can')
let ws = new WebSocket('ws://127.0.0.1:11111')
let player = new jsmpeg(ws, {
canvas: canvas,
autoplay: true
})
</script>
結論
目前這方法沒出啥問題,不過有時候FFMPEG在瀏覽器端會出現編譯錯誤問題,不知道原因為何。
查看评论