摄像头rtsp流转http-flv实现低延迟实时在线播放

简介

因使用大华摄像头,需要在系统中实时播放视频,因此通过自己搭建一个流媒体服务器,将rtsp流转为flv格式,前端再使用flv.js实现在线播放功能。

一、下载nginx

1、更新相关依赖

yum -y install gcc zlib zlib-devel pcre-devel openssl openssl-devel

2、下载nginx

cd /usr/local
mkdir nginx
cd nginx
wget http://nginx.org/download/nginx-1.19.3.tar.gz
tar -zxvf nginx-1.19.3.tar.gz

二、下载nginx-http-flv-module

下载地址
直接下载最新的代码上传到服务器/usr/local/

三、配置环境支持http2.0

注:配置http2.0是为了解决同源策略的限制,同一源下只能同时创建6个请求,播放6路视频。若无此需求,可跳过第一步,直接使用http即可。

1、更新openssl版本

openssl version

在这里插入图片描述
openssl版本在1.0.2以上则不需要更新,否则需要升级版本,如下是升级为1.1.1n

cd /usr/local/
mkdir openssl
cd openssl
wget https://www.openssl.org/source/openssl-1.1.1n.tar.gz
tar -zxvf openssl-1.1.1n.tar.gz
cd openssl-1.1.1n 
./config
make && make install 

备份原有的openssl

mv /usr/bin/openssl /usr/bin/openssl.bak
mv /usr/include/openssl /usr/include/openssl.bak 

添加新的软链接

ln -s /usr/local/bin/openssl /usr/bin/openssl
ln -s /usr/local/include/openssl/ /usr/include/openssl
echo "/usr/local/lib64" >> /etc/ld.so.conf  
ldconfig -v

2、编译nginx

cd  /usr/local/nginx-1.19.3
./configure --with-http_ssl_module --with-openssl=/usr/local/openssl/openssl-1.1.1n --with-http_v2_module --add-module=/usr/local/nginx-http-flv-module

其中--with-openssl 是openssl的路径,--add-module 是nginx-http-flv-module的路径

make -j4
make install

3、生成https测试证书

cd /usr/local/nginx
mkdir  certs
cd /certs
openssl genrsa -des3 -out server.key 2048
openssl rsa -in server.key -out server.key
openssl req -new -key server.key -out server.csr
openssl x509 -req -days 365 -in server.csr -signkey server.key -out server.crt

4、配置nginx.conf

完整配置文件如下

worker_processes  1;
error_log logs/error.log error;


#如果此模块被编译为动态模块并且要使用与 RTMP 相关的功能时,
#必须指定下面的配置项并且它必须位于 events 配置项之前,
#否则 NGINX 启动时不会加载此模块或者加载失败

#load_module modules/ngx_http_flv_live_module.so;
events {
    
    
    worker_connections  4096;
}

http {
    
    
    include       mime.types;
    default_type  application/octet-stream;

    keepalive_timeout  65;

    server {
    
    
        listen       18071;
        location  /live {
    
    
        	flv_live on; 
	        chunked_transfer_encoding on; 

        	add_header 'Access-Control-Allow-Origin' '*';
	        add_header 'Access-Control-Allow-Credentials' 'true';
			add_header 'Access-Control-Allow-Headers' 'X-Requested-With';
			add_header 'Access-Control-Allow-Methods' 'GET,POST,OPTIONS';
			add_header 'Cache-Control' 'no-cache';
        }

	location /hls {
    
    
            types {
    
    
                application/vnd.apple.mpegurl m3u8;
                video/mp2t ts;
            }
            root /tmp;  # hls方式m3u8访问的文件路径
        }
        #stat  json格式
      	location /stat {
    
    
	  		rtmp_stat all;
            rtmp_stat_format json;
      	}

  #      location /stat {
    
    
  #         rtmp_stat all;
  #          rtmp_stat_stylesheet stat.xsl;
  #      }

 #       location /stat.xsl {
    
    
 #           root /usr/local/nginx-http-flv-module; #指定 stat.xsl 的位置
 #       }
    }
    server {
    
    
	        listen 18181 ssl http2;
			server_name  www.gongl.com;
	 		ssl_certificate      "/usr/local/nginx/certs/server.crt";  # server公钥证书的路径
			ssl_certificate_key  "/usr/local/nginx/certs/server.key";  # server私钥的路径
			ssl_session_cache    shared:SSL:1m;	
			ssl_session_timeout  5m;
			ssl_ciphers  HIGH:!aNULL:!MD5;
			ssl_prefer_server_ciphers  on;
			proxy_set_header Host $http_host;
			proxy_set_header X-Real-IP $remote_addr;
			proxy_set_header REMOTE-HOST $remote_addr;
			proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
			location /{
    
    
				proxy_pass http://192.168.3.122:18071/;
			} 
	 }
}

rtmp_auto_push on;
rtmp_auto_push_reconnect 1s;
rtmp_socket_dir /tmp;

rtmp {
    
    
    out_queue           4096;
    out_cork            8;
    max_streams         128;
    timeout             15s;
    drop_idle_publisher 15s;

    log_interval 5s; #log 模块在 access.log 中记录日志的间隔时间,对调试非常有用
    log_size     1m; #log 模块用来记录日志的缓冲区大小

    server {
    
    
        listen 18072;
        server_name  localhost;
        application transition{
    
    
            live on;
            gop_cache off; 
            #播放之前调用(可通过nginx直接配置鉴权或定义到项目中去鉴权)
             on_play           http://192.168.3.13:18061/flv/on_play; 
             #断开之后调用
           #  on_play_done      http://192.168.3.13:18061/flv/on_play_done;
     }
}

指定配置文件启动

../sbin/nginx -s stop
../sbin/nginx -c /usr/local/nginx/conf/nginx.conf

注:若存在连接的http,通过reload重载nginx无法断开之前的http连接。当修改了配置文件,使用stop停止nginx之后再重新启动

四、安装ffmpeg

cd /usr/local
mkdir ffmpeg
cd ffmpeg

1、升级yasm(yasm和nasm选一即可)

cd /usr/local/ffmpeg
mkdir yasm
cd yasm/
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
yasm --version

2、升级nasm

cd /usr/local/ffmpeg
mkdir nasm
cd nasm/

wget https://www.nasm.us/pub/nasm/releasebuilds/2.14.02/nasm-2.14.02.tar.bz2
tar -jxvf nasm-nasm-2.14.02.tar.bz2
cd nasm-2.14.02

./configure
make && make install
nasm --version

3、安装x264视频编码库

cd /usr/local/ffmpeg
mkdir x264
cd x264/

wget https://code.videolan.org/videolan/x264/-/archive/master/x264-master.tar.gz
tar -zxvf x264-master.tar.gz
cd x264-master

./configure
make && make install

4、安装ffmpeg

cd /usr/local/ffmpeg
wget http://www.ffmpeg.org/releases/ffmpeg-6.0.tar.gz
tar -zxvf ffmpeg-6.0.tar.gz
cd ffmpeg-6.0
./configure --prefix=/usr/local/ffmpeg
make &&  make install

配置环境变量

vim  /etc/profile

在最后添加

export PATH=$PATH:/usr/local/ffmpeg/bin     # 指向自己安装的ffmpeg下的bin目录

source /etc/profile  #设置生效
ffmpeg -version  # 查看版本

五、转码测试

1、转码为http-flv直接访问

# 执行指令,转换rtsp转为rtmp
ffmpeg  -rtsp_transport tcp -i "rtsp视频流地址" -vcodec libx264 -vprofile baseline -an -tune zerolatency -preset ultrafast -g 25 -f flv "rtmp://192.168.3.122:18072/transition/home"
# 其中 18072为nginx下配置的rtmp端口,transition为nginx配置的location,home是自定义房间号

出现如下表示成功
在这里插入图片描述
vlc下载地址:http://www.videolan.org/vlc/
使用vlc直接访问,注意其中的18181是https配置的端口,port对应上方的18072,app对应上方的location,stream对应上方的home
在这里插入图片描述

点击播放查看,楼主测试的延迟大概在1s内,符合预期,直接采用该方案

2、转码为hls通过m3u8文件访问

hls保存了最新的视频文件,可自定义保存时限,可通过保存的文件实现回放功能,测试的延迟在2-3秒

# 执行指令,转为m3u8文件
ffmpeg  -rtsp_transport tcp -i "rtsp视频流地址" -vcodec libx264 -vprofile baseline  -an -f hls -hls_time 5 -hls_list_size 2 -hls_flags 2  "/tmp/hls/22222.m3u8"
/tmp/hls/测试.m3u8  # 注意这里配置的nginx中fls下root 配置的 /tmp,根据自己的配置修改

-hls_time n: # 设置每片的长度,默认值为2。单位为秒
-hls_list_size n: # 设置播放列表保存的最多条目,设置为0会保存有所片信息,默认值为5
-hls_flags n:# 设置多少片之后开始覆盖,如果设置为0则不会覆盖,默认值为0.这个选项能够避免在磁盘上存储过多的片,而且能够限制写入磁盘的最多的片的数量
-hls_start_number n:# 设置播放列表中sequence number的值为number,默认值为0

查看文件 cd /tmp/hls
在这里插入图片描述
使用vlc直接访问
在这里插入图片描述

六、通过java下发指令

1、配置文件yml

#flv视频地址
camera:
  #转码服务器账号
  username: root 
  #转码服务器密码
  password: 123456
 #转码服务器ip
  host: 192.168.3.122  
  #播放视频流服务器地址
  flv-url: https://192.168.3.122:18181/live?port=18072&app=transition&stream=%s 
  stat-url: http://192.168.3.122:18071/stat
  #接收流服务器地址
  rtmp-url: rtmp://192.168.3.122:18072/transition/
  #关闭指定进程
  close-command: ps -ef | grep 'ffmpeg.*%s' | grep -v grep | awk '{
    
    print $2}'  | sed -e "s/^/kill -9 /g" | sh - &
  #开始转码推流指令
  open-command: nohup /usr/local/ffmpeg/bin/ffmpeg  -rtsp_transport tcp -i  "%s" -codec:v libx264 -vprofile baseline  -codec:a aac -tune zerolatency -preset ultrafast -ar 24000 -ac 1 -g 25 -s 1600*900 -f flv "${
    
    camera.rtmp-url}%s" 1>/dev/null 2>&1 &

2、远程执行linux指令工具类

package com.gongl.common.utils;

import com.jcraft.jsch.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;

import javax.annotation.PostConstruct;
import java.io.*;

@Service
public class ExecuteShellUtil {
    
    
    private static final Logger log = LoggerFactory.getLogger(ExecuteShellUtil.class);
    @Value("${camera.username}")
    private String username;
    @Value("${camera.password}")
    private String password;
    @Value("${camera.host}")
    private String host;
    private Session session;

    public ExecuteShellUtil() {
    
    
    }

    @PostConstruct
    private void connect() throws JSchException {
    
    
        JSch jsch = new JSch();
        session = jsch.getSession(username, host);
        session.setPassword(password);
        java.util.Properties config = new java.util.Properties();
        config.put("StrictHostKeyChecking", "no");
        session.setConfig(config);
        session.connect();
        log.info("连接到SFTP成功。host: " + host);
    }


    /**
     *
     * @param command
     * @return 0--成功,1--失败
     * @throws Exception
     */
    public int execCmd(String command) throws Exception {
    
    
        int returnCode = -1;
        Channel channel = session.openChannel("exec");
        ((ChannelExec) channel).setCommand(command);
        channel.setInputStream(null);
        ((ChannelExec) channel).setErrStream(System.err);
        BufferedReader reader = new BufferedReader(new InputStreamReader(channel.getInputStream()));
        channel.connect();
        StringBuffer buf = new StringBuffer();
        int c = -1;
        while ((c = reader.read()) != -1) {
    
    
            buf.append((char) c);
        }
        reader.close();
        if (channel.isClosed()) {
    
    
            returnCode = channel.getExitStatus();
        }
        log.info("---给: " + host + " 发送指令:" + command + " 。result:" + returnCode);
        channel.disconnect();
        return returnCode;
    }


}

3、RestTemplate工具类,用于维护不需要转码的rtsp

之前编写的RestTemplate工具类:https://blog.csdn.net/weixin_47914635/article/details/123206595

4、定义播放鉴权

该内容对应nginx中rtmp配置的on_play配置,on_play_done根据自己的需要同理实现即可。
可通过nginx直接配置鉴权或定义到项目中去鉴权

package com.gongl.web.controller.base;

import com.gongl.common.core.domain.AjaxResult;
import com.gongl.quartz.task.FlvMonitorTask;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.servlet.http.HttpServletRequest;

/**
 * @author gongl
 * @date 2023-03-22
 */
@RestController
@RequestMapping("/flv")
public class FlvController {
    
    
    @Autowired
    private FlvMonitorTask flvMonitorTask;

    @RequestMapping("/on_play")
    public AjaxResult<String> on_play(HttpServletRequest request) {
    
    
        //鉴权,根据自己系统需要进行鉴权。返回200成功,返回其他失败
        
        //播放
        String stream = request.getParameter("stream");
        flvMonitorTask.openStream(stream);
        return AjaxResult.success();
    }
}

5、编写定时检测无人观看流以及发送转换流的方法

package com.gongl.quartz.task;

import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.gongl.common.utils.ExecuteShellUtil;
import com.gongl.common.utils.http.RestTemplateUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;



/**
 * @author gongl
 * @date 2023-03-22
 */
@Component
public class FlvMonitorTask {
    
    
    private static final Logger log = LoggerFactory.getLogger(FlvMonitorTask.class);
    @Autowired
    private ExecuteShellUtil executeShellUtil;
    @Value("${camera.stat-url}")
    private String stat;
    @Value("${camera.close-command}")
    private String closeCommand;
    @Value("${camera.open-command}")
    private String openCommand;

	//定时关闭无人观看的视频转码流
    @Scheduled(cron = "0 0/5 * * * ?")
    public void doTask() {
    
    
        JSONArray streams = getBody();
        streams.forEach(x -> {
    
    
            JSONObject object = (JSONObject) x;
            String name = object.getString("name");//摄像头编号,即转换配置的房间号
            JSONArray clients = object.getJSONArray("clients");
            if (clients.size() <= 1) {
    
    
                try {
    
    
                    executeShellUtil.execCmd(String.format(closeCommand, name));
                } catch (Exception e) {
    
    
                    log.error("关闭视频流异常---->" + e);
                }
            }
        });
    }
	//需要播放的视频是否已经在转码
    public void openStream(String stream) {
    
    
        JSONArray streams = getBody();
        boolean flag = true;
        for (Object x : streams) {
    
    
            JSONObject object = (JSONObject) x;
            String name = object.getString("name");
            if (stream.equals(name)) {
    
    
                flag = false;
                break;
            }
        }
        //若没有转码,则开始转码
        if (flag) {
    
    
            try {
    
    
            //根据stream摄像头编号得到摄像头的rtsp流地址
                executeShellUtil.execCmd(String.format(openCommand,"rtsp地址" , stream));
            } catch (Exception e) {
    
    
                log.error("启动视频流转换异常---->" + e);
            }
        }
    }
	//用于获取转码服务器在线的视频流及实时在线连接
    private JSONArray getBody() {
    
    
        String body = RestTemplateUtils.get(stat, String.class).getBody();
        JSONObject jsonObject = JSONObject.parseObject(body);
        JSONObject httpFlv = jsonObject.getJSONObject("http-flv");
        JSONObject server = httpFlv.getJSONArray("servers").getJSONObject(0);
        JSONObject application = server.getJSONArray("applications").getJSONObject(0);
        JSONObject live = application.getJSONObject("live");
        return live.getJSONArray("streams");
    }
}

七、flv.js做追帧、断线重连等

参考文章:http://news.558idc.com/396049.html

猜你喜欢

转载自blog.csdn.net/weixin_47914635/article/details/129691433
今日推荐