java 对接海康 抓图,报警布防,视频拉流 您所在的位置:网站首页 海康威视拉流 java 对接海康 抓图,报警布防,视频拉流

java 对接海康 抓图,报警布防,视频拉流

2024-03-21 23:15| 来源: 网络整理| 查看: 265

java 海康抓图,布防,拉流 介绍:

使用java对接海康的一些操作,包括对海康摄像头(以下均表示为设备)进行抓图,报警布防,视频拉流的一些操作,主要列出一些调用时的核心代码,以及遇到的一些问题。

需要用到的:

海康SDK:这个在官网(海康开放平台 (hikvision.com))下载即可,注意区分系统,本次使用到的是windowsSDK

FFMPEG:这个作为视频推流使用(本次直接引入了FFMPEG的maven,不需要再另外安装单独的软件),亦可使用其他推流软件如OBS(需要知道相应的操作命令)

SRS:这个是媒体服务器,将视频流转为其他格式,使用nginx也可以(参考nginx +rtmp+nginx-http-flv-module 环境搭建_phpstudy 安装nginx-http-flv-module-CSDN博客),但实际环境中你可能已经安装了nginx,但安装时并没有加入视频模块,就涉及到下载模块并重新编译nginx了,这里就不赘述了,SRS安装教程可参考(liunx下使用SRS搭建直播流媒体服务器_srs rtc直播间问题-CSDN博客)

VLC:视频播放器,也可以使用EasyPlayer 用来测试视频地址

这里只列出关键的一些依赖

org.bytedeco.javacpp-presets ffmpeg 4.1-1.4.4 ws.schild jave-all-deps 3.3.1 ws.schild jave-nativebin-win32 ws.schild jave-nativebin-linux32 ws.schild jave-nativebin-osx64 ws.schild jave-nativebin-osxm1 ws.schild jave-nativebin-linux-arm32 ws.schild jave-nativebin-linux-arm64 net.java.dev.jna jna 5.13.0 examples hc-examples 1.0 system ${project.basedir}/lib/examples.jar

这个HCNetSDK.java 和上述example.jar 必须引入,是操作设备的关键(就是SDK), 文件在下载的压缩包里面的开放示例里面会有,直接放到项目下

在这里插入图片描述

整个用到的库文件,官网下载的包里会有,这里HCNetSDK.dll 的路径需要拿到,在加载hcSDK时需要用到

在这里插入图片描述

抓图:

流程:sdk加载 -> 初始化 -> 登录注册 -> 获取设备工作状态 -> 抓图

其中SDK 加载和初始化只需要 执行一次,所以可以注册到spring中,后续需要使用时直接注入

@Bean public HCNetSDK hcNetSDK() { //sdkPath:上述HCNetSDK.dll的路径 String sdkPath = HcProperties.getSdkPath(); HCNetSDK hcNetSDK = Native.load(sdkPath, HCNetSDK.class); if (hcNetSDK.NET_DVR_Init()) { log.info("HCNetSDK 初始化成功"); } else { //hcNetSDK.NET_DVR_GetLastError()获取最后一次异常码,这些异常码在HCNetSDK.java中有相应解释,大概在200行,也可以在网上搜海康异常码 log.error("HcNetSDK 初始化失败,异常码:{},信息:{}", hcNetSDK.NET_DVR_GetLastError(), HCErrorEnum.getByCode(hcNetSDK.NET_DVR_GetLastError()).getMessage()); } //设置连接超时时间,连接尝试次数 hcNetSDK.NET_DVR_SetConnectTime(2000,1); //设置重连功能,重连间隔ms,是否重连, hcNetSDK.NET_DVR_SetReconnect(60000,true); //解释:1 if (HcProperties.isSetCallBack()) { //这里回调函数必须是全局唯一,否则会导致无法调用 HCNetSDK.FMSGCallBack_V31 fmsgCallBack = FMSGCallBack.CALL_BACK_V_31; boolean b = hcNetSDK.NET_DVR_SetDVRMessageCallBack_V31(fmsgCallBack, null); if (b) log.info("设置报警回调函数成功."); else log.error("设置报警回调函数失败:异常码:{},信息:{}", hcNetSDK.NET_DVR_GetLastError(), HCErrorEnum.getByCode(hcNetSDK.NET_DVR_GetLastError()).getMessage()); /* 设备上传的报警信息是COMM_VCA_ALARM(0x4993)类型, 在SDK初始化之后增加调用NET_DVR_SetSDKLocalCfg(enumType为NET_DVR_LOCAL_CFG_TYPE_GENERAL)设置通用参数NET_DVR_LOCAL_GENERAL_CFG的byAlarmJsonPictureSeparate为1, 将Json数据和图片数据分离上传,这样设置之后,报警布防回调函数里面接收到的报警信息类型为COMM_ISAPI_ALARM(0x6009), 报警信息结构体为NET_DVR_ALARM_ISAPI_INFO(与设备无关,SDK封装的数据结构),更便于解析。 */ HCNetSDK.NET_DVR_LOCAL_GENERAL_CFG struNET_DVR_LOCAL_GENERAL_CFG = new HCNetSDK.NET_DVR_LOCAL_GENERAL_CFG(); struNET_DVR_LOCAL_GENERAL_CFG.byAlarmJsonPictureSeparate = 1; //设置JSON透传报警数据和图片分离 struNET_DVR_LOCAL_GENERAL_CFG.write(); Pointer pStrNET_DVR_LOCAL_GENERAL_CFG = struNET_DVR_LOCAL_GENERAL_CFG.getPointer(); hcNetSDK.NET_DVR_SetSDKLocalCfg(17, pStrNET_DVR_LOCAL_GENERAL_CFG); } return hcNetSDK; }

解释1:上述设置回调函数只需设置一次,设置多次会覆盖掉,这个回调函数是在设备报警时的接口,所有设备报警都会进入到此接口,根据形参中第二个参数区分具体设备,这个回调函数必须全局唯一,否则有可能会被回收,收不到报警,实际测试中我直接new一个函数实现类是无法收到报警;

HCNetSDK.FMSGCallBack_V31 fmsgCallBack = FMSGCallBack.CALL_BACK_V_31; hcNetSDK.NET_DVR_SetDVRMessageCallBack_V31(fmsgCallBack, null);

也可以写成

hcNetSDK.NET_DVR_SetDVRMessageCallBack_V31(this::invoke, null) public boolean invoke(int lCommand, HCNetSDK.NET_DVR_ALARMER pAlarmer, Pointer pAlarmInfo, int dwBufLen, Pointer pUser) { //.... return true; }

回调函数

public class FMSGCallBack implements HCNetSDK.FMSGCallBack_V31 { public static final HCNetSDK.FMSGCallBack_V31 CALL_BACK_V_31 = new FMSGCallBack(); @Override public boolean invoke(int lCommand, HCNetSDK.NET_DVR_ALARMER pAlarmer, Pointer pAlarmInfo, int dwBufLen, Pointer pUser) { //AlarmDataParse 这个可以参考海康提供的开发示例来写,里面详细解释了参数的一些使用方式 //可以根据你需要的报警类型来写相关的业务,下面我只贴部分代码 AlarmDataParse.alarmDataHandler(lCommand, pAlarmer, pAlarmInfo, dwBufLen, pUser); return true; } }

处理相关报警,

lCommand :代表事件类型,可以查看HCNetSDK.java 中的对应事件判断报警类型,大概在886行左右;

pAlarmer: 表示设备信息,可以区分是哪个设备的报警;

pAlarmInfo: 与lCommand 关联,不同的报警会触发不同的行为,比如温度报警,会在pAlarmInfo里面存放热成像图片和可见光图片信息,这时就可以对这个pAlarmInfo进行读取,从而抓取图片,但是有些事件又不会触发抓图行为,所以需要根据事件类型来判断pAlarmInfo 的具体实现;

详见:NET_DVR_SetDVRMessageCallBack_V31 (hikvision.com)

@Slf4j public class AlarmDataParse { public static void alarmDataHandler(int lCommand, HCNetSDK.NET_DVR_ALARMER pAlarmer, Pointer pAlarmInfo, int dwBufLen, Pointer pUser) { log.error("侦测到事件类型: {},事件信息:{},deviceIp:{},serialNumber:{},deviceName:{};", Integer.toHexString(lCommand), HCEventEnum.getByCode(lCommand).getMessage(), new String(pAlarmer.sDeviceIP), new String(pAlarmer.sSerialNumber), new String(pAlarmer.sDeviceName, StandardCharsets.US_ASCII)); switch (lCommand) { case HCNetSDK.COMM_THERMOMETRY_ALARM: //温度报警信息 HCNetSDK.NET_DVR_THERMOMETRY_ALARM struTemInfo = new HCNetSDK.NET_DVR_THERMOMETRY_ALARM(); struTemInfo.write(); Pointer pTemInfo = struTemInfo.getPointer(); pTemInfo.write(0, pAlarmInfo.getByteArray(0, struTemInfo.size()), 0, struTemInfo.size()); struTemInfo.read(); String sThermAlarmInfo = "规则ID:" + struTemInfo.byRuleID + "预置点号:" + struTemInfo.wPresetNo + "报警等级:" + struTemInfo.byAlarmLevel + "报警类型:" + struTemInfo.byAlarmType + "当前温度:" + struTemInfo.fCurrTemperature; log.error(sThermAlarmInfo); break; case HCNetSDK.COMM_ALARM_RULE: //行为分析信息 break; case HCNetSDK.COMM_ALARM_V30://移动侦测、视频丢失、遮挡、IO信号量等报警信息(V3.0以上版本支持的设备) HCNetSDK.NET_DVR_ALARMINFO_V30 struAlarmInfo = new HCNetSDK.NET_DVR_ALARMINFO_V30(); struAlarmInfo.write(); Pointer pAlarmInfo_V30 = struAlarmInfo.getPointer(); pAlarmInfo_V30.write(0, pAlarmInfo.getByteArray(0, struAlarmInfo.size()), 0, struAlarmInfo.size()); struAlarmInfo.read(); switch (struAlarmInfo.dwAlarmType) { case 0: log.error("信号量报警"); break; case 1: log.error("硬盘满"); break; case 2: log.error("信号丢失"); break; case 3: log.error("移动侦测"); break; case 4: log.error("硬盘未格式化"); break; case 5: log.error("读写硬盘出错"); break; case 6: log.error("遮挡报警"); break; case 7: log.error("制式不匹配"); break; case 8: log.error("非法访问"); break; } break; default: log.info("其他告警,类型:{}", Integer.toHexString(lCommand)); break; } } }

目前为止,一切正常的话,sdk已经成功加载,初始化,(根据需求决定是否布置回调函数,这个后续布防时详细说明),并注册到Spring中。

现在执行后续操作,登录注册与抓图

设备信息结构

public interface Devices { //设备ip String getIp(); //设备端口,一般是8000 Short getPort(); //设备登录名 String getUsername(); //设备密码 String getPassword(); //设备通道,(用于区分可见光还是热成像) Integer getChannel(); }

hc通用操作抽象类

public abstract class AbstractHcNetHandler { //sdk private HCNetSDK hcNetSDK; //需要对接的设备 private final Devices devices; //登录后返回的id,用于注销设备,登出 protected int id = -1; //是否已获取到设备工作状态 private boolean workState = false; //监听句柄 private int lAlarmHandle = -1; public AbstractHcNetHandler(HCNetSDK sdk, Devices device) { this.hcNetSDK = sdk; this.devices = device; } /** * 注册,登录; */ public void registerV30() { if (!registered()) { NET_DVR_DEVICEINFO_V30 v30 = new NET_DVR_DEVICEINFO_V30(); this.id = this.hcNetSDK.NET_DVR_Login_V30( devices.getIp(), devices.getPort(), devices.getUsername(), devices.getPassword(), v30); } else { return; } if (!registered()) { error("设备注册失败", true); } else { log.info("设备注册成功"); } } /** * 设备工作状态; */ @Override public boolean workState() { if (workState) return true; NET_DVR_WORKSTATE_V30 devwork = new NET_DVR_WORKSTATE_V30(); this.workState = this.hcNetSDK.NET_DVR_GetDVRWorkState_V30(this.id, devwork); return workState; } /** * 撤防,登出,注销; */ @Override public void logout() { if (registered()) { //撤防 close(); log.info("登出设备:{}", this.devices); this.hcNetSDK.NET_DVR_Logout(this.id); //这个写在项目关闭前清除SDK。 //this.hcNetSDK.NET_DVR_Cleanup(); } } protected void error(String message, boolean throwE) { String s = message + ",异常码:" + this.hcNetSDK.NET_DVR_GetLastError() + ",异常信息:" + HCErrorEnum.getByCode(this.hcNetSDK.NET_DVR_GetLastError()).getMessage() + ",设备信息:" + this.devices.toString(); log.error(s); if (throwE) throw new RuntimeException(s); } @Override public T invoke() { try { //注册,登录 registerV30(); //获取设备工作状态 if (workState()) { return handler(this.hcNetSDK, this.devices); } else { error("获取设备状态异常", true); } } catch (Exception e) { error("执行任务异常", true); } // finally { // logout(); // } return null; } @Override public int getId() { return id; } @Override public String getObjectId() { return this.devices.getIp(); } @Override public boolean hasSetAlarm() { return this.lAlarmHandle != -1; } @Override public void setAlarm() { if (hasSetAlarm()) return; registerV30(); //布防需要注册登录,监听则不需要 if (registered()) { //报警布防参数设置 HCNetSDK.NET_DVR_SETUPALARM_PARAM m_strAlarmInfo = new HCNetSDK.NET_DVR_SETUPALARM_PARAM(); m_strAlarmInfo.dwSize = m_strAlarmInfo.size(); m_strAlarmInfo.byLevel = 0; //布防等级 m_strAlarmInfo.byAlarmInfoType = 1; // 智能交通报警信息上传类型:0- 老报警信息(NET_DVR_PLATE_RESULT),1- 新报警信息(NET_ITS_PLATE_RESULT) m_strAlarmInfo.byDeployType = 0; //布防类型:0-客户端布防,1-实时布防 m_strAlarmInfo.write(); this.lAlarmHandle = this.hcNetSDK.NET_DVR_SetupAlarmChan_V41(this.id, m_strAlarmInfo); if (hasSetAlarm()) { log.info("布防成功:device:{}", devices); } else { error("布防失败", false); } } else { error("未注册,无法布防", false); } } @Override public void close() { if (hasSetAlarm()) { if (this.hcNetSDK.NET_DVR_CloseAlarmChan(this.lAlarmHandle)) { this.lAlarmHandle = -1; log.info("撤防成功:device:{}", devices); } else { error("停止监听失败", false); } } } protected abstract T handler(HCNetSDK sdk, Devices cameraInfo) throws Exception;

截图实现

public class HCNetImageManager extends AbstractHcNetHandler { private final Image image; public HCNetImageManager(HCNetSDK sdk, Devices devices, Image image) { super(sdk, devices); this.image = image; } @Override protected Photo handler(HCNetSDK sdk, Devices cameraInfo) throws Exception { //返回图片数据的大小 IntByReference intByReference = new IntByReference(); //NET_DVR_CaptureJPEGPicture_NEW 这个方法是将图片信息抓到内存中去, //再读取内存写入到文件中去,方便对图片信息进行其他操作 //NET_DVR_CaptureJPEGPicture 可以直接将图片抓取到目标路径 // sdk.NET_DVR_CaptureJPEGPicture(super.id, // cameraInfo.getChannel(), // image.getQuality(), // image.getPhotoPath().getBytes(StandardCharsets.UTF_8)); boolean b = sdk.NET_DVR_CaptureJPEGPicture_NEW( // 这个是注册登录后的返回值,返回值不等于-1 即成功,详见上上述注册登录方法registerV30() super.id, //通道号 cameraInfo.getChannel(), //JPEG图像参数 image.getQuality(), //保存JPEG数据的缓冲区 image.getPointer(), //输入缓冲区大小 //这个参数会影响抓图后的图片大小,1024x1024=1M, //太小会抓图失败报内存空间太小,1024X100=100kb 实测可以抓图成功,低于70kb就会报异常 image.getMemSize(), intByReference); if (b) { log.info("抓图到内存成功,返回长度:{}", intByReference.getValue()); } else { super.error("抓图失败,image:" + image, true); return null; } byte[] byteArray = image.getPointer().getByteArray(0, image.getMemSize()); //这里使用的是hutool的工具类写入文件 FileUtil.writeBytes(byteArray, image.getPhotoPath()); image.getPointer().clear(byteArray.length); return Photo.builder() .name(FileNameUtil.getName(image.getPhotoPath())) .path(image.getPhotoPath()) .ip(cameraInfo.getIp()) .build(); }

抓图的参数

public class Image { /** * 图片质量 */ private final HCNetSDK.NET_DVR_JPEGPARA quality; /** * 图片内存容器 */ private Pointer pointer; /** * 内存容器大小 */ private int memSize; /** * 图片存放路径 */ private String photoPath; private Image(){ this.quality=new HCNetSDK.NET_DVR_JPEGPARA(); } public static Image builder(){ return new Image(); } /** * * @param wPicSize * 注意:当图像压缩分辨率为VGA时,支持0=CIF, 1=QCIF, 2=D1抓图, * 当分辨率为3=UXGA(1600x1200), 4=SVGA(800x600), 5=HD720p(1280x720),6=VGA,7=XVGA, 8=HD900p * 仅支持当前分辨率的抓图. * * * 0=CIF, 1=QCIF, 2=D1 3=UXGA(1600x1200), 4=SVGA(800x600), 5=HD720p(1280x720),6=VGA * * @return */ public Image picSize(short wPicSize){ this.quality.wPicSize=wPicSize; this.quality.write(); return this; } /** * @param wPicQuality

片质量系数 0-最好 1-较好 2-一般

*/ public Image picQuality(short wPicQuality){ this.quality.wPicQuality=wPicQuality; this.quality.write(); return this; } /** * @param memSize

图片内存容器大小

*/ public Image memSize(int memSize ){ this.memSize=memSize; this.pointer=new Memory(memSize); return this; } public Image photoDir(String photoPath){ this.photoPath=photoPath; return this; } }

测试

@SpringBootTest public class HcTest { @Resource HCNetSDK sdk; @Test public void t1() { Image image = Image.builder().photoDir("H:\\test\\192.168.1.2.jpg") .picSize((short) 2) .picQuality((short) 2) .memSize(1024 * 100); Devices devices = devices(); AbstractHcNetHandler photoHandler = new HCNetImageManager(sdk, devices, image); Photo invoke = photoHandler.invoke(); } public Devices devices() { return new Devices() { @Override public String getIp() { return "192.168.1.2"; } @Override public Short getPort() { return 8000; } @Override public String getUsername() { return "admin"; } @Override public String getPassword() { return "admin123"; } @Override public Integer getChannel() { return 1; } }; } } 报警布防

希望设备在遇到某些事件时,我们要捕捉到这一告警并作出相应的动作,执行某些业务,比如设备被遮挡时,环境温度突升,有人员在设备前移动,我要截取当前的设备图片,保存这一记录。

上述将HcNetSDK注册到Spring中时,设置了一个报警回调函数,这个就是设备报警时将信息回调的入口,它是全局唯一的,意味着所有设备报警时都会来这个接口中,设置回调函数时不需要登录设备也侧面印证了这一说法,要接收到报警有两种方式:1:报警布防;2:报警监听

报警监听:不需要逐个登录设备,在sdk加载,初始化,设置回调函数后即可开启监听(我理解的话是在本地开启特定端口,监听报警,设备报警主动上报,这一块儿暂时就没去弄了,后面再研究)

报警布防:报警布防需要登录设备,根据设备的登录返回句柄,根据句柄然后设置布防参数就行了,在上述AbstractHcNetHandler中setAlarm() 就是 报警布防。

public void setAlarm() { if (hasSetAlarm()) return; registerV30(); //布防需要注册登录,监听则不需要 if (registered()) { //报警布防参数设置 HCNetSDK.NET_DVR_SETUPALARM_PARAM m_strAlarmInfo = new HCNetSDK.NET_DVR_SETUPALARM_PARAM(); m_strAlarmInfo.dwSize = m_strAlarmInfo.size(); m_strAlarmInfo.byLevel = 0; //布防等级 m_strAlarmInfo.byAlarmInfoType = 1; // 智能交通报警信息上传类型:0- 老报警信息(NET_DVR_PLATE_RESULT),1- 新报警信息(NET_ITS_PLATE_RESULT) m_strAlarmInfo.byDeployType = 0; //布防类型:0-客户端布防,1-实时布防 m_strAlarmInfo.write(); this.lAlarmHandle = this.hcNetSDK.NET_DVR_SetupAlarmChan_V41(this.id, m_strAlarmInfo); if (hasSetAlarm()) { log.info("布防成功:device:{}", devices); } else { error("布防失败", false); } } else { error("未注册,无法布防", false); } }

进入报警回调函数后就可以解析相关类型了,就会用到上述AlarmDataParse 那个类,这个参照海康提供的示例来弄的。

在海康管理界面里面配置温度突升报警,左侧事件里面可以配置移动侦测,遮挡报警

在这里插入图片描述

测试

@SpringBootTest public class HcTest { @Resource HCNetSDK sdk; @Test public void t1() { Image image = Image.builder().photoDir("H:\\test\\192.168.1.2.jpg") .picSize((short) 2) .picQuality((short) 2) .memSize(1024 * 100); Devices devices = devices(); //再写个其他实现类也成,我这里偷懒直接用的抓图实现 AbstractHcNetHandler photoHandler = new HCNetImageManager(sdk, devices, image); //告警布防 photoHandler.setAlarm(); //todo 在设备前打个打火机,就会触发温度报警 try { Thread.sleep(1000 * 60 * 5); } catch (InterruptedException e) { e.printStackTrace(); } } public Devices devices() { return new Devices() { @Override public String getIp() { return "192.168.1.2"; } @Override public Short getPort() { return 8000; } @Override public String getUsername() { return "admin"; } @Override public String getPassword() { return "admin123"; } @Override public Integer getChannel() { return 1; } }; } } 拉流

设备主要功能还是监控,我们要获取设备的视频流进行统一管理,对单一设备进行视频流的控制,实测拉流再推流到媒体服务器,视频延迟在4-5s。

视频推流会用到FFMPEG,接流用到SRS媒体服务器,可以先在本地安装一个ffmpeg ,试一下将视频流推到服务器,再用播放器播放。

ffmpeg 命令参数有很多,可以在官网查看,本次用到的参数windows和linux可以共用,(之前测试时有些命令windows可以用,linux不能用,就很蛋疼,比如那个降低延迟的参数zerolatency),实际上不考虑性能的话可以直接用ffmpeg 对视频流进行截图,但测试时截图一次要话3s时间,但用sdk的话一台设备截图只需要3-400ms,前提是你已经登录注册且获取设备性能,所以在设计上可以在登录设备后将每台设备与sdk的包装类缓存起来,待需要时直接拿取进行相应操作。

public static final LinkedList DEFAULT_CMD = new LinkedList() {{ add("-rtsp_transport");//设置RTSP传输协议 add("tcp");//tcp/udp add("-i");//设置输入流 add("input-address");//input流地址占位 add("-threads");//设置线程个数 add("8");// add("-r");//帧率设定 add("30");//默认25帧 add("-b:v");//输出视频比特率 add("1000k");// add("-bufsize");// add("1000k");// add("-maxrate");// add("1000k");// add("-acodec");//设置音频编码格式 add("copy");//直接用输入流音频格式 add("-vcodec");//设置视频编码格式 add("copy");//直接用输入流视频格式 add("-an");//禁用音频 add("-f");//设置输出文件格式 add("flv");// add("-y");//覆盖输出文件而不询问 add("output-address");// }};

拼起来就是

ffmpeg -rtsp_transport tcp -i "rtsp拉流地址" -threads 8 -r 30 -b:v 1000k -bufsize 1000k -maxrate 1000k -acodec copy -vcodec copy -an -f flv -y "rtmp推流地址"

海康rtsp拉流地址:rtsp://用户名:密码@设备地址:554/h264/ch1/main/av_stream

比如 rtsp://admin:[email protected]:554/h264/ch1/main/av_stream

rtmp推流地址: rtmp://媒体服务器地址:1935/live/文件名

比如 rtmp://192.168.1.200:1935/live/192.168.1.2

执行完成后,打开vlc或者srs自带的播放器 输入播放地址 http://192.168.1.200:8080/live/192.168.1.2.flv 即可播放

然后用java调用其实就是 用Process调用外部命令,可以在内部侦测程序是否结束,执行是否成功

public static ProcessWrapper exec(LinkedList cmd, boolean startNow, boolean destroyOnRuntimeShutdown, boolean openIOStreams) throws IOException { //这个是上述maven引入的,点进去可以看到它帮我们复制了一个ffmpeg到本地路径,所以如果把ffmpeg打包进去就会很大,直接输入命令即可执行ffmpeg命令,已经默认在参数前添加了 ffmpeg的全路径 ProcessWrapper process = new DefaultFFMPEGLocator().createExecutor(); cmd.forEach(process::addArgument); //destroyOnRuntimeShutdown 主程序结束后是否结束 //openIOStreams 是否开启输入,输出流 if (startNow) process.execute(destroyOnRuntimeShutdown, openIOStreams); return process; }

这个是ProcessWrapper部分代码,可以看到有三个标准输入输出流,可以读取inputStream和errorStream内容来查看外部标注输出和异常输出,可以通过判断输出内容中关键字来判断进程是否退出或其他异常,虽然Process有接口可以查看进程是否退出,但它也仅仅只能检测进程是否退出(而且命令一开始就执行失败会一直卡住在那里,无法获取到程序返回码,不知道有无大佬知道如何解决),拉流是一直在拉的,中途流媒体服务器异常或设备异常,程序退出就无法判断是哪里故障了。

public class ProcessWrapper implements AutoCloseable { private static final Logger LOG = LoggerFactory.getLogger(ProcessWrapper.class); private final String ffmpegExecutablePath; private final ArrayList args = new ArrayList(); private Process ffmpeg = null; private ProcessKiller ffmpegKiller = null; private InputStream inputStream = null; private OutputStream outputStream = null; private InputStream errorStream = null; }

测试

public class FFMPEGTest { public static void main(String[] args) throws IOException { LinkedList DEFAULT_CMD = new LinkedList() {{ add("-rtsp_transport");//设置RTSP传输协议 add("tcp");//tcp/udp add("-i");//设置输入流 add("rtsp://admin:[email protected]:554/h264/ch1/main/av_stream");//input流地址占位 add("-threads");//设置线程个数 add("8");// add("-r");//帧率设定 add("30");//默认25帧 add("-b:v");//输出视频比特率 add("1000k");// add("-bufsize");// add("1000k");// add("-maxrate");// add("1000k");// add("-acodec");//设置音频编码格式 add("copy");//直接用输入流音频格式 add("-vcodec");//设置视频编码格式 add("copy");//直接用输入流视频格式 add("-an");//禁用音频 add("-f");//设置输出文件格式 add("flv");// add("-y");//覆盖输出文件而不询问 add("rtmp://192.168.1.200:1935/live/192.168.1.2");// }}; ProcessWrapper exec = exec(DEFAULT_CMD, true, false, true); BufferedReader br = new BufferedReader(new InputStreamReader(exec.getInputStream())); String msg; while ((msg = br.readLine()) != null) { if (msg.contains("error") || msg.contains("fail") || msg.contains("miss") || msg.contains("No such")) { System.out.println("程序发生异常:" + msg); } else { System.out.println("程序执行回显:" + msg); } } exec.destroy(); } public static ProcessWrapper exec(LinkedList cmd, boolean startNow, boolean destroyOnRuntimeShutdown, boolean openIOStreams) throws IOException { ProcessWrapper process = new DefaultFFMPEGLocator().createExecutor(); cmd.forEach(process::addArgument); if (startNow) process.execute(destroyOnRuntimeShutdown, openIOStreams); return process; } }

然后访问媒体服务器上flv格式播放地址即可,测试中使用的是srs默认的推流地址,播放地址对应的是:http://192.168.1.200:8080/live/192.168.1.2.flv (后面这个ip随便取名,你推的时候是什么,拉流的时候就用什么) ; srs支持转换为多种不同格式的视频流地址,按需使用即可,http-flv使用上对比hls延迟更低,但兼容性上会比hls低。详见(Docker | SRS (ossrs.net))。



【本文地址】

公司简介

联系我们

今日新闻

    推荐新闻

    专题文章
      CopyRight 2018-2019 实验室设备网 版权所有