简介
因使用大华摄像头,需要在系统中实时播放视频,因此通过自己搭建一个流媒体服务器,将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");
}
}