首页 > 基础资料 博客日记

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

2024-09-18 09:00:07基础资料围观22

这篇文章介绍了java 对接海康 抓图,报警布防,视频拉流,分享给大家做个参考,收藏Java资料网收获更多编程知识

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 用来测试视频地址

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

<dependencies>
     <!--ffmpeg-->
    <dependency>
        <groupId>org.bytedeco.javacpp-presets</groupId>
        <artifactId>ffmpeg</artifactId>
        <version>4.1-1.4.4</version>
    </dependency>
    <dependency>
        <groupId>ws.schild</groupId>
        <artifactId>jave-all-deps</artifactId>
        <version>3.3.1</version>
        <exclusions>
            <!--  排除windows 32位系统      -->
            <exclusion>
                <groupId>ws.schild</groupId>
                <artifactId>jave-nativebin-win32</artifactId>
            </exclusion>
            <!--  排除linux 32位系统      -->
            <exclusion>
                <groupId>ws.schild</groupId>
                <artifactId>jave-nativebin-linux32</artifactId>
            </exclusion>
            <!-- 排除Mac系统-->
            <exclusion>
                <groupId>ws.schild</groupId>
                <artifactId>jave-nativebin-osx64</artifactId>
            </exclusion>
            <!-- 排除osxm-->
            <exclusion>
                <groupId>ws.schild</groupId>
                <artifactId>jave-nativebin-osxm1</artifactId>
            </exclusion>
            <!-- 排除arm-->
            <exclusion>
                <groupId>ws.schild</groupId>
                <artifactId>jave-nativebin-linux-arm32</artifactId>
            </exclusion>
            <exclusion>
                <groupId>ws.schild</groupId>
                <artifactId>jave-nativebin-linux-arm64</artifactId>
            </exclusion>
        </exclusions>
    </dependency>

    <!-- jna 这个使用海康提供的jna最好,这里是自己引入的-->
    <dependency>
        <groupId>net.java.dev.jna</groupId>
        <artifactId>jna</artifactId>
        <version>5.13.0</version>
    </dependency>

    <!-- hc API  这个在海康官网下载的包里会有,必须要引入-->
    <dependency>
        <groupId>examples</groupId>
        <artifactId>hc-examples</artifactId>
        <version>1.0</version>
        <scope>system</scope>
        <systemPath>${project.basedir}/lib/examples.jar</systemPath>
    </dependency>

</dependencies>

这个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<T>  {
    //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<Photo> {
    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(),
                /*输入缓冲区大小
                *太小会抓图失败报内存空间太小,1024X100=100kb 实测可以抓图成功,低于70kb就会报异常
                *同时这个缓冲区大小是不固定的,取决于你摄像头拍摄的照片质量,有可能当时测试时,设置100kb就可以了,但环境变化了,截取的照片大小超过了阈值,也会报内存空间太小,尽量设置大一些,兼容多个场景
                */
                image.getMemSize(),
                intByReference);
        if (b) {
            log.info("抓图到内存成功,返回长度:{}", intByReference.getValue());
        } else {
            super.error("抓图失败,image:" + image, true);
            return null;
        }

        //实际抓取多长就读多长,使用image.getMemSize() 图片大小会和MemSize一样
        byte[] byteArray = image.getPointer().getByteArray(0, intByReference.getValue());
        
        //这里使用的是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 <p>片质量系数 0-最好 1-较好 2-一般</p>
     */
    public Image picQuality(short wPicQuality){
        this.quality.wPicQuality=wPicQuality;
        this.quality.write();
        return this;
    }

    /**
     * @param memSize <p> 图片内存容器大小 </p>
     */
    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<Photo> 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<Photo> 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<String> DEFAULT_CMD = new LinkedList<String>() {{
        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:admin123@192.168.1.2: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<String> 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<String> 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<String> DEFAULT_CMD = new LinkedList<String>() {{
            add("-rtsp_transport");//设置RTSP传输协议
            add("tcp");//tcp/udp
            add("-i");//设置输入流
            add("rtsp://admin:admin123@192.168.1.2: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<String> 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))。


文章来源:https://blog.csdn.net/weixin_52078718/article/details/136533257
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:jacktools123@163.com进行投诉反馈,一经查实,立即删除!

标签:

相关文章

本站推荐

标签云