nodejs+express+ffmpeg+jsmpeg 实现rtsp转码网页播放

1、版本说明

nodejs版本

 

ffmpeg

 

 

 

2.js 安装

1
2
3
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服务

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
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转发

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
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服务

1
node express-real-time-video.js

 

 

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

 

9、管理express 脚本

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
#!/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

  

1
2
3
4
5
6
7
8
# 开启
bash express-ffmpeg.sh -start
 
# 关闭
bash express-ffmpeg.sh -stop
 
# 重启
bash express-ffmpeg.sh -restart

  

 

posted @   suphowe  阅读(942)  评论(0编辑  收藏  举报
编辑推荐:
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
· 浏览器原生「磁吸」效果!Anchor Positioning 锚点定位神器解析
· 没有源码,如何修改代码逻辑?
阅读排行:
· 全程不用写代码,我用AI程序员写了一个飞机大战
· DeepSeek 开源周回顾「GitHub 热点速览」
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 记一次.NET内存居高不下排查解决与启示
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
点击右上角即可分享
微信分享提示

目录导航