需求背景:
最近公司项目有了一个需求,接入摄像头到系统中并进行实时监控。要进行接入的摄像头是海康品牌的,首先我在官网上进行了一系列的查询,发现官网上提供的有web开发插件和以及提供了海康开发的SDK包,在一番尝试后,发现这些并不能使用于银河麒麟V4电脑系统,故又在网上进行了一番探索,最终选择了ffmpeg对摄像头进行拉流,然后推送到配置了rtmp模块的nginx上,然后通过flv.js进行拉取直播流。由于本人技术有限,目前是可以实现在网页上进行访问,故在此记录一下。
当有了这个想法的时候,在海康官网了解到了有个api开发接口,不过这个海康官方要求必须签订保密合同才可以获取相关API文件,由于我在公司的职位有限,不能得到公司的相关允许,故联系海康开发者邮箱,感谢海康开发者提供的通过rtsp访问摄像头的API。
举例说明:
通道01主码流:
rtsp://admin:abc12345@172.6.22.234:554/Streaming/Channels/101?transportmode=unicast
通道01子码流:
rtsp://admin:abc12345@172.6.22.234:554/Streaming/Channels/102?transportmode=unicast(单播)
rtsp://admin:abc12345@172.6.22.106:554/Streaming/Channels/102?transportmode=multicast (多播)
rtsp://admin:abc12345@172.6.22.106:554/Streaming/Channels/102 (?后面可省略,默认单播)
通道01第3码流:
rtsp://admin:abc12345@172.6.22.234:554/Streaming/Channels/103?transportmode=unicast
零通道主码流(零通道无子码流):
rtsp://admin:12345@172.6.22.106:554/Streaming/Channels/001
注:新版本URL,通道号全部按顺序从1开始。
以上是官方提供的模板案例。
实际操作:
这套操作实现的逻辑是:在点进直播显示页面时,首先前端对后台发送一个请求,传入摄像头的信息(包含用户名、密码、ip等一系列信息),接着在后端调用新的线程启动ffmpeg拉取摄像头的流并转换为flv格式的视频发送到Nginx服务器上,接着在前创建flv.js访问Nginx上的流媒体资源在页面进行显示。
注意!!! 首先要将nginx跑起来,我这边选择了对nginx配置成了开机自启。也可以选择将nginx放到项目中,在调用直播页面时通过命令跑起来。
后端核心代码:
@RequestMapping("/dev/getRTMPUrlAndStartTranscode")
public String getRTMPUrlAndStartTranscode(Device device){
ReturnDataForJSON returnJson=new ReturnDataForJSON();
long startTime=new Date().getTime();
Thread thread = ThreadUtill.getThreadByName("video" + (Integer.parseInt(device.getPort()) - 1));
if(thread != null) {
System.out.println("找到该线程,Name:" + thread.getName());
thread.stop();
}else {
System.out.println("未找到该线程!");
}
try {
String ffmpegPath = "D:\\enviorment\\ffmpeg-master-latest-win64-gpl\\bin\\ffmpeg";
String rtspUrl = "rtsp://" + device.getAccount() + ":" + device.getPassword() + "@" + device.getIp() + ":554/Streaming/Channels/102";
String rtmpUrl = "rtmp://127.0.0.1:1935/myapp/mystream" + device.getPort();
// 构建 FFmpeg 命令行
List<String> command = new ArrayList<>();
command.add(ffmpegPath);
command.add("-rtsp_transport");
command.add("tcp");
command.add("-i");
command.add(rtspUrl);
command.add("-c:a");
command.add("aac");
command.add("-ar");
command.add("22050");
command.add("-c:v");
command.add("copy");
command.add("-preset");
command.add("veryfast");
command.add("-f");
command.add("flv");
command.add(rtmpUrl);
HaiKangService haiKangService = new HaiKangService(command,"video" + device.getPort());
haiKangService.start();
returnJson=CommonUtil.returnSuccess();
} catch (MyException e) {
returnJson=CommonUtil.returnFail(e.getErrorCode(),e.getMessage());
}
}
public HaiKangService(List<String> command,String name) {
super(name);
this.command = command;
this.name = name;
}
@Override
public void run() {
ProcessBuilder builder = new ProcessBuilder(command);
builder.redirectErrorStream(true); // 将标准错误输出与标准输出合并
// 启动进程
Process process;
try {
process = builder.start();
// 获取进程的输入流(用于读取输出信息)
InputStream inputStream = process.getInputStream();
BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
// 读取输出信息
String line;
while ((line = reader.readLine()) != null) {
//System.out.println(line);
}
// 不再打印监控状态,即ffmpeg异常终止,停止线程。
//Thread.currentThread().interrupt();
} catch (IOException e) {
e.printStackTrace();
}
}
前端核心代码:
<canvas id="h5_canvas_1" style="width:100%;height:100%;"></canvas>
<video id="h5_video_1" style="width:100%;height:100%"></video>
</div>
function initMovie() {
var videoElement = document.getElementById('h5_video_1');
var flvPlayer = flvjs.createPlayer({
type: 'flv',
isLive: true,
hasAudio: false,
hasVideo: true,
enableWorker: true,
enableStashBuffer: true,
stashInitialSize: 128,
url: 'http://127.0.0.1:86/live?port=1935&app=myapp&stream=mystream' + videoIndex
});
flvPlayer.attachMediaElement(videoElement);
flvPlayer.load();
flvPlayer.play();
videoElement.addEventListener("progress", () => {
let end = flvPlayer.buffered.end(0); //获取当前buffered值(缓冲区末尾)
let delta = end - flvPlayer.currentTime; //获取buffered与当前播放位置的差值
console.info("delta:",delta);
// 延迟过大,通过跳帧的方式更新视频
if (delta > 10 || delta < 0) {
this.flvPlayer.currentTime = this.flvPlayer.buffered.end(0) - 1;
return;
}
// 追帧
if (delta > 1) {
videoElement.playbackRate = 1.1;
} else {
videoElement.playbackRate = 1;
}
});
}
麒麟电脑下配置nginx:
配置nginx
第一步: 将openssl、pcre、zlib、nginx-http-flv-module-master和nginx放到同一目录下。并在nginx目录中执行如下指令(确保电脑存在gcc、g++环境)
下方指令中 将user改为电脑的用户,否则执行时会产生报错不存在用户, --prefix指令指定的是输入指令时的前缀,目前具体不了解。
./configure --with-openssl=../openssl-1.0.2s --with-pcre=../pcre-8.45 --with-zlib=../zlib-1.3 --with-pcre-jit --user=nginx --prefix=/home/nginx --with-http_ssl_module --with-http_v2_module --add-module=../nginx-http-flv-module-master
第二步: 执行 make & makeinstall 指令,以管理员身份运行,在执行install过程会创建文件夹,未以管理员身份会报错。
第三步: 进入到/home/nginx/conf/下,修改nginx.conf文件,将其中内容替换为下方内容
worker_processes 1; #运行在 Windows 上时,设置为 1,因为 Windows 不支持 Unix domain socket #worker_processes auto; #1.3.8 和 1.2.5 以及之后的版本
#worker_cpu_affinity 0001 0010 0100 1000; #只能用于 FreeBSD 和 Linux #worker_cpu_affinity auto; #1.9.10 以及之后的版本
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;
bash
keepalive_timeout 65;
server {
listen 86;
location / {
root /var/www;
index index.html index.htm;
}
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root html;
}
location /live {
flv_live on; #打开 HTTP 播放 FLV 直播流功能
chunked_transfer_encoding on; #支持 'Transfer-Encoding: chunked' 方式回复
add_header 'Access-Control-Allow-Origin' '*'; #添加额外的 HTTP 头
add_header 'Access-Control-Allow-Credentials' 'true'; #添加额外的 HTTP 头
}
location /hls {
types {
application/vnd.apple.mpegurl m3u8;
video/mp2t ts;
}
root /tmp;
add_header 'Cache-Control' 'no-cache';
}
location /dash {
root /tmp;
add_header 'Cache-Control' 'no-cache';
}
location /stat {
#推流播放和录制统计数据的配置
rtmp_stat all;
rtmp_stat_stylesheet stat.xsl;
}
location /stat.xsl {
root /var/www/rtmp; #指定 stat.xsl 的位置
}
#如果需要 JSON 风格的 stat, 不用指定 stat.xsl
#但是需要指定一个新的配置项 rtmp_stat_format
#location /stat {
# rtmp_stat all;
# rtmp_stat_format json;
#}
location /control {
rtmp_control all; #rtmp 控制模块的配置
}
}
}
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;
csharp
log_interval 5s; #log 模块在 access.log 中记录日志的间隔时间,对调试非常有用
log_size 1m; #log 模块用来记录日志的缓冲区大小
server {
listen 1935;
server_name www.test.*; #用于虚拟主机名后缀通配
application myapp {
live on;
gop_cache on; #打开 GOP 缓存,减少首屏等待时间
}
}
server {
listen 1935;
server_name *.test.com; #用于虚拟主机名前缀通配
application myapp {
live on;
gop_cache on; #打开 GOP 缓存,减少首屏等待时间
}
}
server {
listen 1935;
server_name www.test.com; #用于虚拟主机名完全匹配
application myapp {
live on;
gop_cache on; #打开 GOP 缓存,减少首屏等待时间
}
}
}
<canvas id="h5_canvas_1" style="width:100%;height:100%;"></canvas>
<video id="h5_video_1" style="width:100%;height:100%"></video>
</div>
function initMovie() {
var videoElement = document.getElementById('h5_video_1');
var flvPlayer = flvjs.createPlayer({
type: 'flv',
isLive: true,
hasAudio: false,
hasVideo: true,
enableWorker: true,
enableStashBuffer: true,
stashInitialSize: 128,
url: 'http://127.0.0.1:86/live?port=1935&app=myapp&stream=mystream' + videoIndex
});
flvPlayer.attachMediaElement(videoElement);
flvPlayer.load();
flvPlayer.play();
videoElement.addEventListener("progress", () => {
let end = flvPlayer.buffered.end(0); //获取当前buffered值(缓冲区末尾)
let delta = end - flvPlayer.currentTime; //获取buffered与当前播放位置的差值
console.info("delta:",delta);
// 延迟过大,通过跳帧的方式更新视频
if (delta > 10 || delta < 0) {
this.flvPlayer.currentTime = this.flvPlayer.buffered.end(0) - 1;
return;
}
// 追帧
if (delta > 1) {
videoElement.playbackRate = 1.1;
} else {
videoElement.playbackRate = 1;
}
});
}
麒麟电脑下配置nginx:
配置nginx
第一步: 将openssl、pcre、zlib、nginx-http-flv-module-master和nginx放到同一目录下。并在nginx目录中执行如下指令(确保电脑存在gcc、g++环境)
下方指令中 将user改为电脑的用户,否则执行时会产生报错不存在用户, --prefix指令指定的是输入指令时的前缀,目前具体不了解。
./configure --with-openssl=../openssl-1.0.2s --with-pcre=../pcre-8.45 --with-zlib=../zlib-1.3 --with-pcre-jit --user=nginx --prefix=/home/nginx --with-http_ssl_module --with-http_v2_module --add-module=../nginx-http-flv-module-master
第二步: 执行 make & makeinstall 指令,以管理员身份运行,在执行install过程会创建文件夹,未以管理员身份会报错。
第三步: 进入到/home/nginx/conf/下,修改nginx.conf文件,将其中内容替换为下方内容
worker_processes 1; #运行在 Windows 上时,设置为 1,因为 Windows 不支持 Unix domain socket #worker_processes auto; #1.3.8 和 1.2.5 以及之后的版本
#worker_cpu_affinity 0001 0010 0100 1000; #只能用于 FreeBSD 和 Linux #worker_cpu_affinity auto; #1.9.10 以及之后的版本
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;
bashkeepalive_timeout 65; server { listen 86; location / { root /var/www; index index.html index.htm; } error_page 500 502 503 504 /50x.html; location = /50x.html { root html; } location /live { flv_live on; #打开 HTTP 播放 FLV 直播流功能 chunked_transfer_encoding on; #支持 'Transfer-Encoding: chunked' 方式回复 add_header 'Access-Control-Allow-Origin' '*'; #添加额外的 HTTP 头 add_header 'Access-Control-Allow-Credentials' 'true'; #添加额外的 HTTP 头 } location /hls { types { application/vnd.apple.mpegurl m3u8; video/mp2t ts; } root /tmp; add_header 'Cache-Control' 'no-cache'; } location /dash { root /tmp; add_header 'Cache-Control' 'no-cache'; } location /stat { #推流播放和录制统计数据的配置 rtmp_stat all; rtmp_stat_stylesheet stat.xsl; } location /stat.xsl { root /var/www/rtmp; #指定 stat.xsl 的位置 } #如果需要 JSON 风格的 stat, 不用指定 stat.xsl #但是需要指定一个新的配置项 rtmp_stat_format #location /stat { # rtmp_stat all; # rtmp_stat_format json; #} location /control { rtmp_control all; #rtmp 控制模块的配置 } }
}
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;
csharplog_interval 5s; #log 模块在 access.log 中记录日志的间隔时间,对调试非常有用 log_size 1m; #log 模块用来记录日志的缓冲区大小 server { listen 1935; server_name www.test.*; #用于虚拟主机名后缀通配 application myapp { live on; gop_cache on; #打开 GOP 缓存,减少首屏等待时间 } } server { listen 1935; server_name *.test.com; #用于虚拟主机名前缀通配 application myapp { live on; gop_cache on; #打开 GOP 缓存,减少首屏等待时间 } } server { listen 1935; server_name www.test.com; #用于虚拟主机名完全匹配 application myapp { live on; gop_cache on; #打开 GOP 缓存,减少首屏等待时间 } }
}