nodejs+express+ffmpeg+jsmpeg 实现rtsp转码网页播放
1、版本说明
nodejs版本

ffmpeg

2.js 安装
npm install ws -g npm install express --save
3.ffmpeg管理脚本 控制ffmpeg的启停和查询
#!/bin/bash # 1. list all pushing streams or a specific dst pattern # --list [--dst_pattern=''] # # return json is like: # {"ret": 0, "streams": [ # "http://127.0.0.1:8081/supersecret/live1" # ], "msg": "success"} # 2. stop all streams or a specific pattern # --stop [--dst_pattern=''] # # return json is like: # {"ret":0, "msg":"stop pids: 4428 4441"} # 3. start new forward with params # --start_new [--src_addr='your rtsp addr' --dst_addr='your push dst addr' --video_w='video push width' --video_h='video push height'] # # return json is like: # {"ret":0, "msg":"start new: http://127.0.0.1:8081/supersecret/live1"} cd `dirname $0` ret_common() { local ret=$1 local msg=$2 echo {\"ret\":$ret, \"msg\":\"$msg\"} } list_forward() { local dst_pattern= local x= for ((x=1; x<=$#; x++)); do p=$(eval echo \$$x) case "$p" in --dst_pattern=*) dst_pattern=${p#--dst_pattern=} ;; *) ret_common -1 "unknown param $p, usage $0 --list [--dst_pattern=xxx]" return ;; esac done local list= if [ -n "$dst_pattern" ]; then list=$(ps -A x | grep forward_stream.sh | grep -v grep | grep -o "\-\-dst_addr=.*" | grep "$dst_pattern" | awk '{print $1}' | sed 's/--dst_addr=//') else list=$(ps -A x | grep forward_stream.sh | grep -v grep | grep -o "\-\-dst_addr=.*" | awk '{print $1}' | sed 's/--dst_addr=//') fi # generate json ret msg local list_a=($list) echo {\"ret\": 0, \"streams\": [ for ((x=0; x<${#list_a[*]}; x++)); do if (( $x > 0 )); then echo ,; fi echo \"${list_a[$x]}\" done echo ], \"msg\": \"success\"} } stop_forward() { local dst_pattern= local x= for ((x=1; x<=$#; x++)); do p=$(eval echo \$$x) case "$p" in --dst_pattern=*) dst_pattern=${p#--dst_pattern=} ;; *) ret_common -1 "unknown param $p, usage $0 --list [--dst_pattern=xxx]" return ;; esac done local stop_pids= if [ -n "$dst_pattern" ]; then #stop specific stop_pids=$(ps -A x | grep forward_stream.sh | grep -v grep | grep "\-\-dst_addr=.*${dst_pattern}.*" | awk '{print $1}') else # stop all stop_pids=$(ps -A x | grep forward_stream.sh | grep -v grep | awk '{print $1}') fi local child= for x in $stop_pids; do child=$(pgrep -P $x) kill $x $child &>/dev/null done ret_common 0 "stop pids: $stop_pids" } start_new() { local src_addr= local dst_addr= local video_w= local video_h= for ((x=1; x<=$#; x++)); do p=$(eval echo \$$x) case "$p" in --src_addr=*) src_addr=${p#--src_addr=} ;; --dst_addr=*) dst_addr=${p#--dst_addr=} ;; --video_w=*) video_w=${p#--video_w=} ;; --video_h=*) video_h=${p#--video_h=} ;; *) ret_common -1 "unknown param $p" return ;; esac done bash ./forward_stream.sh --src_addr="$src_addr" --video_w=$video_w --video_h=$video_h --dst_addr="$dst_addr" &>/dev/null & ret_common 0 "start new: $dst_addr" } # parse cmd ret=-1 if (( $# > 0 )); then case "$1" in --list) shift list_forward $@ ;; --stop) shift stop_forward $@ ;; --start_new) shift start_new $@ ;; *) ret_common -1 "unknown param $1" ;; esac else ret_common -2 "need more param" fi
4、express-real-time-video.js express服务
var express = require('express');
var app = express();
const exec = require('child_process').exec;
var serverIp = '127.0.0.1' //服务器地址,自行修改
var localIp = 'localhost'
var websocketUri = 'http://' + localIp
var secret = 'live'
var websocketPort = 8081
var pushPort = 8082
var expressPort = 8085
var rtspWidth = 1280
var rtspHeight = 720
app.all('*', function(req, res, next) {
//设为指定的域
res.header('Access-Control-Allow-Origin', "*");
res.header("Access-Control-Allow-Headers", "X-Requested-With");
res.header('Access-Control-Allow-Headers', 'Content-Type');
res.header("Access-Control-Allow-Methods", "PUT,POST,GET,DELETE,OPTIONS");
res.header('Access-Control-Allow-Credentials', true);
res.header("X-Powered-By", ' 3.2.1');
next();
});
// POST 请求
app.post('/', function (req, res) {
var rtspId = req.query.rtspId;
var rtspUrl = req.query.rtspUrl;
var pushUrl = websocketUri + ':' + websocketPort + '/' + secret + '/' + rtspId;
//开启ffmpeg
execQuery(rtspUrl, pushUrl, rtspWidth, rtspHeight);
var wsUrl = 'ws://' + serverIp + ':' + pushPort + '/' + rtspId;
res.send(wsUrl)
})
var server = app.listen(expressPort, function () {
console.log("应用实例,访问地址为 http://" + localIp + ':' + expressPort)
exeWebsocket()
})
//开启websocket
function exeWebsocket(){
const startWebsocket = 'node realtime-video-websocket.js ' + secret + ' ' + websocketPort + ' ' + pushPort;
console.log('[api] start websocket server:' + startWebsocket);
exec(startWebsocket, function(err,stdout,stderr){
if(err) {
console.log('get weather api error:'+stderr);
} else {
console.log(stdout);
}
});
}
//开启推流
function execFFmpeg(rtspUrl, pushUrl, rtspWidth, rtspHeight){
const pushFFmpeg = 'bash ./forward_mgr.sh --start_new --src_addr=\'' + rtspUrl + '\' --dst_addr=\'' + pushUrl + '\' --video_w=' + rtspWidth + ' --video_h=' + rtspHeight;
console.log('[api] start push rtsp sh:' + pushFFmpeg);
exec(pushFFmpeg, function(err,stdout,stderr){
if(err) {
console.log('get weather api error:'+stderr);
} else {
console.log(stdout);
}
});
}
//查询rtsp流是否存在
function execQuery(rtspUrl, pushUrl, rtspWidth, rtspHeight){
const queryShell = 'bash ./forward_mgr.sh --list --dst_pattern=\'' + pushUrl + '\'';
console.log('[api] query rtsp sh:' + queryShell);
exec(queryShell,
function(error,stdout,stderr){
if(error) {
console.log('get weather api error:'+stderr);
} else {
var data = JSON.parse(stdout);
if(pushUrl == data.streams[0]){
console.log('[api]rtsp push alive')
} else {
execFFmpeg(rtspUrl, pushUrl, rtspWidth, rtspHeight)
}
}
}
);
}
5.realtime-video-websocket.js 实时websocket转发
var fs = require('fs'), http = require('http'), WebSocket = require('ws');
const exec = require('child_process').exec;
if (process.argv.length < 3) {
console.log('输入正确参数');
process.exit();
}
var stream_secret = process.argv[2];//密码
var stream_port = process.argv[3] || 8081;//ffpeng推送端口
var websocket_port = process.argv[4] || 8082;//前端websocket端口 ,比如:8082
var record_stream = false;
var totalSize = 0;
function initWebSocket(websocket_port) {
var clientMap = new Map();//缓存,实现多个视频流同时播放的问题
var socketServer = new WebSocket.Server({
port : websocket_port,
perMessageDeflate : false
});
socketServer.on('connection', function(socket, upgradeReq) {
var url = upgradeReq.socket.remoteAddress + upgradeReq.url;
var params = url.substr(1).split('/');
printData('initWebSocket', params)
var key = params[1];//key就是通过url传递过来的标识比如:(ws://127.0.0.1:8082/live3)其中live3就是这个标识,其他的流可随机生成其他的字符串
var ips = params[0].split(':');
console.log('IP:' + ips[ips.length-1] + '已连接')
var clients = clientMap.get(key);
if(!clients){
clients = new Set();
clientMap.set(key,clients);
}
clients.add(socket);
totalSize++;
process.stdout.write("[INFO]:a new connection, the current number of connections: " + totalSize + ".\r");
socket.on('close', function(code, message) {
var clientSet = clientMap.get(key);
if(clientSet){
clientSet.delete(socket);
totalSize--;
if(clientSet.size == 0){
clientMap.delete(key);
//关闭ffmpeg
closeFFmpeg(key);
}
}
process.stdout.write("[INFO]:close a connection, the current number of connections:" + totalSize + ".\r");
});
socket.on("error",function(err){
// 出错触发 //
console.log("header err")
console.log(err)
})
});
//广播
socketServer.broadcast = function(data, theme) {
var clients = clientMap.get(theme);
if (clients) {
clients.forEach(function (client, set) {
if(client.readyState === WebSocket.OPEN){
client.send(data);
}
});
}
};
return socketServer;
}
function initHttp(stream_port, stream_secret, record_stream, socketServer) {
var streamServer = http.createServer(
function(request, response) {
var params = request.url.substr(1).split('/');
console.log("params--->" + params);
console.log("stream_secret--->" + stream_secret);
printData('initHttp', params)
if (params.length != 2) {
process.stdout.write("\n[ERROR]:Incorrect parameters, enter password and push theme");
response.end();
}
if (params[0] !== stream_secret) {
process.stdout.write("\n[ERROR]:Password error: "+request.socket.remoteAddress+":"+request.socket.remotePort+"");
response.end();
}
response.connection.setTimeout(0);
request.on('data', function(data) {
socketServer.broadcast(data, params[1]);
if (request.socket.recording) {
request.socket.recording.write(data);
}
});
request.on('end', function() {
process.stdout.write("\n[INFO]:close request");
if (request.socket.recording) {
request.socket.recording.close();
}
});
if (record_stream) {
var path = 'recordings/' + Date.now() + '.ts';
request.socket.recording = fs.createWriteStream(path);
}
}).listen(stream_port);
console.log('started rtsp WebSocket service in secret is [%s], service port is [%s], ws port is [%s].',stream_secret,stream_port,websocket_port);
}
function closeFFmpeg(key){
var pushUrl = 'http://localhost:8081/live/' + key;
const closeShell = 'bash ./forward_mgr.sh --stop --dst_pattern=\'' + pushUrl + '\'';
console.log('[socket] close push');
console.log('[socket] close push sh:' + closeShell);
exec(closeShell,
function(error,stdout,stderr){
if(error) {
console.log('get weather api error:'+stderr);
} else {
console.log('[socket]close push result:' + stdout);
}
}
);
}
function printData(mark, params){
console.log('============' + mark + '================ \n')
for (var i =0;i<params.length;i++){
console.log(mark + '--------->' + params[i])
}
}
initHttp(stream_port, stream_secret, record_stream, initWebSocket(websocket_port));
console.log("start success\n")
6.页面video.html
<!DOCTYPE html>
<html>
<head>
<title>JSMpeg Stream Client</title>
<style type="text/css">
html, body {
background-color: #111;
text-align: center;
}
</style>
</head>
<body>
<canvas id="video-canvas" ></canvas>
<script src="./lib/jsmpeg.min.js"></script>
<script src="./jquery-1.11.1.min.js"></script>
<script type="text/javascript">
$(function(){
$.ajax({
type: "POST",
url: "http://192.168.200.112:8085/?rtspUrl=rtsp地址&rtspId=rtsp编号",
dataType: "text",
success: function(data){
openVideo(data);
}
});
});
function openVideo(url){
var canvas = document.getElementById('video-canvas');
var player = new JSMpeg.Player(url, {canvas: canvas});
}
</script>
</body>
</html>
7、启动express服务
node express-real-time-video.js

8、页面放到tomcat下,访问页面

9、管理express 脚本
#!/bin/bash
cd `dirname $0`
express_port=8085
push_port=8081
websocket_port=8082
start()
{
echo "begin start express and websocket"
node express-real-time-video.js &
echo "start express and websocket success!"
exit 0;
}
stop()
{
local express_pid=$(netstat -nlp | grep :$express_port | awk '{print $7}' | awk -F"/" '{ print $1 }')
local push_pid=$(netstat -nlp | grep :$push_port | awk '{print $7}' | awk -F"/" '{ print $1 }')
local websocket_pid=$(netstat -nlp | grep :$websocket_port | awk '{print $7}' | awk -F"/" '{ print $1 }')
if [ -n "$express_pid" ]; then
echo "stop express $express_pid"
kill -9 $express_pid || echo "failed kill express"
echo "kill express success!"
fi
if [ -n "$websocket_pid" ]; then
echo "stop websocket $websocket_pid"
kill -9 $websocket_pid || echo "failed kill websocket"
echo "kill websocket success!"
fi
echo "stop success!"
exit 0;
}
restart()
{
echo "begin restart server"
echo "begin stop server"
local express_pid=$(netstat -nlp | grep :$express_port | awk '{print $7}' | awk -F"/" '{ print $1 }')
local push_pid=$(netstat -nlp | grep :$push_port | awk '{print $7}' | awk -F"/" '{ print $1 }')
local websocket_pid=$(netstat -nlp | grep :$websocket_port | awk '{print $7}' | awk -F"/" '{ print $1 }')
if [ -n "$express_pid" ]; then
echo "stop express $express_pid"
kill -9 $express_pid || echo "failed kill express"
echo "kill express success!"
fi
if [ -n "$websocket_pid" ]; then
echo "stop websocket $websocket_pid"
kill -9 $websocket_pid || echo "failed kill websocket"
echo "kill websocket success!"
fi
echo "stop server success, begin start"
node express-real-time-video.js & >logFile.log
echo "restart server success!"
exit 0;
}
if (( $# > 0 )); then
case "$1" in
-start)
shift
start $@
;;
-stop)
shift
stop $@
;;
-restart)
shift
restart $@
;;
*)
echo "unknown command $1"
;;
esac
else
echo "need more command"
fi
# 开启 bash express-ffmpeg.sh -start # 关闭 bash express-ffmpeg.sh -stop # 重启 bash express-ffmpeg.sh -restart

浙公网安备 33010602011771号