Node-Media-Server (相對穩定可用性高)
主要應用Node.js 實現的RTSP(結合ffmpeg)/RTMP/HTTP/WebSocket/HLS/DASH流媒體服務器
特性
- 跨平台支持 Windows/Linux/Unix
- 支持的音視頻編碼 H.264/H.265/AAC/SPEEX/NELLYMOSER
- 支持緩存最近一個關鍵幀間隔數據,實現RTMP協議秒開
- 支持事件回調
- 支持https/wss加密傳輸
- 支持服務器和流媒體信息統計
- 支持RTMP直播流轉HLS,DASH直播流
- 支持RTMP直播流錄制為MP4文件並開啟faststart
- 支持RTMP/RTSP中繼(關鍵)
- 支持多核集群模式
- 支持錄制為MP4回放
- 支持實時轉碼(關鍵)
- 支持低延遲HLS/DASH
- 支持on_connect/on_publish/on_play/on_done 事件回調
用法
git 版本
簡單運行方式
npm i
node app.js
多核模式運行
node cluster.js
npm 版本(推薦)
單核模式
mkdir nms
cd nms
npm install node-media-server
const { NodeMediaServer } = require('node-media-server');
const config = {
rtmp: {
port: 1935,
chunk_size: 60000,
gop_cache: true,
ping: 60,
ping_timeout: 30
},
http: {
port: 8000,
allow_origin: '*'
}
};
var nms = new NodeMediaServer(config)
nms.run();
多核模式
mkdir nms
cd nms
npm install node-media-server
const { NodeMediaCluster } = require('node-media-server');
const numCPUs = require('os').cpus().length;
const config = {
rtmp: {
port: 1935,
chunk_size: 60000,
gop_cache: true,
ping: 60,
ping_timeout: 30
},
http: {
port: 8000,
allow_origin: '*'
},
cluster: {
num: numCPUs
}
};
var nmcs = new NodeMediaCluster(config)
nmcs.run();
鑒權驗證(更安全)
加密后的 URL 形式:
rtmp://hostname:port/appname/stream?sign=expires-HashValue
http://hostname:port/appname/stream.flv?sign=expires-HashValue
ws://hostname:port/appname/stream.flv?sign=expires-HashValue
1.原始推流或播放地址:
rtmp://192.168.0.10/live/stream
2.配置驗證秘鑰為: 'nodemedia2017privatekey',同時打開播放和發布的鑒權開關
const config = {
rtmp: {
port: 1935,
chunk_size: 60000,
gop_cache: true,
ping: 60,
ping_timeout: 30
},
http: {
port: 8000,
allow_origin: '*'
},
auth: {
play: true,
publish: true,
secret: 'nodemedia2017privatekey'
}
}
3.請求過期時間為: 2017/8/23 11:25:21 ,則請求過期時間戳為:
1503458721
4.md5計算結合“完整流地址-失效時間-密鑰”的字符串:
HashValue = md5("/live/stream-1503458721-nodemedia2017privatekey”)
HashValue = 80c1d1ad2e0c2ab63eebb50eed64201a
5.最終請求地址為
rtmp://192.168.0.10/live/stream?sign=1503458721-80c1d1ad2e0c2ab63eebb50eed64201a
注意:'sign' 關鍵字不能修改為其他的
RTMP協議傳輸H.265視頻
H.265並沒有在Adobe的官方規范里實現,這里使用id 12作為標識,也是國內絕大多數雲服務商使用的id號
PC轉碼推流: ffmpeg-hw-win32
純JavaScrip 直播播放器: NodePlayer.js
事件回調
......
nms.run();
nms.on('preConnect', (id, args) => {
console.log('[NodeEvent on preConnect]', `id=${id} args=${JSON.stringify(args)}`);
// let session = nms.getSession(id);
// session.reject();
});
nms.on('postConnect', (id, args) => {
console.log('[NodeEvent on postConnect]', `id=${id} args=${JSON.stringify(args)}`);
});
nms.on('doneConnect', (id, args) => {
console.log('[NodeEvent on doneConnect]', `id=${id} args=${JSON.stringify(args)}`);
});
nms.on('prePublish', (id, StreamPath, args) => {
console.log('[NodeEvent on prePublish]', `id=${id} StreamPath=${StreamPath} args=${JSON.stringify(args)}`);
// let session = nms.getSession(id);
// session.reject();
});
nms.on('postPublish', (id, StreamPath, args) => {
console.log('[NodeEvent on postPublish]', `id=${id} StreamPath=${StreamPath} args=${JSON.stringify(args)}`);
});
nms.on('donePublish', (id, StreamPath, args) => {
console.log('[NodeEvent on donePublish]', `id=${id} StreamPath=${StreamPath} args=${JSON.stringify(args)}`);
});
nms.on('prePlay', (id, StreamPath, args) => {
console.log('[NodeEvent on prePlay]', `id=${id} StreamPath=${StreamPath} args=${JSON.stringify(args)}`);
// let session = nms.getSession(id);
// session.reject();
});
nms.on('postPlay', (id, StreamPath, args) => {
console.log('[NodeEvent on postPlay]', `id=${id} StreamPath=${StreamPath} args=${JSON.stringify(args)}`);
});
nms.on('donePlay', (id, StreamPath, args) => {
console.log('[NodeEvent on donePlay]', `id=${id} StreamPath=${StreamPath} args=${JSON.stringify(args)}`);
});
Https/Wss 視頻加密傳輸
生成證書
openssl genrsa -out privatekey.pem 1024
openssl req -new -key privatekey.pem -out certrequest.csr
openssl x509 -req -in certrequest.csr -signkey privatekey.pem -out certificate.pem
配置 https支持
const { NodeMediaServer } = require('node-media-server');
const config = {
rtmp: {
port: 1935,
chunk_size: 60000,
gop_cache: true,
ping: 60,
ping_timeout: 30
},
http: {
port: 8000,
allow_origin: '*'
},
https: {
port: 8443,
key:'./privatekey.pem',
cert:'./certificate.pem',
}
};
var nms = new NodeMediaServer(config)
nms.run();
播放加密傳輸視頻
https://localhost:8443/live/STREAM_NAME.flv
wss://localhost:8443/live/STREAM_NAME.flv
注意:Web瀏覽器播放自簽名的證書需先添加信任才能訪問
API
保護API
const config = {
.......
auth: {
api : true,
api_user: 'admin',
api_pass: 'nms2018',
},
......
}
注意:基於Basic auth提供驗證,請注意修改密碼,默認並未開啟。
服務器信息統計
http://localhost:8000/api/server
{
"os": {
"arch": "x64",
"platform": "darwin",
"release": "16.7.0"
},
"cpu": {
"num": 8,
"load": 12,
"model": "Intel(R) Core(TM) i7-4790 CPU @ 3.60GHz",
"speed": 3592
},
"mem": {
"totle": 8589934592,
"free": 754126848
},
"net": {
"inbytes": 6402345,
"outbytes": 6901489
},
"nodejs": {
"uptime": 109,
"version": "v8.9.0",
"mem": {
"rss": 59998208,
"heapTotal": 23478272,
"heapUsed": 15818096,
"external": 3556366
}
},
"clients": {
"accepted": 207,
"active": 204,
"idle": 0,
"rtmp": 203,
"http": 1,
"ws": 0
}
}
流信息統計
http://localhost:8000/api/streams
{
"live": {
"s": {
"publisher": {
"app": "live",
"stream": "s",
"clientId": "U3UYQ02P",
"connectCreated": "2017-12-21T02:29:13.594Z",
"bytes": 190279524,
"ip": "::1",
"audio": {
"codec": "AAC",
"profile": "LC",
"samplerate": 48000,
"channels": 6
},
"video": {
"codec": "H264",
"width": 1920,
"height": 1080,
"profile": "Main",
"level": 4.1,
"fps": 24
}
},
"subscribers": [
{
"app": "live",
"stream": "s",
"clientId": "H227P4IR",
"connectCreated": "2017-12-21T02:31:35.278Z",
"bytes": 18591846,
"ip": "::ffff:127.0.0.1",
"protocol": "http"
},
{
"app": "live",
"stream": "s",
"clientId": "ZNULPE9K",
"connectCreated": "2017-12-21T02:31:45.394Z",
"bytes": 8744478,
"ip": "::ffff:127.0.0.1",
"protocol": "ws"
},
{
"app": "live",
"stream": "s",
"clientId": "C5G8NJ30",
"connectCreated": "2017-12-21T02:31:51.736Z",
"bytes": 2046073,
"ip": "::ffff:192.168.0.91",
"protocol": "rtmp"
}
]
},
"stream": {
"publisher": null,
"subscribers": [
{
"app": "live",
"stream": "stream",
"clientId": "KBH4PCWB",
"connectCreated": "2017-12-21T02:31:30.245Z",
"bytes": 0,
"ip": "::ffff:127.0.0.1",
"protocol": "http"
}
]
}
}
}
轉 HLS/DASH 直播流
const { NodeMediaServer } = require('node-media-server');
const config = {
rtmp: {
port: 1935,
chunk_size: 60000,
gop_cache: true,
ping: 60,
ping_timeout: 30
},
http: {
port: 8000,
mediaroot: './media',
allow_origin: '*'
},
trans: {
ffmpeg: '/usr/local/bin/ffmpeg',
tasks: [
{
app: 'live',
hls: true,
hlsFlags: '[hls_time=2:hls_list_size=3:hls_flags=delete_segments]',
dash: true,
dashFlags: '[f=dash:window_size=3:extra_window_size=5]'
}
]
}
};
var nms = new NodeMediaServer(config)
nms.run();
直播錄制為MP4文件
const { NodeMediaServer } = require('node-media-server');
const config = {
rtmp: {
port: 1935,
chunk_size: 60000,
gop_cache: true,
ping: 60,
ping_timeout: 30
},
http: {
port: 8000,
mediaroot: './media',
allow_origin: '*'
},
trans: {
ffmpeg: '/usr/local/bin/ffmpeg',
tasks: [
{
app: 'vod',
mp4: true,
mp4Flags: '[movflags=faststart]',
}
]
}
};
var nms = new NodeMediaServer(config)
nms.run();
Rtsp/Rtmp 中繼
NodeMediaServer 使用ffmpeg實現RTMP/RTSP的中繼服務。(我們主要應用這里將rtsp轉為rtmp)
靜態拉流
靜態拉流模式在服務啟動時執行,當發生錯誤時自動重連。可以是一個直播流,也可以是一個本地文件。理論上並不限制是RTSP或RTMP協議
relay: {
ffmpeg: '/usr/local/bin/ffmpeg',
tasks: [
{
app: 'cctv',
mode: 'static',
edge: 'rtsp://184.72.239.149/vod/mp4:BigBuckBunny_175k.mov',//rtsp
name: 'BigBuckBunny'
rtsp_transport : 'tcp' //['udp', 'tcp', 'udp_multicast', 'http']
}, {
app: 'iptv',
mode: 'static',
edge: 'rtmp://live.hkstv.hk.lxdns.com/live/hks',//rtmp
name: 'hks'
}, {
app: 'mv',
mode: 'static',
edge: '/Volumes/ExtData/Movies/Dancing.Queen-SD.mp4',//本地文件
name: 'dq'
}
]
}
動態拉流
當本地服務器收到一個播放請求,如果這個流不存在,則從配置的邊緣服務器拉取這個流。當沒有客戶端播放這個流時,自動斷開。
relay: {
ffmpeg: '/usr/local/bin/ffmpeg',
tasks: [
{
app: 'live',
mode: 'pull',
edge: 'rtmp://192.168.0.20',
}
]
}
動態推流
當本地服務器收到一個發布請求,自動將這個流推送到邊緣服務器。
relay: {
ffmpeg: '/usr/local/bin/ffmpeg',
tasks: [
{
app: 'live',
mode: 'push',
edge: 'rtmp://192.168.0.10',
}
]
}
安裝應用流程
安裝ffmpeg(版本要求4.0.0以上)
# 進入/usr/local/src目錄安裝yasm
cd /usr/local/src
wget http://www.tortall.net/projects/yasm/releases/yasm-1.3.0.tar.gz
tar zxvf yasm-1.3.0.tar.gz
cd yasm-1.3.0/
./configure
make && make install
# 進入/usr/local/src目錄安裝
cd /usr/local/src
wget https://ffmpeg.org/releases/ffmpeg-4.0.tar.bz2
tar jxvf ffmpeg-4.0.tar.bz2
cd ffmpeg-4.0/
./configure
make && make install
# 查看是否安裝成功
cd /usr/local/bin
ll (存在ffmpeg可執行文件)
安裝Node-Media-Server
# 克隆工程到本地/usr/local/src目錄
cd /usr/local/src
git clone git@github.com:qq1126176532/Node-Media-Server.git
# 開始安裝npm
curl --silent --location https://rpm.nodesource.com/setup_10.x | bash -
yum install -y nodejs
npm install -g cnpm --registry=https://registry.npm.taobao.org
# 查看版本確認安裝成功
npm -v
#執行安裝
cd Node-Media-Server/
npm i
#修改啟動腳本(這里以簡單工作模式啟動為例,多核心集群模式結合中繼模式存在一些問題)
vim app.js:
---
const { NodeMediaServer } = require('./index');
const config = {
rtmp: {
port: 1935,
chunk_size: 60000,
gop_cache: true,
ping: 60,
ping_timeout: 30
},
http: {
port: 8000,
webroot: './public',
mediaroot: './media',
allow_origin: '*'
},
https: {
port: 8443,
key: './privatekey.pem',
cert: './certificate.pem',
},
auth: {
api: true,
api_user: 'admin',
api_pass: 'admin',
play: false,
publish: false,
secret: 'nodemedia2017privatekey'
},
//引入中繼模式任務
relay: {
//指定ffmpeg可執行文件位置
ffmpeg: '/usr/local/bin/ffmpeg',
tasks: [
{
//應用名稱
app: 'iptv',
//工作模式 靜態即可
mode: 'static',
//中繼地址
edge: 'rtsp://184.72.239.149/vod/mp4:BigBuckBunny_175k.mov',
//訪問資源名稱
name: 'rtsp',
//傳輸協議
rtsp_transport : 'tcp' //['udp', 'tcp', 'udp_multicast', 'http']
}
]
},
};
let nms = new NodeMediaServer(config)
nms.run();
nms.on('preConnect', (id, args) => {
console.log('[NodeEvent on preConnect]', `id=${id} args=${JSON.stringify(args)}`);
// let session = nms.getSession(id);
// session.reject();
});
nms.on('postConnect', (id, args) => {
console.log('[NodeEvent on postConnect]', `id=${id} args=${JSON.stringify(args)}`);
});
nms.on('doneConnect', (id, args) => {
console.log('[NodeEvent on doneConnect]', `id=${id} args=${JSON.stringify(args)}`);
});
nms.on('prePublish', (id, StreamPath, args) => {
console.log('[NodeEvent on prePublish]', `id=${id} StreamPath=${StreamPath} args=${JSON.stringify(args)}`);
// let session = nms.getSession(id);
// session.reject();
});
nms.on('postPublish', (id, StreamPath, args) => {
console.log('[NodeEvent on postPublish]', `id=${id} StreamPath=${StreamPath} args=${JSON.stringify(args)}`);
});
nms.on('donePublish', (id, StreamPath, args) => {
console.log('[NodeEvent on donePublish]', `id=${id} StreamPath=${StreamPath} args=${JSON.stringify(args)}`);
});
nms.on('prePlay', (id, StreamPath, args) => {
console.log('[NodeEvent on prePlay]', `id=${id} StreamPath=${StreamPath} args=${JSON.stringify(args)}`);
// let session = nms.getSession(id);
// session.reject();
});
nms.on('postPlay', (id, StreamPath, args) => {
console.log('[NodeEvent on postPlay]', `id=${id} StreamPath=${StreamPath} args=${JSON.stringify(args)}`);
});
nms.on('donePlay', (id, StreamPath, args) => {
console.log('[NodeEvent on donePlay]', `id=${id} StreamPath=${StreamPath} args=${JSON.stringify(args)}`);
});
---
# 保存退出
# 啟動服務
nohup node app.js &
# 查看服務是否啟動成功
ps -ef | grep "app.js"
[root@localhost Node-Media-Server]# ps -ef | grep "app.js"
root 37745 127550 7 10:31 pts/1 00:00:00 node app.js
tail -f nohup.out(查看啟動日志)
# 三個所用(1935 8000 8443)端口暴露
firewall-cmd --zone=public --add-port=1935/tcp --permanent
firewall-cmd --zone=public --add-port=8000/tcp --permanent
firewall-cmd --zone=public --add-port=8443/tcp --permanent
#(關閉端口 如果開錯了備用還原)
firewall-cmd --zone=public --remove-port=8080/tcp --permanent
# 重新加載配置
firewall-cmd --reload
# 重啟防火牆
systemctl restart firewalld.service
# 查看所有開放端口
firewall-cmd --zone=public --list-ports
客戶端
- 訪問http://(服務器所在IP):8000
- 可查看簡單樣例 推送本地攝像頭和接收服務端推送的rtmp協議流:rtmp://(服務器所在IP)/iptv/rtsp
- 也可結合 vue-videojs-demo案例優化顯示效果需要和前端同事交流
引用資料及思考:
感謝如下鏈接提供文章資源:
前期准備和思考:
- 1.之前沒做高這方面的內容,起始渡鳥狗哥都找不到幫助太多的有價值的資料。有時候java搞起來不方便也可以傾向於考慮一下其他腳本語言或者方式的實現。
- 2.ws-socket:此方案最終摒棄原因如下:需要對接外網收費服務,免費服務不穩定,需要定期更新密鑰,連接數有限。docker服務地址,官網
- 3.Node-Media-Server方法切合實際需求,可離線內網使用,畫面問題需后期持續優化。
- 4.應用Node.js基於Chrome V8 引擎的 JavaScript 運行環境,使用事件驅動、非阻塞式 I/O 的模型輕量高效。
- 5.服務穩定,支持多協議方便后期擴展,服務單一避免冗余和服務帶來的代碼臃腫,rtsp推流方式多樣,提供加密傳輸機制更加安全。