记录SpringBoot对接大华摄像头并能够实时预览

一、前言

如果本文章对你有帮助帮忙点个关注或小赞赞哦。

技术栈:SpringBoot、Websocket、Flv.js框架、大华SDK代码(设备网络SDK下载地址:

兼容性:PC端,H5(uniapp的app可用页面嵌套方式),不兼容IOS(IOS目前版本不支持FLV流)

二、配置流程

1.主动注册(可连接到公网服务器)

先说一下整体的工作流程,首先通过使用大华摄像头的主动注册功能填写我们的服务器IP(公网IP或局域网IP)和端口,点击设置后摄像头就能够主动连接服务器锁所听的端口,比如我这里服务器监听的端口就为9200,(以下配置面板需要下载大华SDK源码,并运行源码代码或源码打包的jar包)

2. 编码模式修改

需要将编码模式从h265改为h264否则无法实时查看,ConfigTool需要再大华官网中进行下载,野生网站也有,或者问大华客服也会给链接,我这里就懒得贴上了。

三、 源码部分(自己按需引入大华源码SDK)

 1.所需依赖

(1)websocket依赖

   <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-websocket</artifactId>
   </dependency>

2.后端代码部分

(1)创建监听StartListener类

        这个类主要是用来监听一个端口,让大华摄像头通过主动注册连接上来,这个也是大华提供给我们的SDK,必须使用这个,否则协议不一样会有乱码。我这里开放的9100端口,部署到公网startServer一定要是0.0.0.0否则无法访问,StartListener类这部分基本都是大华SDK里面源码需要的一些代码,觉得麻烦把整个大华SDK源码放入项目让它自动导入就行。

@Component
public class StartListener implements ApplicationListener<ContextRefreshedEvent> {

	public static final NetSDKLib netSdk = NetSDKLib.NETSDK_INSTANCE;

	// 主动注册监听回调
	private SgymCameraServiceImpl.ServiceCB servicCallback = new SgymCameraServiceImpl.ServiceCB();

	/**
	 * 注入启动器
	 */
	@Autowired
	private WebSocketNettyServer webSocketNettyServer;

	@Override
	public void onApplicationEvent(ContextRefreshedEvent event) {
		InitTest();
		AutoRegisterModule.startServer("0.0.0.0",
				Integer.parseInt("9100"),
				servicCallback);
	}

	public void InitTest(){
		// 初始化SDK库
		netSdk.CLIENT_Init(HaveReConnectCallBack.DisConnectCallBack.getInstance(), null);

		// 设置断线重连成功回调函数
		netSdk.CLIENT_SetAutoReconnect(HaveReConnectCallBack.getInstance(), null);
		//打开日志,可选
		NetSDKLib.LOG_SET_PRINT_INFO setLog = new NetSDKLib.LOG_SET_PRINT_INFO();
		String logPath = new File(".").getAbsoluteFile().getParent() + File.separator + "sdk_log" + File.separator + "sdk.log";
		setLog.bSetFilePath = 1;
		System.arraycopy(logPath.getBytes(), 0, setLog.szLogFilePath, 0, logPath.getBytes().length);
		setLog.bSetPrintStrategy = 1;
		setLog.nPrintStrategy = 0;
		if (!netSdk.CLIENT_LogOpen(setLog)){
			System.err.println("Open SDK Log Failed!!!");
		}
	}

	/**
	 * 设备重连回调
	 */
	private static class HaveReConnectCallBack implements NetSDKLib.fHaveReConnect {
		private HaveReConnectCallBack() {
		}

		private static class CallBackHolder {
			private static HaveReConnectCallBack instance = new HaveReConnectCallBack();
		}

		public static HaveReConnectCallBack getInstance() {
			return HaveReConnectCallBack.CallBackHolder.instance;
		}

		public void invoke(NetSDKLib.LLong m_hLoginHandle, String pchDVRIP, int nDVRPort, Pointer dwUser) {
			System.out.printf("ReConnect Device[%s] Port[%d]\n", pchDVRIP, nDVRPort);

		}

		private static class DisConnectCallBack implements NetSDKLib.fDisConnect {

			private DisConnectCallBack() {
			}

			private static class CallBackHolder {
				private static HaveReConnectCallBack.DisConnectCallBack instance = new HaveReConnectCallBack.DisConnectCallBack();
			}

			public static HaveReConnectCallBack.DisConnectCallBack getInstance() {
				return HaveReConnectCallBack.DisConnectCallBack.CallBackHolder.instance;
			}

			public void invoke(NetSDKLib.LLong lLoginID, String pchDVRIP, int nDVRPort, Pointer dwUser) {
				System.out.printf("Device[%s] Port[%d] DisConnect!\n", pchDVRIP, nDVRPort);
			}
		}

	}
}

(2)新建业务处理类CameraServiceImpl

        具体推送到websocket我就不做示范了,具体根据自己业务来写,摄像头注册上来需要一个map去存储摄像头的一些信息,像登录句柄,预览句柄都要做好关联关系。

@Slf4j
@Service
public class SgymCameraServiceImpl {


    /**
     * 开始预览
     * @param loginHandle
     * @param deviceId
     */
    public void realplay(NetSDKLib.LLong loginHandle,String deviceId) {
        NetSDKLib.NET_IN_REALPLAY_BY_DATA_TYPE inParam = new NetSDKLib.NET_IN_REALPLAY_BY_DATA_TYPE();
        NetSDKLib.NET_OUT_REALPLAY_BY_DATA_TYPE outParam = new NetSDKLib.NET_OUT_REALPLAY_BY_DATA_TYPE();
        inParam.nChannelID = 0;
        inParam.rType = 0;
        inParam.emDataType = 5;
//        inParam.szSaveFileName = "d:/123.mp4";
        NetSDKLib.LLong lRealHandle = netsdk.CLIENT_RealPlayByDataType(loginHandle,
                inParam, outParam, 20000);
        log.info("设备:{},进入开始预览",deviceId);
        log.info("lRealHandle---->{},----------->{},-------------->{}",lRealHandle,loginHandle,lRealHandle);
        if(lRealHandle.longValue()!=0){
            System.out.println("realplay success");
            netSdk.CLIENT_SetRealDataCallBackEx(lRealHandle, CbfRealDataCallBackEx.getInstance(),null, 31);
        }

    }

    /**
     * \if ENGLISH_LANG
     * Start RealPlay
     * \else
     * 停止预览
     * \endif
     */
    public synchronized static void stopRealPlay(NetSDKLib.LLong rawLRealHandle,String deviceId) {
        if(Objects.isNull(rawLRealHandle)) {
            return;
        }
        boolean bRet = LoginModule.netsdk.CLIENT_StopRealPlay(rawLRealHandle);
        if(bRet) {
            log.info("设备:{}已停止预览",deviceId);
        }
    }
    /**
     * 实时预览数据回调函数--扩展(pBuffer内存由SDK内部申请释放)
     */
    private static class CbfRealDataCallBackEx implements NetSDKLib.fRealDataCallBackEx {
        private CbfRealDataCallBackEx() {
        }

        private static class CallBackHolder {
            private static CbfRealDataCallBackEx instance = new CbfRealDataCallBackEx();
        }

        public static CbfRealDataCallBackEx getInstance() {
            return CbfRealDataCallBackEx.CallBackHolder.instance;
        }

        @SneakyThrows
        @Override
        public void invoke(NetSDKLib.LLong lRealHandle, int dwDataType, Pointer pBuffer,
                           int dwBufSize, int param, Pointer dwUser) {
            byte[] bytes = pBuffer.getByteArray(0, dwBufSize);
            if ((dwDataType-1000) == 5) {//回调格式为flv的流
                ByteBuffer buffer = ByteBuffer.wrap(bytes);
                log.info("flv流-----> {}",buffer);
                //这里将流通过websocket推送到前端
            }
          /*  if(0 != lRealHandle.longValue())
            {
                switch(dwDataType) {
                    case 0:
                    *//*    ByteBuffer byteBufferFromStream = ByteBuffer.wrap(bytes);
                        staticWebSocket.sendMessageToOne(0,bytes);*//*
                        break;
                    case 1:
                        //标准视频数据
                        break;
                    case 2:
                        //yuv 数据

                        break;
                    case 3:
                        //pcm 音频数据

                        break;
                    case 4:
                        //原始音频数据

                        break;
                    default:
                        break;
                }
            }*/
        }
    }

    /**
     * 侦听服务器回调函数
     */
    public static class ServiceCB implements NetSDKLib.fServiceCallBack {
        @Override
        public int invoke(NetSDKLib.LLong lHandle, final String pIp, final int wPort,
                          int lCommand, Pointer pParam, int dwParamLen,
                          Pointer dwUserData) {

            // 将 pParam 转化为序列号
            byte[] buffer = new byte[dwParamLen];
            pParam.read(0, buffer, 0, dwParamLen);
            String deviceId = "";
            try {
                deviceId = new String(buffer, "GBK").trim();
            } catch (UnsupportedEncodingException e) {
                e.printStackTrace();
            }

            System.out.printf("Register Device Info [Device address %s][port %s][DeviceID %s] \n", pIp, wPort, deviceId);
            switch (lCommand) {
                case NetSDKLib.EM_LISTEN_TYPE.NET_DVR_DISCONNECT: {  // 验证期间设备断线回调
                    log.info("进入验证期间设备断线回调");
                    break;
                }
                case NetSDKLib.EM_LISTEN_TYPE.NET_DVR_SERIAL_RETURN: { // 设备注册携带序列号
                    log.info("进入设备注册携带序列号--->{}",deviceId);

                    NetSDKLib.LLong loginLLongId = login(pIp, wPort, "admin", "123456", deviceId);

                    break;
                }
                default:
                    break;
            }
            return 0;
        }
    }
}

(3) WebSocket类

        具体怎么写根据你们业务来,但是要做好session与设备id或需要查看监控的用户id之间的关联关系

@Slf4j
@Component
@ServerEndpoint("/websocket/{userId}/{deviceId}")
public class WebSocket {

/*    @Autowired
    private CameraPreviewSession cameraPreviewSession;*/

    private static CameraPreviewSession cameraPreviewSession;



    @Autowired
    public void setDeviceListenerService(CameraPreviewSession cameraPreviewSession) {
        WebSocket.cameraPreviewSession = cameraPreviewSession;
    }


    /**
     * 线程安全的无序的集合
     */
    private static final CopyOnWriteArraySet<Session> SESSIONS = new CopyOnWriteArraySet<>();

    /**
     * 存储在线连接数
     */
    public static final Map<String, Session> SESSION_POOL = new HashMap<>();

    public static final Map<Session, String> USER_SESSION_POOL = new HashMap<>();

    public static final Map<Session, String> DEVICE_SESSION_POOL = new HashMap<>();

    @OnOpen
    public void onOpen(Session session, @PathParam(value = "userId") String userId,@PathParam(value = "deviceId") String deviceId) {
        try {
            SESSIONS.add(session);
            SESSION_POOL.put(userId +"-"+ deviceId, session);
            USER_SESSION_POOL.put(session,userId +"-"+ deviceId);
            DEVICE_SESSION_POOL.put(session,deviceId);
            log.info("【WebSocket消息】有新的连接,总数为:" + SESSIONS.size());
            cameraPreviewSession.setCameraPreviewSession(deviceId, userId +"-"+ deviceId);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    @OnClose
    public void onClose(Session session) {
        try {
            SESSIONS.remove(session);
            String userId = USER_SESSION_POOL.get(session);
            SESSION_POOL.remove(userId);
            String deviceId = DEVICE_SESSION_POOL.get(session);
            int size = cameraPreviewSession.removeDeviceCameraPreviewUserId(userId, deviceId);
            USER_SESSION_POOL.remove(session);
            DEVICE_SESSION_POOL.remove(session);
            Set<NetSDKLib.LLong> longList = cameraPreviewSession.byUserGetLRealHandle(userId);
            if (CollectionUtil.isNotEmpty(longList)){
                longList.forEach(item->{
                    if (Objects.nonNull(item)){
                        SgymCameraServiceImpl.stopRealPlay(item);
                    }

                });
            }
            cameraPreviewSession.byUserDeleteRealHandle(userId);
            if (size<=0){
                cameraPreviewSession.removeDeviceCameraPreview(deviceId);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    @OnMessage
    public void onMessage(String message) {
        log.info("【WebSocket消息】收到客户端消息:" + message);
    }

    /**
     * 此为广播消息
     *
     * @param message 消息
     */
    public void sendAllMessage(String message) {
        log.info("【WebSocket消息】广播消息:" + message);
        for (Session session : SESSIONS) {
            try {
                if (session.isOpen()) {
                    session.getAsyncRemote().sendText(message);
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * 此为单点消息
     *
     * @param userId  用户编号
     * @param message 消息
     */
    public void sendOneMessage(String userId, String message) {
        Session session = SESSION_POOL.get(userId);
        if (session != null && session.isOpen()) {
            try {
                synchronized (session) {
                    log.info("【WebSocket消息】单点消息:" + message);
                    session.getAsyncRemote().sendText(JSON.toJSONString(message));
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * 此为单点消息(多人)
     *
     * @param userIds 用户编号列表
     * @param message 消息
     */
    public void sendMoreMessage(String[] userIds, String message) {
        for (String userId : userIds) {
            Session session = SESSION_POOL.get(userId);
            if (session != null && session.isOpen()) {
                try {
                    log.info("【WebSocket消息】单点消息:" + message);
                    session.getAsyncRemote().sendText(message);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
    }


    /**
     * 实现服务器主动推送
     * @param userId 播放拉流的回调句柄
     * @param buffer  发送的数据
     */
    public void sendMessageToOne(Long userId, ByteBuffer buffer) throws IOException {

        // 使用Base64编码器将byte数组转换为Base64字符串

        Session session = SESSION_POOL.get(userId.toString());

        if (Objects.nonNull(session)){
            session.getBasicRemote().sendBinary(buffer);
        }

    }

    public void sendMessageToMulti(String userId, ByteBuffer buffer) throws IOException {
        System.out.println("userId----->"+userId);
        Session session = SESSION_POOL.get(userId);
        if (Objects.nonNull(session)){
            try {
                synchronized(SESSIONS) {
                    session.getBasicRemote().sendBinary(buffer);
                }
            }catch (Exception e){
                e.printStackTrace();
                session.close();
            }
        }
    }
}

(4)CameraPreviewSession类

public interface CameraPreviewSession {


    void setCameraPreviewSession(String deviceId,String userId);

    void setCameraPreviewSession(String deviceId, List<String> userId);

    void setLRealHandleDeviceSession(String deviceId, NetSDKLib.LLong lRealHandle);

    String byLRealHandleGetDeviceId(NetSDKLib.LLong lRealHandle);

    NetSDKLib.LLong byDeviceIdGetLRealHandle(String deviceId);

    Set<String> byDeviceIdGetUserIds(String deviceId);

    void removeDeviceCameraPreview(String deviceId);

    void removeDeviceRealHandle(String deviceId);

    void removeLRealHandleDevice(NetSDKLib.LLong lRealHandle);

    int removeDeviceCameraPreviewUserId(String userId, String deviceId);

    void setLRealHandleUserMap(NetSDKLib.LLong lRealHandle,String userId);

    String  byLRealHandleGetUser(NetSDKLib.LLong lRealHandle);

    void byUserDeleteRealHandle(String userId);

    Set<NetSDKLib.LLong> byUserGetLRealHandle(String userId);
}

 (4)CameraPreviewSessionImpl类

@Service
public class CameraPreviewSessionImpl implements CameraPreviewSession {

    private static Map<String, Set<String>> previewSessionKeyMap = new HashMap<>();

    private static Map<NetSDKLib.LLong, String> lRealHandleUserMap = new HashMap<>();

    private static Map<String,Set<NetSDKLib.LLong>> userLRealHandleMap = new HashMap<>();

    private static Map<NetSDKLib.LLong, String> lRealHandleDeviceMap = new HashMap<>();

    private static Map<String,NetSDKLib.LLong> deviceRealHandleMap = new HashMap<>();

    @Override
    public void setCameraPreviewSession(String deviceId, String userId) {
        Set<String> rawUserIds = previewSessionKeyMap.get(deviceId);
        if (CollectionUtil.isNotEmpty(rawUserIds)){
            rawUserIds.add(userId);
            previewSessionKeyMap.put(deviceId,rawUserIds);
        }else{
            Set<String> userIds = new HashSet<>();
            userIds.add(userId);
            previewSessionKeyMap.put(deviceId,userIds);
        }
    }

    @Override
    public void setCameraPreviewSession(String deviceId, List<String> userId) {
        Set<String> rawUserIds = previewSessionKeyMap.get(deviceId);
        if (CollectionUtil.isNotEmpty(rawUserIds)){
            rawUserIds.addAll(userId);
            previewSessionKeyMap.put(deviceId,rawUserIds);
        }else{
            Set<String> userIds = new HashSet<>(userId);
            previewSessionKeyMap.put(deviceId,userIds);
        }
    }

    @Override
    public void setLRealHandleDeviceSession(String deviceId, NetSDKLib.LLong lRealHandle) {
        lRealHandleDeviceMap.put(lRealHandle,deviceId);
        deviceRealHandleMap.put(deviceId,lRealHandle);
    }

    @Override
    public String byLRealHandleGetDeviceId(NetSDKLib.LLong lRealHandle) {
        return lRealHandleDeviceMap.get(lRealHandle);
    }

    @Override
    public NetSDKLib.LLong byDeviceIdGetLRealHandle(String deviceId) {
        return deviceRealHandleMap.get(deviceId);
    }

    @Override
    public Set<String> byDeviceIdGetUserIds(String deviceId) {
        Set<String> userIds = previewSessionKeyMap.get(deviceId);
        if (CollectionUtil.isEmpty(userIds)){
            return new HashSet<>();
        }
        return userIds;
    }


    @Override
    public void removeDeviceCameraPreview(String deviceId) {
        previewSessionKeyMap.remove(deviceId);
    }


    @Override
    public void removeDeviceRealHandle(String deviceId) {
        deviceRealHandleMap.remove(deviceId);
    }

    @Override
    public void removeLRealHandleDevice(NetSDKLib.LLong lRealHandle) {
        lRealHandleDeviceMap.remove(lRealHandle);
    }

    @Override
    public int removeDeviceCameraPreviewUserId(String userId, String deviceId) {
        Set<String> userIds = previewSessionKeyMap.get(deviceId);
        if (CollectionUtils.isNotEmpty(userIds)){
            userIds.remove(userId);
            if (CollectionUtils.isNotEmpty(userIds)){
                previewSessionKeyMap.put(deviceId,userIds);
            }
            return Math.max((userIds.size() - 1), 0);
        }
      return 0;
    }

    @Override
    public void setLRealHandleUserMap(NetSDKLib.LLong lRealHandle, String userId) {
        lRealHandleUserMap.put(lRealHandle,userId);
        Set<NetSDKLib.LLong> rawLRealHandles = userLRealHandleMap.get(userId);
        Set<NetSDKLib.LLong> newList = new HashSet<>();
        newList.add(lRealHandle);
        if (rawLRealHandles!=null){
            newList.addAll(rawLRealHandles);
        }

        userLRealHandleMap.put(userId,newList);
    }

    @Override
    public String byLRealHandleGetUser(NetSDKLib.LLong lRealHandle) {
        return lRealHandleUserMap.get(lRealHandle);
    }

    @Override
    public void byUserDeleteRealHandle(String userId) {
        Set<NetSDKLib.LLong> lLongList = userLRealHandleMap.get(userId);
        lLongList.forEach(item->{
            lRealHandleUserMap.remove(item);
        });
        userLRealHandleMap.remove(userId);
    }

    @Override
    public Set<NetSDKLib.LLong> byUserGetLRealHandle(String userId) {
        return userLRealHandleMap.get(userId);
    }
}

3.前端代码部分 

 导入flv.js

npm install flv.js

(1)代码部分

<template>
    <div>
        <video
                controls
                type="rtmp/flv"
                style="width: 100%; min-height: 160.31px"
                @click="handleClick"
        ></video>
        <button>开始预览</button>
    </div>
</template>

<script>
    import flvjs from 'flv.js'
    export default {
        data() {
            return {
                flvPlayerList:null,
            }
        },
        computed: {
         
        },
        methods: {
            // 创建播放实例(我这里用户id和设备id都是1)
            handleClick() {
                if (flvjs.isSupported()) {
                    this.flvPlayerList = flvjs.createPlayer(
                        {
                            type: 'flv',
                            //192.168.31.48:10003填写自己的ip和端口
                            url: `ws://192.168.31.48:10003/websocket/1/1`,
                            isLive: true, //数据源是否为直播流
                            hasAudio: false, //数据源是否包含有音频
                            hasVideo: true, //数据源是否包含有视频
                            enableStashBuffer: false //是否启用缓存区
                        },
                        {
                            enableWorker: false, //不启用分离线程
                            enableStashBuffer: false, //关闭IO隐藏缓冲区
                            autoCleanupSourceBuffer: true //自动清除缓存
                        }
                    )
                 
                }
            },
        }
    }
</script>

<style scoped lang="scss">
    
</style>