客户有海康和大华的监控设备,没有买各类安防平台,国标方式需要预留给其他需要接入的系统,得兼容高版本chrome,询问了大华的客服人员,最后选择了该方案进行解决,记录下曲折的过程。延迟大约10秒的样子,应该还能通过设置参数在优化,CPU占用率较高,不适合高并发,小项目用的少可以。如果高并发场景高的还是别用,可以使用ZLMediaKit开源(尝试了下ffmpeg占用率是要低很多,好东西太优秀的国产开源),或者SRS等一些开源的去改吧改吧用。

海康大华摄像机NVR接口方式在高版本chrome浏览器预览的解决方案

  1. rtsp+nginx转m3u8播放

  1. rtsp+nginx转flv播放

  1. ZLMediaKit+wvp拉流

方案一(目前使用方式,不想滋生出其他服务来给自己找麻烦,记录下整个过程):

一、ffmpeg+nginx搭建过程支持h264和h265

#前置安装一些后面需要的
yum -y install git
yum install -y bzip2
yum install -y cmake
yum install unzip -y
yum install gcc-c++ -y
yum install pcre pcre-devel -y
yum install -y  libarchive
yum install zlib zlib-devel -y
yum install openssl openssl-devel -y
#sudo yum -y install cmake   #3.0版本以上
yum install wget -y
cd /usr/local/
sudo wget https://cmake.org/files/v3.22/cmake-3.22.0-rc1-linux-x86_64.tar.gz
tar -zxvf cmake-3.22.0-rc1-linux-x86_64.tar.gz
sudo mv cmake-3.22.0-rc1-linux-x86_64 /opt/cmake-3.22.0
sudo ln -sf /opt/cmake-3.22.0/bin/* /usr/bin/
cmake --version
#下载安装nginx http://nginx.org/en/download.html
cd /usr/local/
tar -zxvf nginx-1.23.3.tar.gzcd nginx-1.23.3
linux 安装flv模块已包含了rtmp
wget https://github.com/winshining/nginx-http-flv-module/archive/master.zip
unzip master.zip
./configure  --prefix=/usr/local/nginx --with-http_ssl_module --with-http_stub_status_module --add-module=/usr/local/nginx-1.23.3/nginx-http-flv-module-master
make && make install
#nginx的配置,修改完重启下nginx

#nginx配置rtmp详解参考

#需要通过nginx简单负载均衡参考

https://blog.csdn.net/weixin_37530941/article/details/128702616

#直播相关的配置的详细介绍https://blog.51cto.com/eguid/5100113

二、对外网nginx的配置如下


user  root root;
worker_processes  2;#设置为内核数*2#error_log  logs/error.log;
#error_log  logs/error.log  notice;
#error_log  logs/error.log  info;#pid        logs/nginx.pid;events {worker_connections  1024;
}http {include       mime.types;default_type  application/octet-stream;#log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '#                  '$status $body_bytes_sent "$http_referer" '#                  '"$http_user_agent" "$http_x_forwarded_for"';#access_log  logs/access.log  main;sendfile        on;#tcp_nopush     on;#keepalive_timeout  0;keepalive_timeout  65;    client_max_body_size 100m;#gzip  on;server {listen       8099;#配置安装服务器的Ip地址,与下面rtmp的配置一致server_name  172.16.121.75;#charset koi8-r;#access_log  logs/host.access.log  main;# location / {#     index  index.html index.htm;#     proxy_pass http://webservers;#     add_header Access-Control-Allow-Origin *;#     add_header Access-Control-Allow-Credentials true;#     add_header Access-Control-Allow-Headers Authorization,Content-Type,Accept,Origin,User-Agent,DNT,Cache-Control,X-Mx-ReqToken,X-Requested-With;#     add_header Access-Control-Allow-Methods GET,POST,OPTIONS;#     try_files $uri $uri/ @router; # }# ffmpeg直播地址location /live {flv_live on;chunked_transfer_encoding  on; #open 'Transfer-Encoding: chunked' responseadd_header 'Access-Control-Allow-Credentials' 'true'; #add additional HTTP headeradd_header 'Access-Control-Allow-Origin' '*'; #add additional HTTP headeradd_header Access-Control-Allow-Headers X-Requested-With;add_header Access-Control-Allow-Methods GET,POST,OPTIONS;add_header 'Cache-Control' 'no-cache';}# This URL provides RTMP statistics in XMLlocation /stat {rtmp_stat all;# Use this stylesheet to view XML as web page# in browserrtmp_stat_stylesheet stat.xsl;}location /stat.xsl {# XML stylesheet to view RTMP stats.# Copy stat.xsl wherever you want# and put the full directory path hereroot /path/to/stat.xsl/;}#使用转发location /hls {# Serve HLS fragmentstypes {application/vnd.apple.mpegurl m3u8;video/mp2t ts;}root html;add_header Cache-Control no-cache;}location /dash {# Serve DASH fragmentsroot /tmp;add_header Cache-Control no-cache;}error_page   500 502 503 504  /50x.html;location = /50x.html {root   html;}error_page  404 403               /404.html;location = /404.html {root /usr/local/nginx/html;} # redirect server error pages to the static page /50x.html#error_page   500 502 503 504  /500.html;location = /500.html {root   /usr/local/nginx/html;}}}
#rtmp设置
rtmp {out_queue               4096;out_cork                 8;max_streams             128;timeout                 15s;drop_idle_publisher     15s;log_interval 5s;log_size     1m;server {listen 1935;      #监听的端口号server_name 172.16.121.75; #与上面监听的8099端口的server名字一致application live {      #自定义的名字live on;}#直播hls配置application hls {live on;hls on;hls_path /usr/local/nginx/html/hls;#直播缓存路径window环境的配置为绝对地址,参考上传的hls_fragment 2s;#设置HLS片段长度。 默认为5秒。hls_playlist_length 5s;#设置HLS播放列表长度。 默认为30秒。hls_continuous on; #连续模式。hls_nested on;     #嵌套模式就是允许如localhost:8099/hts/目录1/目录2/文件名.m3u8;hls_cleanup off; # 切换HLS清理。 默认情况下,该功能处于打开状态。 在这种模式下,nginx缓存管理器进程从HLS目录中删除旧的HLS片段和播放列表,必须关闭才能按目录去删除,如果访问的url没有层级要求,把m3u8的名字取好点就可以没有必要按层级去删除目录或者建目录}}
}

#下载包甩到/home/village/ffmpeg 下根据实际需自己放,或者放到/usr/local下

三、linux环境程序部署相关下载包分享和安装过程

#链接:https://pan.baidu.com/s/1gTB0Hjxa7mqnBjPMKe-YQw

#提取码:0824

#大致的安装过程

cd /home/village/ffmpeg
tar jxvf nasm-2.15.tar.bz2
#安装h264 和 h265时候所需
cd nasm-2.15/
./configure  --prefix=/usr/local
make && make install
nasm --version    # 查看版本号  如果提示命令找不到 配下环境变量
cd /home/village/ffmpeg
tar -zxvf yasm-1.3.0.tar.gz
cd yasm-1.3.0
./configure
make && make install
#安装pkg-config
cd /home/village/ffmpeg
#wget https://pkg-config.freedesktop.org/releases/pkg-config-0.29.2.tar.gz
sudo tar -zxvf pkg-config-0.29.2.tar.gz
cd pkg-config-0.29.2/
sudo ./configure --with-internal-glib
make
make check
make install
export PATH=/usr/local/lib/pkgconfig:$PATH
#查看版本
pkg-config --version
#安装h264
cd /home/village/ffmpeg
tar -jxvf x264-master.tar.bz2
cd x264-master
mkdir /usr/local/x264
./configure --prefix=/usr/local/x264 --enable-shared --enable-static
make && make install
#添加环境变量
vim /etc/profile
#在文件末尾添加
export PATH=/usr/local/x264/bin:$PATH
export PATH=/usr/local/x264/include:$PATH
export PATH=/usr/local/x264/lib:$PATH
source /etc/profile
安装h265
cd /home/village/ffmpeg
tar -zxvf x265_3.2.tar.gz
cd x265_3.2/build/linux
./make-Makefiles.bash
#选择时候将选择ENABLE_HDR10_PLUS和HIGH_BIT_DEPTH通过回车设置ON,然后q键退出
vi /home/village/ffmpeg/x265_3.2/build/linux/x265.pc
#找到Libs.private: -lstdc++ -lm -lrt -ldl 末尾添加-lpthread
make && make install
pkg-config --list-all   # 查看是否有x265 x264
#没有vim /etc/profile
export PKG_CONFIG_PATH=/usr/local/lib/pkgconfig:$PKG_CONFIG_PATH
export PKG_CONFIG_PATH=/usr/local/x264/lib/pkgconfig:$PKG_CONFIG_PATH
export PKG_CONFIG_PATH=/usr/local/lib:$PKG_CONFIG_PATH
source /etc/profile
pkg-config --list-all # 查看是否有x265 x264
#https://blog.csdn.net/angl129/article/details/122339796
# 支持10bit
cd /home/village/ffmpeg/x265_3.2/source
vim CMakeLists.txt
#/HIGH_BIT_DEPTH 修改option(HIGH_BIT_DEPTH "Store pixel samples as 16bit values (Main10/Main12)" OFF)  OFF改为ON
cd /home/village/ffmpeg/x265_3.2/build/linux
cmake -G "Unix Makefiles" -DCMAKE_INSTALL_PREFIX=/usr/local -DENABLE_SHARED=OFF ../../source
#安装
make
make install
#ffmpeg
cd /home/village/ffmpeg
tar -xzvf ffmpeg-4.1.tar.gz
cd ffmpeg-4.1
./configure --prefix=/usr/local/ffmpeg --enable-shared --enable-yasm --enable-libx264 --enable-libx265 --enable-gpl --enable-pthreads --extra-cflags=-I/usr/local/x264/include --extra-ldflags=-L/usr/local/x264/lib --disable-x86asm --pkg-config="pkg-config --static"
# vim /etc/ld.so.conf
#在文件末尾加上
/usr/local/ffmpeg/lib
/usr/local/lib
/usr/local/x264/lib
#让配置生效sudo ldconfig
make && make install
cp /usr/local/ffmpeg/bin/* /usr/bin/
vi /etc/profile
#末尾加入
export PATH=/usr/local/ffmpeg/bin:$PATH
export LD_LIBRARY_PATH=/usr/local/ffmpeg/lib:$LD_LIBRARY_PATH
#生效
source /etc/profile
ffmpeg -version

#处理大华rtsp到m3u8测试

#linux测试
ffmpeg -rtsp_transport tcp -i "rtsp://admin:admin123@IP:554/cam/realmonitor?channel=1&subtype=0" -strict -2 -c:v libx264 -vsync 2 -c:a aac -f hls -hls_time 4 -hls_list_size 3 -hls_wrap 10 -y /usr/local/nginx/html/hls/channel1.m3u8
#多级目录测试,目录结构/hls/用户名/自定的视频表ID/自定的视频表ID.m3u8
#简单处理就是hls_cleanup开启,定义好m3u8的文件名,通过文件名解析去关掉相应的ffmpeg
ffmpeg -rtsp_transport tcp -i "rtsp://admin:admin123@IP:554/cam/realmonitor?channel=1&subtype=0" -strict -2 -c:v libx264 -vsync 2 -c:a aac -f hls -hls_time 4 -hls_list_size 3 -hls_wrap 10 -y /usr/local/nginx/html/hls/ea6f791673c640998e31dd29082621f1/3E4DC4ECEA2847ED8E5D88BB2BA3BFCC/3E4DC4ECEA2847ED8E5D88BB2BA3BFCC.m3u8
#window下测试
ffmpeg -rtsp_transport tcp -i "rtsp://admin:admin123@IP:554/cam/realmonitor?channel=1&subtype=0" -strict -2 -c:v libx264 -vsync 2 -c:a aac -f hls -hls_time 4 -hls_list_size 3 -hls_wrap 10 -y D:\tools\nginx\vedioCache\hls\ea6f791673c640998e31dd29082621f1\3E4DC4ECEA2847ED8E5D88BB2BA3BFCC\3E4DC4ECEA2847ED8E5D88BB2BA3BFCC.m3u8
#http m3u8格式访问地址
http://localhost:8099/hls/ea6f791673c640998e31dd29082621f1/3E4DC4ECEA2847ED8E5D88BB2BA3BFCC/3E4DC4ECEA2847ED8E5D88BB2BA3BFCC.m3u8
http://172.16.121.75:8099/hls/ea6f791673c640998e31dd29082621f1/3E4DC4ECEA2847ED8E5D88BB2BA3BFCC/3E4DC4ECEA2847ED8E5D88BB2BA3BFCC.m3u8

#vlc播发器串流播放测试

#本地window测试环境就需要在本地安装ffmpeg和nginx

#window 下测试用,已编译好的安装了rtmp的nginx共享地址

链接:https://pan.baidu.com/s/1F9QKRXpubngbd7X1xypkwA

提取码:0824

#java通过接口调用方式调用ffmpeg参考以下博文进行改造,按海康和大华rtsp的格式设计和维护一张表,也可以通过集成官方的SDK去获取相应的设备列表信息

https://blog.csdn.net/weixin_43288858/article/details/128253490?spm=1001.2014.3001.5502

https://www.freesion.com/article/5775913700/

四、代码改造

ffmpeg java调用上面涉及的一些代码下载地址

http://github.com/eguid/FFCH4J

#代码和配置实现接口调用预览和关闭预览

#POM引入

 <!--javacv基础包,包含javacv和javacpp,必须--><dependency><groupId>org.bytedeco</groupId><artifactId>javacv</artifactId><version>1.5.4</version></dependency><!-- ffmpeg依赖 --><dependency><groupId>org.bytedeco</groupId><artifactId>ffmpeg-platform</artifactId><version>4.3.1-1.5.4</version></dependency><!-- redis依赖 --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId></dependency>

#yml配置加入

#ffmpeg相关自定义的设置
ffmpeg:#macOs系统转流文件缓存地址macOsTempPath: /Users/jiangsha/Documents/upload#linux系统转流文件缓存地址,rtmp如何配置需要一致linuxTempPath: /usr/local/nginx/html#本地系统转流文件缓存地址,rtmp直播缓存地址windowTempPath: D:\tools\nginx\vedioCache# 映射出来的流媒体所在端口livePort: 8099# 映射出来的流媒体所在服务,nginx配置的/hls用于转发流的liveApp: hls#httpHeader http或者httpsurlHead: http#流媒体所在服务器liveServiceUrl: localhost#一次在线观看的有效时间,系统资源有限,无法一直推流消耗内存,默认给定30分钟expireMinutes: 30#延迟关闭最大时间 20 关闭后20秒内重新打开不重复调用expireMinutesDelay: 20#配置文件中找到 notify-keyspace-events ""  将其改成   notify-keyspace-events "Ex" 用于回调
spring: redis:host: redis的IPpassword: 密码database: 4port: 6379

#redis配置类和回调类

@Configuration
public class RedisConfig {@Bean//标红别管他RedisMessageListenerContainer container(RedisConnectionFactory connectionFactory) {RedisMessageListenerContainer container = new RedisMessageListenerContainer();container.setConnectionFactory(connectionFactory);return container;}
}@Component
@Slf4j
public class RedisKeyExpirationListener extends KeyExpirationEventMessageListener {public RedisKeyExpirationListener(RedisMessageListenerContainer listenerContainer) {super(listenerContainer);}@AutowiredCameraSecretInfoService cameraSecretInfoService;/*** key过期触发的事件*/@SneakyThrows@Overridepublic void onMessage(Message message, byte[] pattern) {String channel = new String(message.getChannel(), StandardCharsets.UTF_8);String key = new String(message.getBody(), StandardCharsets.UTF_8);boolean contains = key.contains(Constants.FFMPEG_USER_KEY);if (contains) {log.info("redis key 过期:pattern={},channel={},key={}", new String(pattern), channel, key);// key 形式 bladeFile:userId:pkIdString[] params = key.split(":");cameraSecretInfoService.removeExpiredVideo(params[1], params[2]);}}
}

#ffmpeg自定义配置类

@Data
@Configuration
@ConfigurationProperties(value = "ffmpeg")
public class FfmpegConfig {/*** 本地测试流文件路径缓存地址*/private String macOsTempPath;/*** window流文件路径存地址,配置在yml文件里面*/private String windowTempPath;/*** linux流文件路径存地址,配置在yml文件里面*/private String linuxTempPath;/*** 映射出来的流媒体所在端口*/private String livePort;/*** 映射出来的流媒体所在服务,nginx配置的勇于转发流的*/private String liveApp;/*** httpHeader*/private String urlHead;/*** 流媒体服务地址,设置为流媒体所在地址,也就是服务所地址*/private String liveServiceUrl;/*** 一次观看系统监控的时间,过期后会自动释放,单位分钟*/private int expireMinutes;/***  延迟关闭的10秒*/private int expireMinutesDelay;/*** 默认命令行执行根路径*/private String path;/*** 是否开启debug模式*/private boolean debug;/*** 任务池大小*/private Integer size;/*** 回调通知地址*/private String callback;/*** 是否开启保活*/private boolean keepalive;/*** Description 获取返回3um8的地址 如http://221.178.132.15:8099/live/userId/vedioId/vedioId.m3u8** @param userId*            userId* @param vedioId*            vedioId* @return java.lang.String* @author* @date 19:33 2023/1/7**/public String getRealLiveUrl(String userId, String vedioId) {StringBuilder stringBuilder = new StringBuilder();stringBuilder.append(this.urlHead);stringBuilder.append("://");stringBuilder.append(this.liveServiceUrl);stringBuilder.append(":");stringBuilder.append(this.livePort + "/");stringBuilder.append(this.liveApp + "/");stringBuilder.append(userId + "/");stringBuilder.append(vedioId + "/");stringBuilder.append(vedioId);stringBuilder.append(".m3u8");return stringBuilder.toString();}public String getRealLiveUrl(String vedioId) {StringBuilder stringBuilder = new StringBuilder();stringBuilder.append(this.urlHead);stringBuilder.append("://");stringBuilder.append(this.liveServiceUrl);stringBuilder.append(":");stringBuilder.append(this.livePort + "/");stringBuilder.append(this.liveApp + "/");stringBuilder.append(vedioId + "/");stringBuilder.append(vedioId);stringBuilder.append(".m3u8");return stringBuilder.toString();}/*** Description ffmpeg服务存储m3u8的地址* * @param userId*            userId* @param vedioId*            vedioId* @return java.lang.String* @author* @date 20:03 2023/1/7**/public String getSaveTempFileName(String userId, String vedioId) {return FilePathUtil.getFfmpegTmpPath() + File.separator + this.getLiveApp() + File.separator + userId + File.separator + vedioId + File.separator + vedioId + ".m3u8";}/*** Description ffmpeg服务存储m3u8的地址** @param vedioId*            vedioId* @return java.lang.String* @author* @date 20:03 2023/1/7**/public String getSaveTempFileName(String vedioId) {return FilePathUtil.getFfmpegTmpPath() + File.separator + this.getLiveApp() + File.separator + vedioId + File.separator + vedioId + ".m3u8";}/*** Description ffmpeg服务存储m3u8的地址** @param userId*            userId* @param vedioId*            vedioId* @return java.lang.String* @author* @date 20:03 2023/1/7**/public String getBaseSavePath(String userId, String vedioId) {return FilePathUtil.getFfmpegTmpPath() + File.separator + this.getLiveApp() + File.separator + userId + File.separator + vedioId + File.separator;}/*** Description ffmpeg服务存储m3u8的地址** @param vedioId*            vedioId* @return java.lang.String* @author* @date 20:03 2023/1/7**/public String getBaseSavePath(String vedioId) {return FilePathUtil.getFfmpegTmpPath() + File.separator + this.getLiveApp() + File.separator + vedioId + File.separator;}
}

#文件路径工具类兼容各系统

public class FilePathUtil {private FilePathUtil() {}private static final FfmpegConfig ffmpegConfig = SpringUtils.getBean(FfmpegConfig.class);public static String getFfmpegTmpPath() {String tempPath = "";if (SystemUtils.isMacOs()) {tempPath = ffmpegConfig.getMacOsTempPath();} else if (SystemUtils.isWindows()) {tempPath = ffmpegConfig.getWindowTempPath();FileUtil.makeDir(tempPath);} else {tempPath = ffmpegConfig.getLinuxTempPath();}return tempPath;}public static String getFilePath(String fileName) {return getFilePath() + File.separator + fileName;}}

#API接口和DTO

@Api(value = "大华监控视频预览API", tags = "大华监控视频预览API")
@RequestMapping("/village/live")
@Validated
public interface CameraSecretInfoApi {/*** Description 预览大华视频** @param villageMonitorDTO*            villageMonitorDTO* @return com.gsww.village.common.base.OpenResponse<java.lang.String>* @author* @date 15:55 2023/1/9**/@ApiOperation(value = "预览某个视频返回m3u8地址", notes = "预览某个视频返回m3u8地址")@PostMapping("/ffmpegOpen")@BusinessOperateLog(operateModule = "通过ffmpeg预览某个视频", operateType = OperateType.QUERY, operateDesc = "预览摄像头")OpenResponse<VillageMonitorDTO>ffmpegOpen(@Validated(ValidatedGroup.CreateGroup.class) @RequestBody VillageMonitorDTO villageMonitorDTO);/*** Description 关闭预览** @param villageMonitorParamDTO*            villageMonitorParamDTO* @return com.gsww.village.common.base.OpenResponse<java.lang.Boolean>* @author* @date 15:55 2023/1/9**/@ApiOperation(value = "关闭某个视频预览", notes = "关闭某个视频预览")@PostMapping("/ffmpegClose")@BusinessOperateLog(operateModule = "通过ffmpeg关闭某个视频预览", operateType = OperateType.QUERY, operateDesc = "关闭预览摄像头")OpenResponse<Boolean> ffmpegOff(@RequestBody VillageMonitorParamDTO villageMonitorParamDTO);}
#DTO类自己定义的表接各类摄像头数据进来方便前端拼接展示,CURD工程师最爱的
@Data
@ApiModel(description = "VillageMonitorDTO")
public class VillageMonitorDTO {/*** 网络摄像机地址*/@ApiModelProperty(value = "网络摄像机地址")@Trimmed@Length(max = 25, message = "网络摄像机地址不能超过25个字符",groups = {ValidatedGroup.CreateGroup.class, ValidatedGroup.UpdateGroup.class})@NotBlank(message = "网络摄像机地址不能为空")private String vedioAdress;/*** 端口*/@ApiModelProperty(value = "端口")@Trimmed@Length(max = 10, message = "端口不能超过10个字符",groups = {ValidatedGroup.CreateGroup.class, ValidatedGroup.UpdateGroup.class})@NotEmpty(message = "端口不能为空")private String vedioPort;/*** 摄像机类型, 预留后面去实现 1大华0海康*/@ApiModelProperty(value = "摄像机类型")private String vedioType;/*** 通道号*/@ApiModelProperty(value = "通道号")@Trimmed@Length(max = 2, message = "通道号不能超过2个字符",groups = {ValidatedGroup.CreateGroup.class, ValidatedGroup.UpdateGroup.class})@NotEmpty(message = "通道号不能为空")private String channelNo;/*** 通道名称或摄像头名称*/@ApiModelProperty(value = "通道名称或摄像头名称")@Trimmed@Length(max = 20, message = "通道名称或摄像头名称不能超过20个字符",groups = {ValidatedGroup.CreateGroup.class, ValidatedGroup.UpdateGroup.class})@NotEmpty(message = "通道名称或摄像头名称不能为空")private String channelName;/*** 码流类型 大华0 主流 1辅流 海康1主流0辅流*/@ApiModelProperty(value = "码流类型")@Trimmed@Length(max = 10, message = "码流类型不能超过10个字符",groups = {ValidatedGroup.CreateGroup.class, ValidatedGroup.UpdateGroup.class})@NotEmpty(message = "码流类型不能为空")private String subType;/*** 账户名称*/@ApiModelProperty(value = "账户名称")@Trimmed@Length(max = 20, message = "账户名称不能超过20个字符",groups = {ValidatedGroup.CreateGroup.class, ValidatedGroup.UpdateGroup.class})@NotEmpty(message = "账户名称不能为空")private String accountName;/*** 账户密码*/@ApiModelProperty(value = "账户密码")@Trimmed@Length(max = 100, message = "账户密码不能超过100个字符",groups = {ValidatedGroup.CreateGroup.class, ValidatedGroup.UpdateGroup.class})@NotEmpty(message = "账户密码不能为空")private String accountPass;/*** 数据所属区划代码*/@ApiModelProperty(value = "数据所属区划代码")@Trimmed@Length(max = 20, message = "数据所属区划代码不能超过20个字符",groups = {ValidatedGroup.CreateGroup.class, ValidatedGroup.UpdateGroup.class})private String areaCode;/*** 数据所属区划名称*/@ApiModelProperty(value = "数据所属区划名称")@Trimmed@Length(max = 20, message = "数据所属区划名称不能超过20个字符",groups = {ValidatedGroup.CreateGroup.class, ValidatedGroup.UpdateGroup.class})private String areaName;/*** 经度*/@ApiModelProperty(value = "经度")@Trimmedprivate Double longitude;/*** 纬度*/@ApiModelProperty(value = "纬度")@Trimmedprivate Double latitude;/*** 备注*/@ApiModelProperty(value = "备注")@Trimmed@Length(max = 20, message = "备注不能超过20个字符",groups = {ValidatedGroup.CreateGroup.class, ValidatedGroup.UpdateGroup.class})private String note;/*** m3u8Url 返回的地址*/private String m3u8Url;@ApiModelProperty(value = "主键")private String pkId;/*** 创建人id*/@ApiModelProperty(value = "创建人id")private String createUserId;/*** 标识位*/private String flagEemp;/*** 创建人*/@ApiModelProperty(value = "创建人")private String createUserName;/*** 更新人id*/@ApiModelProperty(value = "更新人id")private String updateUserId;/*** 更新人*/@ApiModelProperty(value = "更新人")private String updateUserName;/*** 创建时间*/@ApiModelProperty(value = "创建时间")@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")private Date createTime;/*** 更新时间*/@ApiModelProperty(value = "更新时间")@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")private Date updateTime;/*** 1、新增,2、修改,3、删除*/@ApiModelProperty(value = "操作标志")private String statusEemp;/*** 逻辑删除标志位*/@ApiModelProperty(value = "逻辑删除0未删除1已删除")private String deleted;}

#接视频的存储在自己系统的业务表设计

DROP TABLE IF EXISTS `t_village_monitor`;
CREATE TABLE `t_village_monitor`  (`PK_ID` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '主键',`VEDIO_ADRESS` varchar(25) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '网络摄像机IP地址',`VEDIO_PORT` varchar(10) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT 'RTSP端口',`CHANNEL_NO` varchar(2) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '通道号',`CHANNEL_NAME` varchar(20) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '通道名称',`VEDIO_TYPE` varchar(10) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '设备类型',`SUB_TYPE` varchar(10) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '码流类型',`ACCOUNT_NAME` varchar(20) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '账户名称',`ACCOUNT_PASS` varchar(100) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '账户密码',`AREA_CODE` varchar(20) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '数据所属区划代码',`AREA_NAME` varchar(20) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '数据所属区划名称',`LONGITUDE` decimal(10, 6) NULL DEFAULT NULL COMMENT '经度',`LATITUDE` decimal(10, 6) NULL DEFAULT NULL COMMENT '纬度',`NOTE` varchar(20) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '备注',`FLAG_EEMP` varchar(1) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '标识位',`CREATE_USER_ID` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '创建用户ID',`CREATE_USER_NAME` varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '创建用户名称',`UPDATE_USER_ID` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '修改用户ID',`UPDATE_USER_NAME` varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '修改用户姓名',`STATUS_EEMP` varchar(1) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '逻辑标识位:(1:新增,2:修改,3:删除)',`CREATE_TIME` timestamp(0) NOT NULL DEFAULT CURRENT_TIMESTAMP(0) ON UPDATE CURRENT_TIMESTAMP(0) COMMENT '创建时间',`UPDATE_TIME` timestamp(0) NOT NULL DEFAULT CURRENT_TIMESTAMP(0) ON UPDATE CURRENT_TIMESTAMP(0) COMMENT '修改时间',`DELETED` char(1) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '删除标识(0:否,1:是)',PRIMARY KEY (`PK_ID`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '视频监控表' ROW_FORMAT = Dynamic;-- ----------------------------
-- Records of t_village_monitor
-- ----------------------------
INSERT INTO `t_village_monitor` VALUES ('3E4DC4ECEA2847ED8E5D88BB2BA3BFCC', '61.171.182.292', '554', '8', '高清球机17', '1', '0', 'admin', 'qUReS8rEJLdHkqqRRg8uOg==', '340323202000', '仲兴镇', 117.195874, 33.204568, NULL, NULL, 'ea6f791673c640998e31dd29082621f1', 'ljp', 'ea6f791673c640998e31dd29082621f1', 'ljp', '2', '2023-01-10 10:50:50', '2023-01-10 11:38:59', '0');
INSERT INTO `t_village_monitor` VALUES ('3E4DC4ECEA2847ED8E5D88BB2BA3BFC2', '62.172.182.291', '554', '6', '高清球机15', '1', '0', 'admin', 'qUReS8rEJLdHkqqRRg8uOg==', '340323103000', '连城镇', 117.368578, 33.292745, NULL, NULL, 'ea6f791673c640998e31dd29082621f1', 'ljp', 'ea6f791673c640998e31dd29082621f1', 'ljp', '2', '2023-01-10 10:50:32', '2023-01-10 11:38:41', '0');

#Controller

@RestController
public class CameraSecretInfoController implements CameraSecretInfoApi {@AutowiredCameraSecretInfoService cmeraSecretInfoService;@Overridepublic OpenResponse<VillageMonitorDTO> ffmpegOpen(VillageMonitorDTO villageMonitorDTO) {if (StrUtil.isEmpty(villageMonitorDTO.getPkId())) {throw new BusinessException("摄像ID不能为空");}villageMonitorDTO.setAccountPass(AesEncryptUtil.desEncrypt(villageMonitorDTO.getAccountPass()));VillageMonitorDTO returnDto = cmeraSecretInfoService.ffmpegOpen(villageMonitorDTO);// 休息3秒钟先加载一下视频,体验好些就不加if (!returnDto.getReplayFlag()) {try {Thread.sleep(3000);} catch (InterruptedException e) {e.printStackTrace();}}return OpenResponse.success(returnDto);}@Overridepublic OpenResponse<Boolean> ffmpegOff(VillageMonitorParamDTO villageMonitorParamDTO) {if (StrUtil.isEmpty(villageMonitorParamDTO.getPkId())) {throw new BusinessException("摄像ID不能为空");}cmeraSecretInfoService.ffmpegOff(villageMonitorParamDTO.getPkId());return OpenResponse.success();}@Overridepublic OpenResponse<List<VillageMonitorDTO>> ffmpegOpenList(VillageMonitorListParamDTO villageMonitorListParamDTO) {List<VillageMonitorDTO> list = new ArrayList<>();List<Boolean> booleanList = new ArrayList<>();int size = villageMonitorListParamDTO.getListVideos().size();villageMonitorListParamDTO.getListVideos().stream().forEach(dto -> {dto.setAccountPass(AesEncryptUtil.desEncrypt(dto.getAccountPass()));VillageMonitorDTO returnDto = cmeraSecretInfoService.ffmpegOpen(dto);returnDto.setAccountPass("");list.add(returnDto);if (returnDto.getReplayFlag()) {booleanList.add(returnDto.getReplayFlag());}});// 休息3秒钟先加载一下视频,体验好些就不加if (!(booleanList.size() == size)) {try {Thread.sleep(3000);} catch (InterruptedException e) {e.printStackTrace();}}return OpenResponse.success(list);}@Overridepublic OpenResponse<Boolean> ffmpegOffList(IdsDTO<String> ids) {ids.getIds().stream().forEach(id -> cmeraSecretInfoService.ffmpegOff(id));return OpenResponse.success();}
}

#service接口和实现类

public interface CameraSecretInfoService {/*** Description 返回M3u8地址** @param villageMonitorDTO*            villageMonitorParamDTO* @return java.lang.String* @author* @date 16:04 2023/1/7**/VillageMonitorDTO ffmpegOpen(VillageMonitorDTO villageMonitorDTO);/*** Description 关闭预览** @param pkId*            pkId* @author* @date 16:04 2023/1/7**/void ffmpegOff(String pkId);/*** Description 关闭预览** @param pkId*            pkId* @author* @date 16:04 2023/1/7**/void stopBackVideoDelay(String pkId);/*** Description 停止并删除过期的预览** @param userId*            token1* @return void* @author* @date 15:56 2023/1/7**/void removeExpiredVideo(String userId, String vedioId) throws IOException;/*** Description 删除文件夹下面所有文件** @param basePath*            basePath* @return void* @author* @date 15:57 2023/1/7**/void deleteDir(String basePath) throws IOException;void stopBackVideo(String vedioId) throws IOException;
}#// 过期删除关闭视频流的KEY用于redis的回调public static final String FFMPEG_USER_KEY = "bladeFile:";
#视频预览实现
@Service
@Slf4j
public class CameraSecretInfoServiceImpl implements CameraSecretInfoService {@Resourceprivate StringRedisTemplate stringRedisTemplate;@AutowiredFfmpegConfig ffmpegConfig;@AutowiredCommandManager manager;/*** Description 开启预览,启动ffmpeg的key规则为用户ID+视频监控表ID,用户失效的时候按用户全部删除** @param villageMonitorDTO* @return java.lang.String* @author* @date 17:44 2023/1/7**/@Overridepublic VillageMonitorDTO ffmpegOpen(VillageMonitorDTO villageMonitorDTO) {// 当前用户IDString userId = AbsExtDomainUtil.getUserInfo().getUserId();// 存放的m3u8文件夹地址 root/appUrl/userId /pkId/pkId.m3u8FileUtil.makeDir(ffmpegConfig.getBaseSavePath(userId, villageMonitorDTO.getPkId()));String codeId = userId + ":" + villageMonitorDTO.getPkId();if (ObjectUtil.isEmpty(manager.query(codeId))&& Boolean.FALSE.equals(stringRedisTemplate.hasKey(Constants.FFMPEG_USER_KEY + codeId))) {manager.stop(codeId); // 先停止视频manager.start(codeId,CommandBuidlerFactory.createBuidler().add("ffmpeg").add("-rtsp_transport", "tcp").add("-i", getRtspUrl(villageMonitorDTO)) // 取videoUrl.add("-strict", "-2")//转成h264国标.add("-vcodec", "libx264").add("-vsync", "2").add("-preset:v").add("ultrafast").add("-tune:v").add("zerolatency")// 不要声音//.add("-an")// 音频.add("-c:a", "aac").add("-hls_time", "3").add("-hls_list_size", "2").add("-hls_wrap", "3").add("-y")//如果使用http-flv把下面的地址换成类似rtmp://172.16.121.75:1935/live/test.add(ffmpegConfig.getSaveTempFileName(userId, villageMonitorDTO.getPkId())));CommandTasker info = manager.query(codeId);villageMonitorDTO.setReplayFlag(false);log.info("启动ffmpeg:" + info.toString());}  else {villageMonitorDTO.setReplayFlag(true);}// 设置ffmpeg视频失效时间,避免长时间占用资源,造成内存溢出stringRedisTemplate.opsForValue().set(Constants.FFMPEG_USER_KEY + codeId, codeId,ffmpegConfig.getExpireMinutes(), TimeUnit.MINUTES);// 返回路径根据ffmpeg存放视频路径+nginx代理灵活配置//如果使用http-flv的话实际返回地址换成类似http://172.16.121.75:8099/live?port=1935&app=live&stream=test     villageMonitorDTO.setM3u8Url(ffmpegConfig.getRealLiveUrl(userId, villageMonitorDTO.getPkId()));return villageMonitorDTO;}@Overridepublic void ffmpegOff(String pkId) {stopBackVideoDelay(pkId);}@Overridepublic void removeExpiredVideo(String userId, String vedioId) throws IOException {String basePathAll = ffmpegConfig.getBaseSavePath(userId, vedioId);File fileExist = new File(basePathAll);// 文件夹文件夹存在,则停止后删除if (fileExist.exists()) {stopBackVideo(userId, vedioId);if (fileExist.exists()) {deleteDir(basePathAll);}}}@Overridepublic void deleteDir(String basePath) throws IOException {Path path = Paths.get(basePath);Files.walkFileTree(path, new SimpleFileVisitor<Path>() {// 先去遍历删除文件@Overridepublic FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {Files.delete(file);log.warn("文件被删除 : %s%n", file);return FileVisitResult.CONTINUE;}// 再去遍历删除目录@Overridepublic FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException {Files.delete(dir);log.warn("文件夹被删除: %s%n", dir);return FileVisitResult.CONTINUE;}});}@Overridepublic void stopBackVideo(String vedioId) throws IOException {String userId = AbsExtDomainUtil.getUserInfo().getUserId();String basePath = ffmpegConfig.getBaseSavePath(userId, vedioId);File fileExist = new File(basePath);String codeId = userId + ":" + vedioId;// 停止ffmpeg转码manager.stop(codeId);manager.start(codeId, CommandBuidlerFactory.createBuidler().add("rm -rf", basePath));// 对文件夹进行删除操作if (fileExist.exists()) {deleteDir(basePath);}// 清除相应的redisstringRedisTemplate.delete(Constants.FFMPEG_USER_KEY + codeId);}@Overridepublic void stopBackVideoDelay(String vedioId) {String userId = AbsExtDomainUtil.getUserInfo().getUserId();String codeId = userId + ":" + vedioId;// 延迟10秒关闭,免得重复拉log.info(codeId + " 将在" + ffmpegConfig.getExpireMinutesDelay() + "秒后关闭流");stringRedisTemplate.opsForValue().set(Constants.FFMPEG_USER_KEY + codeId, codeId,ffmpegConfig.getExpireMinutesDelay(), TimeUnit.SECONDS);}public void stopBackVideo(String userId, String vedioId) throws IOException {// 解析jwt的token值,拿到最后面一截,这个也是不会重复String basePath = FilePathUtil.getFfmpegTmpPath() + File.separator + ffmpegConfig.getLiveApp() + File.separator+ userId + File.separator + vedioId + File.separator;File fileExist = new File(basePath);String codeId = userId + ":" + vedioId;// 停止ffmpeg转码manager.stop(codeId);manager.start(codeId, CommandBuidlerFactory.createBuidler().add("rm -rf", basePath));// 对文件夹进行删除操作if (fileExist.exists()) {deleteDir(basePath);}// 清除相应的redisstringRedisTemplate.delete(Constants.FFMPEG_USER_KEY + codeId);}/*** 得到文件名称** @param file*            文件* @param fileNames*            文件名* @return {@link List}<{@link String}>*/private List<String> getFileNames(File file, List<String> fileNames) {File[] files = file.listFiles();for (File f : files) {if (f.isDirectory()) {fileNames.add(f.getName());}}return fileNames;}/*** Description 获取到设备的rtsp流地址** @param villageMonitorDTO*            villageMonitorDTO* @return java.lang.String* @author* @date 13:45 2023/1/10**/private String getRtspUrl(VillageMonitorDTO villageMonitorDTO) {StringBuilder stringBuild = new StringBuilder("rtsp://");stringBuild.append(villageMonitorDTO.getAccountName() + ":");stringBuild.append(villageMonitorDTO.getAccountPass() + "@");stringBuild.append(villageMonitorDTO.getVedioAdress() + ":");stringBuild.append(villageMonitorDTO.getVedioPort());// 默认大华if (StrUtil.isEmpty(villageMonitorDTO.getVedioType())|| Constants.ONE.equals(villageMonitorDTO.getVedioType())) {stringBuild.append("/cam/realmonitor?");stringBuild.append("channel=" + villageMonitorDTO.getChannelNo());stringBuild.append("&subtype=" + villageMonitorDTO.getSubType());} else {stringBuild.append("/Streaming/Channels/");stringBuild.append(villageMonitorDTO.getChannelNo() + villageMonitorDTO.getSubType());}// stringBuild.append("\\\"");return stringBuild.toString();}
}

#开源保活处理下避免内存溢出造成应用程序奔溃

public class KeepAliveHandler extends Thread{/**待处理队列*/private static Queue<String> queue=null;public int err_index=0;//错误计数 5次调用后自动停止public volatile int stop_index=0;//安全停止线程标记/** 任务持久化器*/private TaskDao taskDao = null;public KeepAliveHandler(TaskDao taskDao) {super();this.taskDao=taskDao;queue=new ConcurrentLinkedQueue<>();}public static void add(String id ) {if(queue!=null) {queue.offer(id);}}public boolean stop(Process process) {if (process != null) {process.destroy();return true;}return false;}@Overridepublic void run() {for(;stop_index==0;) {if(queue==null) {continue;}String id=null;CommandTasker task=null;try {while(queue.peek() != null) {System.err.println("准备重启任务:"+queue);id=queue.poll();task=taskDao.get(id);//重启任务ExecUtil.restart(task);}}catch(Exception e) {//重启任务失败,5次后自动停止避免内存溢出err_index++;System.err.println(id+" 任务重启失败" + err_index + "次,详情:"+task);if (err_index >=5) {stop_index=1;System.err.println(id+" 任务重启失败,保活关闭");}}}}@Overridepublic void interrupt() {stop_index=1;}}#开源代码配置工具类linux路径问题修改
PropertiesUtil类修改
/*** 获取对应文件路径下的文件流 兼容linux打包读取路径* * @param path* @return* @throws FileNotFoundException*/public static InputStream getInputStream(String path) throws IOException {return new ClassPathResource(path).getInputStream();}

接口测试

#前端测试html ,src修改为输出的m3u8地址

#前端部分代码

链接:https://pan.baidu.com/s/1Ev-1cuA5WRSC_vbCSKA-Wg

提取码:0824

<!DOCTYPE html>
<html lang="zh-CN"><head><meta charset="UTF-8"><title>前端播放m3u8格式视频</title><link href="https://vjs.zencdn.net/7.4.1/video-js.css" rel="stylesheet"><script src='https://vjs.zencdn.net/7.4.1/video.js'></script><script src="https://cdnjs.cloudflare.com/ajax/libs/videojs-contrib-hls/5.15.0/videojs-contrib-hls.min.js"type="text/javascript"></script><!-- videojs-contrib-hls 用于在电脑端播放 如果只需手机播放可以不引入 -->
</head><body><style>.video-js .vjs-tech {position: relative !important;}</style><div><video id="myVideo" class="video-js vjs-default-skin vjs-big-play-centered" controls preload="auto"data-setup='{}' style='width: 100%;height: auto'><source id="source"src="http://IP:8099/hls/channel1.m3u8"type="application/x-mpegURL"></source></video></div><div class="qiehuan"style="width:100px;height: 100px;background: red;margin:0 auto;line-height: 100px;color:#fff;text-align: center">切换视频</div>
</body><script>// videojs 简单使用var myVideo = videojs('myVideo', {bigPlayButton: true,textTrackDisplay: false,posterImage: false,errorDisplay: false,})myVideo.play()var changeVideo = function (vdoSrc) {if (/\.m3u8$/.test(vdoSrc)) { //判断视频源是否是m3u8的格式myVideo.src({src: vdoSrc,type: 'application/x-mpegURL' //在重新添加视频源的时候需要给新的type的值})} else {myVideo.src(vdoSrc)}myVideo.load();myVideo.play();}var src = 'http://IP:8099/hls/channel1.m3u8';document.querySelector('.qiehuan').addEventListener('click', function () {changeVideo(src);})
</script>

方案二

在方案一的代码中把ffmpeg组装的地址换成

ffmpeg -re -rtsp_transport tcp -i "rtsp://admin:admin123@IP:554/cam/realmonitor?channel=3&subtype=0" -f flv -vcodec h264 -vprofile baseline -acodec aac -ar 44100 -strict -2 -ac 1 -f flv -s 640*360 -q 10 "rtmp://172.16.121.75(nginxIP):1935(rtmp端口)/live(服务名称)/test"

通过vlc打开

http://172.16.121.75:8099/live?port=1939&app=live&stream=test

前端可以通过flv.js访问,vue的代码自行改造,以下代码保存为html直接打开

<!doctype html>
<html lang="en">
<head><meta charset="UTF-8"><meta name="viewport"content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0"><meta http-equiv="X-UA-Compatible" content="ie=edge"><title>播放http-flv</title>
</head>
<body>
<video id="videoElement"></video>
<script src="https://cdn.bootcdn.net/ajax/libs/flv.js/1.6.2/flv.min.js"></script>
<script>if (flvjs.isSupported()) {const videoElement = document.getElementById('videoElement');const flvPlayer = flvjs.createPlayer({type: 'flv',url: 'http://172.16.121.75:8099/live?port=1935&app=live&stream=test'});flvPlayer.attachMediaElement(videoElement);flvPlayer.load();flvPlayer.play();}
</script>
</body>
</html>

效果如下

参考博文https://blog.csdn.net/Candyz7/article/details/126741970

方案三:

通过ZLM免费开源国标平台搭建接入的示例,看看拉流方式的实现,适合并发较高情况,不过需要搭建个流媒体服务,有支持http-flv可以开箱就用:

参考https://notemi.cn/wvp---zlmedia-kit---mediaserverui-to-realize-streaming-playback-and-recording-of-camera-gb28181.html

前端如果想分离出来简单调整下代码实现前后端分离

如果想支持拉流拉成m3u8格式可以简单调整下如下,没有深入改会有很多隐患

代码调整下

配置文件更改

前端分离需要更改的地方如下

index.js更改替换下build

build: {// Template for index.htmlindex: path.resolve(__dirname, '../dist/index.html'),// PathsassetsRoot: path.resolve(__dirname, '../dist'),assetsSubDirectory: 'static',assetsPublicPath: './',proxyTable: {'/zlm': {target:'http://172.16.121.75:18080',            //请求的目标地址的BaseURLws:true,changeOrigin:true,                            //是否开启跨域pathRewrite:{'^/zlm':''                                    //重新路径,把EzaYun开头的,替换成 ''}},'/static/snap': {target: 'http://127.16.121.75:18080',changeOrigin: true,// pathRewrite: {//   '^/static/snap': '/static/snap'// }}},

这样前端打包完成后就和一般vue打包一样生成个dist,这样通过nginx就可以简单部署前端代码

配置调整下

nginx加上流媒体服务访问m3u8

#流媒体前端代理
location /zlm-server/ {alias /home/village/wvp/web/dist/;index  index.html index.htm;}#流媒体接口代理location /zlm/api/ {proxy_pass  http://172.16.121.75:18080/api/;}location /zlm/api/user/login {proxy_pass http://172.16.121.75:18080/api/user/login;}#前端获取流媒体 location /zlmhls {# Serve HLS fragmentstypes {application/vnd.apple.mpegurl m3u8;video/mp2t ts;}#root html;alias /usr/local/nginx/html/hls;}

拉流占用情况

java接口方式调用海康大华摄像机预览。相关推荐

  1. 海康大华摄像机图像收集工具

    文章目录 简介 准备工作 设置需要连接的摄像机 设置需要采集的点位信息 收集图像 开始收集图像 查看图像收集信息 暂停/恢复摄像机收集 取消图像收集 完成图像收集 其他功能 实时预览 开始实时预览 结 ...

  2. 海康大华等安防摄像机采用通用RTSP协议流转RTMP推送至Web无插件播放展示的流程方法

    行业现状 中国互联网化的进程已经越来越快了,各个行业都在进行着互联网化的改造,流媒体.音视频,作为跑在互联网上最大量级的数据类型,其从编码方式到传输协议到终端兼容都成为各家标准抢占的高点,RTMP.H ...

  3. LiveGBS流媒体平台GB/T28181功能-摄像头报警告警预案触发图片截取视频录制海康大华华为宇视等摄像头报警触发截图录像

    LiveGBS摄像头报警告警预案触发图片截取视频录制海康大华华为宇视等摄像头报警触发截图录像 1.报警信息 1.1.报警查询 1.2.配置开启报警订阅 1.2.1.国标设备编辑 1.2.2.勾选订阅项 ...

  4. 华为海康大华摄像头编译RTSP转RTMP和HTTPFLV

    华为海康大华摄像头编译RTSP转RTMP和HTTPFLV 项目需求要看摄像头实时画面谷歌浏览器不支持RTSP流直接展示 方案一 通过Java+nginx+rtsp转rtmp流 方案二 通过Java+ ...

  5. 安防RTSP协议摄像头实现WEB端无插件直播流媒体服务EasyNVR实现海康大华宇视摄像头网页播放的方法

    背景分析:微信直播的兴起 进入移动互联网时代以来,企业微信公众号已成为除官网以外非常重要的宣传渠道,当3.2亿直播用户与9亿微信用户的势能累加,在微信上开启直播已成为越来越多企业的必然选择. Easy ...

  6. Qt编写视频监控管理平台(支持海康/大华/宇视/华为/天地伟业/H264/H265等)

    一.前言 海康大华等厂家自己的客户端软件,基本上都是支持自家的设备,不支持其他家的摄像机和硬盘录像机,并不是因为技术上做不到,这些大厂要实现支持兼容其他的家的(他们家的服务端或者收费的都是支持其他家的 ...

  7. LiveVISGAT1400视图库服务-支持海康大华华为宇视天地伟业等设备视图库接入使用说明

    LiveVISGAT1400视图库服务-支持海康大华华为宇视天地伟业等设备视图库接入使用说明 LiveVIS GAT1400视图库服务安装使用说明 1.服务说明 1.1.安装包说明 1.2.视图库服务 ...

  8. 基于activex插件的海康大华网页版的播放(一)

    研一下学期开学一个月内,完成了海康大华网页版的播放器,其实就是在web端调用基于activex控件做的MFC插件.先看一下效果图: 项目需求:做一个网页版的能够播放海康.大华等通用设备的实时流的播放器 ...

  9. 海康大华天地伟业网络摄像头chrome浏览器web二次开发

    海康大华天地伟业网络摄像头chrome浏览器二次开发 海康大华天地伟业网络摄像头chrome浏览器web二次开发 由于工作的原因需要开发海康和大华,还有天地伟业的摄像头,而且必须是本地部署开发,每个厂 ...

最新文章

  1. 中国高校人工智能学院院长 【截止到 2019-05-07】
  2. swift_038(Swift之guard关键字(守护))
  3. asp php时间格式,ASP_asp格式化日期时间格式的代码,' ====================================== - phpStudy...
  4. 动态链接库的创建和调用方法
  5. 字体小于12px解决办法
  6. 汉诺塔java程序_Java编写一个汉诺塔的过程
  7. matlab fopen wt,matlab的fopen和fprintf
  8. shell中判断一个参数是否为整型
  9. 鼠标偏移量_不止颜值!活动鼠标使用评测
  10. python插入排序
  11. mongovue mysql_MongoDB 客户端 MongoVue(转)
  12. 项目实战:十种方法实现图像数据集降维
  13. 二十年经典港台电视剧回顾
  14. 设计模式(一) 简单工厂模式
  15. 3-5 单链表分段逆转
  16. python匿名函数调用_(Python) 函数、匿名函数
  17. 失败者的人性弱点,来看看你中了几点
  18. linux maven 发布项目,Linux下基于Maven的自动化打包发布项目
  19. EXCEL vba工程密码破解
  20. kafka官方文档中文翻译(kafka参数解释)

热门文章

  1. linux cp命令参数及用法详解---linux 复制文件命令cp
  2. win10edge启用html5,Win 10 Edge浏览器极速运行的设置方法
  3. Windows系统自带的DOS窗口
  4. Orthogonal Convolutional Neural Networks
  5. Eigen介绍及简单使用
  6. 计算机对英语写作的帮助,2018年6月英语六级写作范文:计算机对写作能力的影响...
  7. anaconda使用jupyter
  8. 【杭电数电实验】verilog入门指北
  9. Docker部署ELK(配置密码登录)及Elastalert企业微信告警配置
  10. arduino实验日记