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

  

 

posted @ 2020-09-29 14:20  suphowe  阅读(1098)  评论(0)    收藏  举报