文章详情

短信预约-IT技能 免费直播动态提醒

请输入下面的图形验证码

提交验证

短信预约提醒成功

通过java解码web前端直接预览海康威视摄像头

2023-09-29 18:26

关注

一:前言

        最近进行项目开发时遇到了需要前端直接调用摄像头,并直接进行播放的需求。原本计划通过海康威视官网的《WEB无插件开发包 V3.2》直接进行控制、交互,实现摄像头直接登录以及取流预览。但是前端人员现场驻场开发后反映各种兼容性问题频发,反正就是不能友好的进行预览播放。鉴于此我直接查询了官网上相关的sdk,然后选用了《设备网络SDK_Win64 V6.1.9.4_build20220412》进行开发java版本的转码工具。整体思路是在PS流中解析出H264的裸流然后通过websocket传给前端,前端基于wfs.js进行h264的裸流播放。

二:开发准备

下载开发SDK开发包,并先查看和熟悉sdk使用方法并先查看和熟悉sdk使用方法并先查看和熟悉sdk使用方法!(拜托不要一来就问源码呀,这就是我写的所有的代码了呀,甚至前端我都给你贴出来了,前端引入js、引入jquery-3.0.0.js不需要教吧。麻烦先在官网下载下来把项目运行起来啊,现在的做个开发的都这么浮夸的吗?)

海康开放平台sdk下载

下载wfs.js插件:GitHub - MarkRepo/wfs.js: use html5 video tag with MSE for raw h264 live streaming.

三:整体介绍

逻辑流转图:

由于博主太懒了,这里没有图。

3.1.后端逻辑:


①:java加载SDK包(dll)实现sdk加载和调用

②:调用sdk的NET_DVR_Login_V40(m_strLoginInfo, m_strDeviceInfo)接口实现登录。

③:调用sdk的NET_DVR_RealPlay_V40(userID, strClientInfo, fRealDataCallBack , null)接口实现预览取流。

④:在我们自定义的fRealDataCallBack回调函数中对取流数据进行解码,就是将数据进行截取以及转码操作,并将数据包进行存贮。

⑤:创建websocket类监听前端连接,区分摄像头后通过websocket进行实时推流。socket地址为'/wstest/{lUserID}'  其中lUserID是登录并预览成功后返回的lUserID!

主要代码展示:

package com.xunshi.hikangvision.Controller;import com.xunshi.hikangvision.untils.PreverViewUntil;import com.xunshi.hikangvision.vo.LoginVo;import com.xunshi.hikangvision.vo.result.R;import org.springframework.web.bind.annotation.*;import java.util.List;@RestController@RequestMapping("/playvision")public class HKController {//    @Resource PreverViewUntil preverViewUntil;        @PostMapping("/loginAndPlayView")    public R loginAndPlayView(@RequestBody LoginVo loginVo)    {       return R.buildOkData(PreverViewUntil.loginAndPlayView(loginVo));    }        //@ApiParam("loginAndPlayView接口中返回的lUserID")    //@ApiParam("loginAndPlayView接口中返回的lPlayID")    @GetMapping("/logoutPlayView")    public R logoutPlayView(            @RequestParam (value = "lUserID",required = true) String lUserID,            @RequestParam (value = "lPlayID",required = false) String lPlayID)    {        if(null!=lPlayID)        {            PreverViewUntil.logoutPlayView(lUserID,lPlayID);        }        else        {            PreverViewUntil.logoutPlayView(lUserID);        }        return R.buildOk();    }        @PostMapping("/login")    public R> login(@RequestBody List loginVos)    {        return R.buildOkData(PreverViewUntil.login(loginVos));    }        @PostMapping("/playView")    public R playView(@RequestBody LoginVo loginVo)    {        return R.buildOkData(PreverViewUntil.playView(loginVo));    }        @PostMapping("/logoutPlayViewOnly")    public R logoutPlayViewOnly(@RequestBody LoginVo loginVo)    {        return R.buildOkData(PreverViewUntil.logoutPlayViewOnly(loginVo));    }}
package com.xunshi.hikangvision.untils;import com.sun.jna.Native;import com.sun.jna.Pointer;import com.xunshi.hikangvision.untils.Common.osSelect;import com.xunshi.hikangvision.vo.LoginVo;import com.xunshi.hikangvision.vo.MyBlockingQueue;import javax.annotation.PostConstruct;import javax.annotation.PreDestroy;import java.util.ArrayList;import java.util.List;import java.util.concurrent.ArrayBlockingQueue;import java.util.concurrent.BlockingQueue;//@Componentpublic class PreverViewUntil {    static boolean isInit = false;//是否初始化    static HCNetSDK hCNetSDK = null;    static PlayCtrl playControl = null;    static PreverViewUntil.FExceptionCallBack_Imp fExceptionCallBack;//异常捕获回调        static class FExceptionCallBack_Imp implements HCNetSDK.FExceptionCallBack {        public void invoke(int dwType, int lUserID, int lHandle, Pointer pUser) {            switch(dwType)            {                case HCNetSDK.EXCEPTION_AUDIOEXCHANGE://语音对讲时网络异常                    System.out.println("登录句柄:"+lUserID+"语音对讲异常");                    break;                case HCNetSDK.EXCEPTION_ALARM://报警上传时网络异常                    System.out.println("登录句柄:"+lUserID+"报警上传时网络异常");                    break;                case HCNetSDK.EXCEPTION_PREVIEW://网络预览时异常                    System.out.println("登录句柄:"+lUserID+"网络预览时异常");                    //TODO: 关闭网络预览                    break;                case HCNetSDK.EXCEPTION_SERIAL://透明通道传输时异常                    System.out.println("登录句柄:"+lUserID+"透明通道传输时异常");                    //TODO: 关闭透明通道                    break;                case HCNetSDK.EXCEPTION_RECONNECT://预览时重连                    System.out.println("登录句柄:"+lUserID+"预览时重连");                    break;                default:                    System.out.println("登录句柄:"+lUserID+",异常事件类型:"+Integer.toHexString(dwType));                    System.out.println("具体错误参照 SDK网络使用手册中:NET_DVR_SetExceptionCallBack_V30 方法中的异常定义!");                    break;            }            return;        }    }        private static boolean CreateSDKInstance() {        if (hCNetSDK == null) {            synchronized (HCNetSDK.class) {                String strDllPath = "";                try {                    if (osSelect.isWindows())                        //win系统加载库路径                        strDllPath = System.getProperty("user.dir") + "\\lib\\HCNetSDK.dll";                    else if (osSelect.isLinux())                        //Linux系统加载库路径                        strDllPath = System.getProperty("user.dir") + "/lib/libhcnetsdk.so";                    System.out.println("loadLibrary: " + strDllPath);                    hCNetSDK = (HCNetSDK) Native.loadLibrary(strDllPath, HCNetSDK.class);                } catch (Exception ex) {                    System.out.println("loadLibrary: " + strDllPath + " Error: " + ex.getMessage());                    return false;                }            }        }        return true;    }        private static boolean CreatePlayInstance() {        if (playControl == null) {            synchronized (PlayCtrl.class) {                String strPlayPath = "";                try {                    if (osSelect.isWindows())                        //win系统加载库路径                        strPlayPath = System.getProperty("user.dir") + "\\lib\\PlayCtrl.dll";                    else if (osSelect.isLinux())                        //Linux系统加载库路径                        strPlayPath = System.getProperty("user.dir") + "/lib/libPlayCtrl.so";                    playControl=(PlayCtrl) Native.loadLibrary(strPlayPath,PlayCtrl.class);                } catch (Exception ex) {                    System.out.println("loadLibrary: " + strPlayPath + " Error: " + ex.getMessage());                    return false;                }            }        }        return true;    }        @PostConstruct    public static void init() {        System.out.println("加载海康威视SDK dll");        System.out.println("初始化路径为:"+System.getProperty("user.dir") + "\\lib\\HCNetSDK.dll");        if (hCNetSDK == null&&playControl==null) {            if (!CreateSDKInstance()) {                System.out.println("Load SDK fail");                return;            }            if (!CreatePlayInstance()) {                System.out.println("Load PlayCtrl fail");                return;            }        }        System.out.println("海康威视SDK dll加载成功");        //linux系统建议调用以下接口加载组件库        if (osSelect.isLinux()) {            HCNetSDK.BYTE_ARRAY ptrByteArray1 = new HCNetSDK.BYTE_ARRAY(256);            HCNetSDK.BYTE_ARRAY ptrByteArray2 = new HCNetSDK.BYTE_ARRAY(256);            //这里是库的绝对路径,请根据实际情况修改,注意改路径必须有访问权限            String strPath1 = System.getProperty("user.dir") + "/lib/libcrypto.so.1.1";            String strPath2 = System.getProperty("user.dir") + "/lib/libssl.so.1.1";            System.arraycopy(strPath1.getBytes(), 0, ptrByteArray1.byValue, 0, strPath1.length());            ptrByteArray1.write();            hCNetSDK.NET_DVR_SetSDKInitCfg(3, ptrByteArray1.getPointer());            System.arraycopy(strPath2.getBytes(), 0, ptrByteArray2.byValue, 0, strPath2.length());            ptrByteArray2.write();            hCNetSDK.NET_DVR_SetSDKInitCfg(4, ptrByteArray2.getPointer());            String strPathCom = System.getProperty("user.dir") + "/lib";            HCNetSDK.NET_DVR_LOCAL_SDK_PATH struComPath = new HCNetSDK.NET_DVR_LOCAL_SDK_PATH();            System.arraycopy(strPathCom.getBytes(), 0, struComPath.sPath, 0, strPathCom.length());            struComPath.write();            hCNetSDK.NET_DVR_SetSDKInitCfg(2, struComPath.getPointer());        }        System.out.println("开始初始化海康威视Sdk");        //SDK初始化,一个程序只需要调用一次        boolean initSuc = hCNetSDK.NET_DVR_Init();        if (initSuc != true) {            System.out.println("初始化海康威视Sdk失败");        }        System.out.println("海康威视Sdk初始化成功!");        System.out.println("开始设置异常消息回调");        //异常消息回调        if(fExceptionCallBack == null)        {            fExceptionCallBack = new PreverViewUntil.FExceptionCallBack_Imp();        }        Pointer pUser = null;        if (!hCNetSDK.NET_DVR_SetExceptionCallBack_V30(0, 0, fExceptionCallBack, pUser)) {            return ;        }        System.out.println("设置异常消息回调成功");        System.out.println("开始设置启动SDK写日志");        //启动SDK写日志        hCNetSDK.NET_DVR_SetLogToFile(3, "..\\sdkLog\\", false);        isInit = true;    }    //类销毁时清理sdk    @PreDestroy    public void clearSdk() {        if (null!=hCNetSDK)        {            //SDK反初始化,释放资源,只需要退出时调用一次            hCNetSDK.NET_DVR_Cleanup();        }    }        public static List login(List loginVos) {        if(loginVos.size()<1) return loginVos;        for (int i = 0; i < loginVos.size(); i++) {            LoginVo loginVo = loginVos.get(i);            if(!isInit)            {                init();            }            //如果已经登录,就先退出登陆            String userIdByIp = MyBlockingQueue.findUserIdByIp(loginVo.getIp());            if(null!=userIdByIp)            {                //自动判断是否在预览并退出                PreverViewUntil.logoutPlayView(userIdByIp);            }            //登录设备,每一台设备分别登录; 登录句柄是唯一的,可以区分设备            HCNetSDK.NET_DVR_USER_LOGIN_INFO m_strLoginInfo = new HCNetSDK.NET_DVR_USER_LOGIN_INFO();//设备登录信息            HCNetSDK.NET_DVR_DEVICEINFO_V40 m_strDeviceInfo = new HCNetSDK.NET_DVR_DEVICEINFO_V40();//设备信息            String m_sDeviceIP = "********";//设备ip地址            m_sDeviceIP=loginVo.getIp();            m_strLoginInfo.sDeviceAddress = new byte[HCNetSDK.NET_DVR_DEV_ADDRESS_MAX_LEN];            System.arraycopy(m_sDeviceIP.getBytes(), 0, m_strLoginInfo.sDeviceAddress, 0, m_sDeviceIP.length());            String m_sUsername = "*****";//设备用户名            m_sUsername=loginVo.getUserName();            m_strLoginInfo.sUserName = new byte[HCNetSDK.NET_DVR_LOGIN_USERNAME_MAX_LEN];            System.arraycopy(m_sUsername.getBytes(), 0, m_strLoginInfo.sUserName, 0, m_sUsername.length());            String m_sPassword = "******";//设备密码            m_sPassword=loginVo.getPassword();            m_strLoginInfo.sPassword = new byte[HCNetSDK.NET_DVR_LOGIN_PASSWD_MAX_LEN];            System.arraycopy(m_sPassword.getBytes(), 0, m_strLoginInfo.sPassword, 0, m_sPassword.length());            m_strLoginInfo.wPort = 8000; //SDK端口            m_strLoginInfo.bUseAsynLogin = false; //是否异步登录:0- 否,1- 是            m_strLoginInfo.write();            String ipPortStr=loginVo.getIp()+":"+loginVo.getProt();            System.out.println("开始登录:"+ipPortStr);            int lUserID = hCNetSDK.NET_DVR_Login_V40(m_strLoginInfo, m_strDeviceInfo);            if (lUserID == -1) {                System.out.println(ipPortStr+"登录失败,错误码为" + hCNetSDK.NET_DVR_GetLastError());                loginVo.setLoginStatus("0");                loginVo.setLoginMessage("登录失败,错误码为" + hCNetSDK.NET_DVR_GetLastError());                loginVo.setLUserID(null);            } else {                //判断DVR工作状态//                HCNetSDK.NET_DVR_WORKSTATE_V30 devwork = new HCNetSDK.NET_DVR_WORKSTATE_V30();//                boolean net_DVR_GetDVRWorkState_V30 = hCNetSDK.NET_DVR_GetDVRWorkState_V30(lUserID, devwork);//                if (net_DVR_GetDVRWorkState_V30) {//                    //设备的状态,0-正常,1-CPU占用率太高,超过85%,2-硬件错误,例如串口死掉//                    if(devwork.dwDeviceStatic!=0)//                    {////                    }//                }//                else//                {//                    //未知错误:获取DVR工作状态失败//                }                //这里直接认为登陆成功就能预览吧。。                String successStr=m_sDeviceIP + ":设备登录成功! " + "设备序列号:" +                        new String(m_strDeviceInfo.struDeviceV30.sSerialNumber).trim();                System.out.println(successStr);                loginVo.setLoginMessage(successStr);                loginVo.setLoginStatus("1");                loginVo.setLUserID(lUserID);                m_strDeviceInfo.read();                //记录ip已经登录                MyBlockingQueue.IPToUserIdMap.put(loginVo.getIp(),loginVo.getLUserID().toString());            }        }        return loginVos;    }        public static LoginVo playView(LoginVo vo)    {        //注释掉的代码也可以参考,去掉注释可以运行        //VideoDemo.getIPChannelInfo(lUserID); //获取IP通道        //int lDChannel = (int)m_strDeviceInfo.struDeviceV30.byStartDChan + lChannel -1;        String ipPortStr=vo.getIp()+":"+vo.getProt();        System.out.println("预览通道号: " + vo.getLDChannel());        System.out.println("尝试预览连接:"+ipPortStr);        VidePreView.RealPlay(vo.getLUserID(), vo.getLDChannel());//预览        if(VidePreView.lPlayStatus)        {            vo.setPlsyStatus("1");            vo.setPlsyMessage("预览请求成功!");            vo.setLPlayID(VidePreView.lPlay);            System.out.println("预览请求成功:"+vo);            //创建数据体,等待视频流实时回调            BlockingQueue bq = new ArrayBlockingQueue<>(10);            MyBlockingQueue.bpMap.put(vo.getLUserID().toString(),bq);            MyBlockingQueue.PlayToUserIdMap.put(vo.getLPlayID().toString(),vo.getLUserID().toString());        }else        {            vo.setPlsyStatus("0");            vo.setPlsyMessage("预览失败:"+VidePreView.lPlayErrorMassage);            vo.setLPlayID(null);        }        return vo;    }        public static LoginVo loginAndPlayView(LoginVo loginVo) {        if(!isInit)        {            init();        }        //如果已经登录,就先退出登陆        String userIdByIp = MyBlockingQueue.findUserIdByIp(loginVo.getIp());        if(null!=userIdByIp)        {            //自动判断是否在预览并退出          PreverViewUntil.logoutPlayView(userIdByIp);        }        //登录        List loginVos = new ArrayList<>();        loginVos.add(loginVo);        loginVos = login(loginVos);        String ipPortStr=loginVo.getIp()+":"+loginVo.getProt();        if("1".equals(loginVo.getLoginStatus()))        {            //预览            if("1".equals(loginVos.get(0).getLoginStatus()))            {                playView(loginVos.get(0));            }            else            {                System.out.println(ipPortStr+"----登陆成功,但是预览失败!");                //登录并预览接口中,如果登陆成功但是预览失败,自动退出登录-清理数据体                PreverViewUntil.logoutPlayView(loginVo.getLUserID().toString());            }        }        else        {            System.out.println(ipPortStr+"----登陆失败!");        }        return loginVos.get(0);    }        public static void logoutPlayView(String lUserID,String lPlayID) {        if(null!=hCNetSDK&&MyBlockingQueue.bpMap.containsKey(lUserID))        {            if (hCNetSDK.NET_DVR_StopRealPlay(Integer.valueOf(lPlayID)))            {                System.out.println("停止预览成功");            }            //退出程序时调用,每一台设备分别注销            if (hCNetSDK.NET_DVR_Logout(Integer.valueOf(lUserID))) {                System.out.println("注销登录成功");            }            //清理数据体            MyBlockingQueue.clearByUserId(lUserID,true);        }    }        public static void logoutPlayView(String lUserID) {        if(null!=hCNetSDK &&MyBlockingQueue.bpMap.containsKey(lUserID))        {            String playId = MyBlockingQueue.findPlayIdByUserId(lUserID);            if(null!=playId)            {                if (hCNetSDK.NET_DVR_StopRealPlay(Integer.valueOf(playId)))                {                    System.out.println("停止预览成功");                }            }            //退出程序时调用,注销登录            if (hCNetSDK.NET_DVR_Logout(Integer.valueOf(lUserID))) {                System.out.println("注销登录成功");            }            //清理数据体            MyBlockingQueue.clearByUserId(lUserID,true);        }    }        public static LoginVo logoutPlayViewOnly(LoginVo loginVo) {        if(null!=hCNetSDK)        {            if(null!=loginVo.getLPlayID())            {                if (hCNetSDK.NET_DVR_StopRealPlay(loginVo.getLPlayID()))                {                    System.out.println("停止预览成功");                    loginVo.setLPlayID(null);                    loginVo.setPlsyMessage("停止预览成功");                    loginVo.setLoginStatus("0");                }                else                {                    //接口返回失败请调用NET_DVR_GetLastError获取错误码,通过错误码判断出错原因。                    loginVo.setPlsyMessage("停止预览失败:接口返回失败请调用NET_DVR_GetLastError获取错误码,通过错误码判断出错原因。");                }            }            //清理数据体            MyBlockingQueue.clearByUserId(loginVo.getLUserID().toString(),false);        }        return loginVo;    }}
package com.xunshi.hikangvision.untils;import com.sun.jna.Pointer;import com.sun.jna.ptr.ByteByReference;import com.xunshi.hikangvision.vo.MyBlockingQueue;import java.io.IOException;import java.util.HashMap;import java.util.Map;import java.util.concurrent.BlockingQueue;import static com.xunshi.hikangvision.untils.PreverViewUntil.hCNetSDK;public class VidePreView {    static FRealDataCallBack1 fRealDataCallBack;//预览回调函数实现    static VideoDemo.fPlayEScallback fPlayescallback; //裸码流回调函数    public static int lPlay = -1;  //预览句柄    public static boolean lPlayStatus = false;//预览是否成功    public static String lPlayErrorMassage = "";//预览错误信息        public static void RealPlay(int userID, int iChannelNo) {        if (userID == -1) {            System.out.println("请先登录");            lPlayStatus=false;            lPlayErrorMassage="请先登录";            return;        }        HCNetSDK.NET_DVR_PREVIEWINFO strClientInfo = new HCNetSDK.NET_DVR_PREVIEWINFO();        strClientInfo.read();        strClientInfo.hPlayWnd = null;  //窗口句柄,从回调取流不显示一般设置为空        strClientInfo.lChannel = iChannelNo;  //通道号        strClientInfo.dwStreamType=0; //0-主码流,1-子码流,2-三码流,3-虚拟码流,以此类推        strClientInfo.dwLinkMode=0; //连接方式:0- TCP方式,1- UDP方式,2- 多播方式,3- RTP方式,4- RTP/RTSP,5- RTP/HTTP,6- HRUDP(可靠传输) ,7- RTSP/HTTPS,8- NPQ        strClientInfo.bBlocked=1;        strClientInfo.write();        //回调函数定义必须是全局的        if (fRealDataCallBack == null) {            fRealDataCallBack = new FRealDataCallBack1();        }        //开启预览        lPlay = hCNetSDK.NET_DVR_RealPlay_V40(userID, strClientInfo, fRealDataCallBack , null);        if (lPlay == -1) {            int iErr = hCNetSDK.NET_DVR_GetLastError();            System.out.println("取流失败" + iErr);            lPlayStatus=false;            lPlayErrorMassage="取流失败,错误码:" + iErr;            return;        }        System.out.println("取流成功");        lPlayStatus=true;        return;    }    //预览回调    static class FRealDataCallBack1 implements HCNetSDK.FRealDataCallBack_V30 {        //预览回调   lRealHandle预览句柄回调        public void invoke(int lRealHandle, int dwDataType, ByteByReference pBuffer, int dwBufSize, Pointer pUser) {            //System.out.println("码流数据回调, 数据类型: " + dwDataType + ", 数据长度:" + dwBufSize);            //播放库解码            switch (dwDataType) {                case HCNetSDK.NET_DVR_SYSHEAD: //系统头                case HCNetSDK.NET_DVR_STREAMDATA:   //码流数据                    if ((dwBufSize > 0)) {                        //System.out.println("取流回调进行中.....");                        byte[] outputData = pBuffer.getPointer().getByteArray(0, dwBufSize);                        try {writeESH264(outputData,lRealHandle);//将流写入对应的实体                        } catch (IOException e) {e.printStackTrace();                        }                    }            }        }        //多路视频的pes数据进行缓存,知道某一路视频的RTP包开头进入时进行取出返给前端        Map EsBytesMap=new HashMap<>();                public void writeESH264(final byte[] outputData,int lRealHandle) throws IOException {            if (outputData.length <= 0) {                return;            }            String playIdStr = String.valueOf(lRealHandle);            byte[] allEsBytes = null;//当前这个通道的一个Rtp包数据            if(!EsBytesMap.containsKey(playIdStr))            {                EsBytesMap.put(playIdStr,allEsBytes);            }            else            {                allEsBytes=EsBytesMap.get(playIdStr);            }            if ((outputData[0] & 0xff) == 0x00//                    && (outputData[1] & 0xff) == 0x00//                    && (outputData[2] & 0xff) == 0x01//                    && (outputData[3] & 0xff) == 0xBA) {// RTP包开头                // 一个完整的帧解析完成后将解析的数据放入BlockingQueue,websocket获取后发送给前端                if (allEsBytes != null && allEsBytes.length > 0) {                    //System.out.println("回调的lRealHandle:"+lRealHandle);                    if(MyBlockingQueue.PlayToUserIdMap.containsKey(String.valueOf(lRealHandle)))                    {                        String userId = MyBlockingQueue.PlayToUserIdMap.get(String.valueOf(lRealHandle));                        BlockingQueue blockingQueue = MyBlockingQueue.bpMap.get(userId);                        //System.out.println("当前的lPlayID:"+lRealHandle);                        //System.out.println("myBlockingQueue.bq is null?"+(null==blockingQueue));                        try {blockingQueue.put(allEsBytes);//将当前的某一路视频通道的上一个Rtp包放到队列中去MyBlockingQueue.bpMap.put(userId,blockingQueue);allEsBytes = null;EsBytesMap.put(playIdStr,allEsBytes);//置空当前通道的RTP包,下次回调就是pes包进行取流追加                        } catch (InterruptedException e) {e.printStackTrace();                        }                    }                }            }            // 是00 00 01 eo开头的就是视频的pes包            if ((outputData[0] & 0xff) == 0x00//                    && (outputData[1] & 0xff) == 0x00//                    && (outputData[2] & 0xff) == 0x01//                    && (outputData[3] & 0xff) == 0xE0) {//                // 去掉包头后的起始位置                int from = 9 + outputData[8] & 0xff;                int len = outputData.length - 9 - (outputData[8] & 0xff);                // 获取es裸流                byte[] esBytes = new byte[len];                System.arraycopy(outputData, from, esBytes, 0, len);                if (allEsBytes == null) {                    allEsBytes = esBytes;                } else {                    byte[] newEsBytes = new byte[allEsBytes.length + esBytes.length];                    System.arraycopy(allEsBytes, 0, newEsBytes, 0, allEsBytes.length);                    System.arraycopy(esBytes, 0, newEsBytes, allEsBytes.length, esBytes.length);                    allEsBytes = newEsBytes;                }                EsBytesMap.put(playIdStr,allEsBytes);//当前视频通道的部分包数据进行缓存            }        }    }}
package com.xunshi.hikangvision.sevice;import com.xunshi.hikangvision.untils.PreverViewUntil;import com.xunshi.hikangvision.vo.MyBlockingQueue;import lombok.extern.slf4j.Slf4j;import org.springframework.stereotype.Component;import javax.websocket.*;import javax.websocket.server.PathParam;import javax.websocket.server.ServerEndpoint;import java.io.IOException;import java.nio.ByteBuffer;import java.util.concurrent.BlockingQueue;import java.util.concurrent.atomic.AtomicInteger;@Slf4j@ServerEndpoint(value = "/wstest/{lUserID}")@Componentpublic class OneWebSocket {        private static AtomicInteger onlineCount = new AtomicInteger(0);        @OnOpen    public void onOpen(final Session session , @PathParam("lUserID") String lUserID) {        onlineCount.addAndGet(1);        System.out.println("当前已经登录用户句柄S:"+MyBlockingQueue.bpMap.keySet());        log.info("有新连接加入sessionid:{},摄像头登录用户的句柄为:{} 当前在线socket(视频路数)数量:{}", session.getId(),lUserID, onlineCount);        if(MyBlockingQueue.bpMap.containsKey(lUserID)){            if(null==MyBlockingQueue.findPlayIdByUserId(lUserID))            {                System.out.println(String.format("警告:根据登录句柄%s,没有找到用户预览句柄",lUserID));            }            BlockingQueue blockingQueue = MyBlockingQueue.bpMap.get(lUserID);            MyBlockingQueue.SessionToUserIdMap.put(session.getId(),lUserID);            //这里按照逻辑来说这里绑定后就应该开启一个线层来干这个事情,查询了一下好像websocket就是多线程的直接干吧            while (null!=session&&session.isOpen()&&null!=blockingQueue) {                try {                    byte[] esBytes = (byte[]) blockingQueue.take();                    if(esBytes.length<1) {                        System.out.println("取流失败,无内容");                        continue;                    }                    ByteBuffer data = ByteBuffer.wrap(esBytes);                    session.getBasicRemote().sendBinary(data);                } catch (InterruptedException e) {                    System.out.println("socket 数据发失败,错误信息为:"+e.getMessage());                    return;                } catch (IOException e) {                    System.out.println("socket 数据发失败,错误信息为:"+e.getMessage());                    return;                }            }        }        else        {            System.out.println("当前没有找到用户登录句柄,无法播放:"+lUserID);        }    }        @OnClose    public void onClose(final Session session) {        onlineCount.decrementAndGet(); // 在线数减1        System.out.println(String.format("socket[%s]断开链接,查找并执行退出预览&登录",session.getId()));        //执行退出操作       if(MyBlockingQueue.SessionToUserIdMap.containsKey(session.getId()))       {           String userId = MyBlockingQueue.SessionToUserIdMap.get(session.getId());           if(null!=userId)           {               System.out.println(String.format("找到正在登录id[%s]预览的的相关信息,执行停止预览并退出登录操作",userId));               PreverViewUntil.logoutPlayView(userId);//执行退出预览操作           }       }       else       {           System.out.println(String.format("没有找到该socket相关的登录预览信息,无需操作!"));       }    }        @OnMessage    public void onMessage(final String message, final Session session) {        log.info("服务端收到客户端[{}]的消息:{}", session.getId(), message);    }    @OnError    public void onError(final Session session, final Throwable error) {        System.out.println(String.format("socket[%s]发生错误,查找并执行退出预览&登录,错误消息是:"+error.getMessage(),session.getId()));        //执行退出操作        if(MyBlockingQueue.SessionToUserIdMap.containsKey(session.getId()))        {            String userId = MyBlockingQueue.SessionToUserIdMap.get(session.getId());            if(null!=userId)            {                System.out.println(String.format("找到正在登录id[%s]预览的的相关信息,执行停止预览并退出登录操作",userId));                PreverViewUntil.logoutPlayView(userId);//执行退出预览操作            }        }        else        {            System.out.println(String.format("没有找到该socket相关的登录预览信息,无需操作!"));        }    }        private void sendMessage(final String message, final Session toSession) {        try {            log.info("服务端给客户端[{}]发送消息{}", toSession.getId(), message);            toSession.getBasicRemote().sendText(message);        } catch (Exception e) {            log.error("服务端发送消息给客户端失败:{}", e);        }    }}
package com.xunshi.hikangvision.vo;import java.util.HashMap;import java.util.Map;import java.util.concurrent.BlockingQueue;import java.util.concurrent.atomic.AtomicReference;public class MyBlockingQueue {//    static public BlockingQueue bq = new ArrayBlockingQueue<>(10);//    public  String socketSessionId;   //前端和这个摄像头建立的websocketid//    public  String lUserID="-1";  //摄像头登录句柄//    public  String lPlayID="-1";    //摄像预览句柄    static public Map SessionToUserIdMap=new HashMap<>();    static public Map PlayToUserIdMap=new HashMap<>();    static public Map IPToUserIdMap=new HashMap<>();    static public Map bpMap=new HashMap<>();        static public void clearByUserId(String userId,boolean logout)    {        if(null==MyBlockingQueue.bpMap)        {            System.out.println("bpMap is null ");            MyBlockingQueue.bpMap=new HashMap<>();        }        if(null==MyBlockingQueue.PlayToUserIdMap)        {            System.out.println("PlayToUserIdMap is null ");            MyBlockingQueue.PlayToUserIdMap=new HashMap<>();        }        if(null==MyBlockingQueue.SessionToUserIdMap)        {            System.out.println("SessionToUserIdMap is null ");            MyBlockingQueue.SessionToUserIdMap=new HashMap<>();        }        for (String key : MyBlockingQueue.PlayToUserIdMap.keySet())        {            String value = MyBlockingQueue.PlayToUserIdMap.get(key);            if(value.equals(userId))            {                MyBlockingQueue.PlayToUserIdMap.remove(key);                break;            }        }        for (String key : MyBlockingQueue.SessionToUserIdMap.keySet())        {            String value = MyBlockingQueue.SessionToUserIdMap.get(key);            if(value.equals(userId))            {                MyBlockingQueue.SessionToUserIdMap.remove(key);                break;            }        }        if(logout)        {            for (String key : MyBlockingQueue.IPToUserIdMap.keySet())            {                String value = MyBlockingQueue.IPToUserIdMap.get(key);                if(value.equals(userId))                {                    MyBlockingQueue.IPToUserIdMap.remove(key);                    break;                }            }        }    }        static public String findUserIdByIp(String Ip)    {        for (String key : MyBlockingQueue.IPToUserIdMap.keySet())        {            String value = MyBlockingQueue.IPToUserIdMap.get(key);            if(key.equals(Ip))            {                return value;            }        }        return null;    }        static public String findPlayIdByUserId(String userId)    {        for (String key : MyBlockingQueue.PlayToUserIdMap.keySet())        {            String value = MyBlockingQueue.PlayToUserIdMap.get(key);            if(value.equals(userId))            {                return key;            }        }        return null;    }}
package com.xunshi.hikangvision.vo.result;import lombok.AllArgsConstructor;import lombok.Getter;import lombok.Setter;import lombok.ToString;import lombok.experimental.Accessors;import org.springframework.http.HttpStatus;import org.springframework.util.ObjectUtils;import java.io.Serializable;@ToString@Accessors(chain = true)@AllArgsConstructorpublic class R implements Serializable {    private static final long serialVersionUID = 1L;    @Getter    @Setter    private int code = CommonConstants.SUCCESS;    @Getter    @Setter    private HttpStatus httpStatus;    @Getter    @Setter    private T data;    private String[] messages = {};    public R() {        super();    }    public R(T data) {        super();        this.data = data;    }    public R(String... msg) {        super();        this.messages = msg;    }    public R(T data, String... msg) {        super();        this.data = data;        this.messages = msg;    }    public R(T data, int code, String... msg) {        super();        this.data = data;        this.code = code;        this.messages = msg;    }    public R(Throwable e) {        super();        setMessage(e.getMessage());        this.code = CommonConstants.FAIL;    }    public static R buildOk(String... messages) {        return new R(messages);    }    public static  R buildOkData(T data, String... messages) {        return new R(data, messages);    }    public static  R buildFailData(T data, String... messages) {        return new R(data, CommonConstants.FAIL, messages);    }    public static  R buildFail(String... messages) {        return new R(null, CommonConstants.FAIL, messages);    }    public static  R build(T data, int code, String... messages) {        return new R(data, code, messages);    }    public static  R build(int code, String... messages) {        return new R(null, code, messages);    }    public String getMessage() {        return readMessages();    }    public void setMessage(String message) {        addMessage(message);    }    public String readMessages() {        StringBuilder sb = new StringBuilder();        for (String message : messages) {            sb.append(message);        }        return sb.toString();    }    public void addMessage(String message) {        this.messages = ObjectUtils.addObjectToArray(messages, message);    }}

3.2.前端逻辑流程


①:准备好摄像头参数:ip、端口、预览通道、用户名、密码,请求后端接口‘/playvision/login’实现登录,登陆成功后端会返回loginStatus(登录状态:0-失败1-成功)、lUserID(登陆摄像头的id)

②:请求预览接口'/playvision/playView',将登录后返回的数据直接请求过来即可。请求成功后会返回plsyStatus(预览状态:0-失败1-成功)、lPlayID(预览成功的通道句柄id)

③:预览接口请求成功后,我们调用wfs.js插件进行实时预览。

h.264 To fmp4