java获取大华摄像头视频流实现推流直播java获取大华视

java获取大华视频流实现推流直播

前言

​ 目前摄像头直播的方案主要有通过有通过rtsp方式、国标协议、调用厂家sdk获取视频流等方式。大华sdk支持摄像头主动注册的方式对接服务器,通过这种方式,可以实现远程获取视频流并对摄像头进行控制,通过推流到流媒体服务器可以通过flv、wsflv、hls等主流流媒体协议在手机以及网页进行视频的实时查看与控制。

​ 感谢banmajio的文章对我的启发(blog.csdn.net/weixin_4077…

一、使用技术栈

springboot+javaCV+jna+nms

二、实现步骤

2.1 官网下载大华netSDK

www.dahuatech.com/service/dow…

我主要使用的是java win64位版本

2.2 流媒体服务器部署

github.com/illuspas/No…

我选用的是Node-Media-Server开源版本,使用docker部署十分方便

window10可以安装一个docker desktop进行部署

docker run --name nms -d -p 1935:1935 -p 1934:8000 illuspas/node-media-server
复制代码

部署完成后可以登录管理页面进行查看http://localhost:1934/admin/streams,默认用户名密码都是admin

1935端口为rtmp端口,也就是推流的端口,1934为拉流地址

2.3 下载播放器

www.videolan.org/ 下载vlc播放器备用

2.4 项目搭建

新建一个spring boot项目

2.4.1 引入大华net SDK

按照官网demo引入大华net sdk,将win64库文件放到resources目录下(其他环境的库按需要添加),下面提供一种通用的加载dll文件的方法

NetSDKLib NETSDK_INSTANCE = (NetSDKLib)NetSDKLib.LoadHelper.loadDll("dhnetsdk", NetSDKLib.class);

	NetSDKLib CONFIG_INSTANCE = (NetSDKLib)NetSDKLib.LoadHelper.loadDll("dhconfigsdk", NetSDKLib.class);

    @Slf4j
    public static class LoadHelper {
        public synchronized static Object loadDll(String libName, Class<?> className) {
            String libExtension = ".dll", systemType = "win";
            String libFullName = libName + libExtension;

            if (Platform.getOSType() == Platform.LINUX) {
                    libExtension = ".so";
                    systemType = "linux";
                    libFullName = "lib" + libName + libExtension;
            }

            String nativeTempDir = System.getProperty("java.io.tmpdir");

            InputStream in = null;
            BufferedInputStream reader = null;
            FileOutputStream writer = null;
            File extractedLibFile = new File(nativeTempDir + File.separator + libFullName);

            if (!extractedLibFile.exists()) {
                try {
                    systemType += System.getProperty("sun.arch.data.model");
                    Resource resource = new ClassPathResource("/libs/"+ systemType + "/" + libFullName);
                    in = resource.getInputStream();

                    if (in == null){
                        return null;
                    }
                    reader = new BufferedInputStream(in);
                    writer = new FileOutputStream(extractedLibFile);
                    byte[] buffer = new byte[1024];
                    while (reader.read(buffer) > 0) {
                        writer.write(buffer);    //将 dll/so/dylib 写入临时文件中
                        buffer = new byte[1024];
                    }
                } catch (IOException e) {
                    log.error(e.getMessage());
                } finally {
                    if (in != null) try {
                        in.close();
                    } catch (IOException e) {
                        log.error(e.getMessage());
                    }
                    if (writer != null) try {
                        writer.close();
                    } catch (IOException e) {
                        log.error(e.getMessage());
                    }
                }
            }
            String temp = extractedLibFile.toString();
            String dllName = temp.substring(0,temp.indexOf("."));
            if (Platform.getOSType() == Platform.LINUX) {
                dllName = temp;
            }
            return Native.loadLibrary(dllName,className);
        }
    }
复制代码

大华提供的ToolKits已经包含了丰富的摄像头操作方法,我们可以在自身业务的基础上进一步封装,或者基于大华c版的《网络SDK开发手册.chm》自己通过jna的方式添加官方没有提供的方法使用。

2.4.2 主动注册

实现主动注册的话,服务端需要实现netsdk提供的fServiceCallBack接口,然后将实现类与设备断线重连的回调类注册到netsdk,绑定本机的9500端口为设备主动注册的端口。

 @EventListener
    public void initNetSdk(ContextRefreshedEvent event) {
        if (Platform.getOSType() == Platform.WINDOWS) {
            try {
                //初始化SDK库
                netSdk.CLIENT_Init(DisConnectCallBack.getInstance(), null);
                //设置断线重连成功回调函数
                netSdk.CLIENT_SetAutoReconnect(HaveReConnectCallBack.getInstance(), null);
                //开启主动注册服务
                ServiceCB servicCallback = ServiceCB.getInstance();
                netSdk.CLIENT_ListenServer(autoRegIp, 9500, 1000, servicCallback, null);
            } catch (Exception e) {
                log.error(e.getMessage());
                log.error(this.netSdk.CLIENT_GetLastError()+"");
            }
        }
    }
复制代码

登录大华摄像头配置页面,配置摄像头开启主动注册并配置主动注册地址(ip为本机地址,通过ipconfig查看),设备序列号(保证多台不重复)

在实现类的invoke方法中,判断lCommand为EM_LISTEN_TYPE.NET_DVR_SERIAL_RETURN,则为设备主动注册携带序列号,下面是设备主动注册携带的信息打印。

ERROR com.zb.video.netsdk.event.ServiceCB:54 - 1 Register Device Info [Device address 192.168.1.200][port 62801][DeviceID 6F06F7DPAJ3C924][lCommand 1]!
复制代码

然后通过摄像头的序列号、登录名、密码进行登录,主要调用netSdk.CLIENT_LoginWithHighLevelSecurity方法进行登录。

NetSDKLib.NET_DEVICEINFO_Ex deviceInfo = new NetSDKLib.NET_DEVICEINFO_Ex();
Pointer deviceId = ToolKits.GetGBKStringToPointer(serNum);
//入参
NetSDKLib.NET_IN_LOGIN_WITH_HIGHLEVEL_SECURITY pstInParam=new NetSDKLib.NET_IN_LOGIN_WITH_HIGHLEVEL_SECURITY();
pstInParam.nPort=vsDeviceLoginInfo.getPort();
pstInParam.szIP=vsDeviceLoginInfo.getIp().getBytes();
pstInParam.szPassword=vsDeviceLoginInfo.getPassword().getBytes();
pstInParam.szUserName=vsDeviceLoginInfo.getUser().getBytes();
pstInParam.emSpecCap = 2;// 主动注册方式
pstInParam.pCapParam=deviceId;
//出参
NetSDKLib.NET_OUT_LOGIN_WITH_HIGHLEVEL_SECURITY pstOutParam=new NetSDKLib.NET_OUT_LOGIN_WITH_HIGHLEVEL_SECURITY();
pstOutParam.stuDeviceInfo=deviceInfo;
//登录句柄
LLong loginHandle = netSdk.CLIENT_LoginWithHighLevelSecurity(pstInParam, pstOutParam);
复制代码

获取到的登录句柄不为0则为登录成功,否则需要调用ErrorCode.getErrorCode(NetSDKConfig.netSdk.CLIENT_GetLastError())获取登录失败的原因,登录成功后可以通过登录句柄调用其他接口对设备进行各种操作。

2.4.3 开启预览并获取大华视频流并推送

登录成功后,可以通过http接口对视频流的开启和关闭进行控制,开启视频流包含调用netsdk实时预览接口,在视频流回调中对大华视频流进行去封装处理得到H264裸码流,通过Java的管道流,将H264裸码流写入PipedOutputStream中,然后将对应的PipedInputStream当做参数传入到javacv的FFmpegFrameGrabber的构造方法中,通过javacv推流到nms的1935端口。对JAVACV不了解的可以网上查看相应的资料,本人也是通过查阅资料,与博主交流实现的。

管道流PipedInputStream,PipedOutputStream成对出现,需要将两者建立连接才能正常工作。建立连接有以下两种方式:

//第一种方式
PipedInputStream inputStream = new PipedInputStream();
PipedOutputStream outputStream = new PipedOutputStream(inputStream);

//第二种方式
PipedInputStream inputStream = new PipedInputStream();
PipedOutputStream outputStream = new PipedOutputStream();
inputStream.connect(outputStream);
复制代码

开始推流接口要注意判断当前摄像头之前是否已经开始推流,已经开启的直接获取之前的流地址返回就行。没有开启的则首先调用大华netsdk接口获取视频流:

 public void startRealplay(LLong loginHandle,Integer playSign,CameraPojo cameraPojo){
 		//默认为辅码流
        int streamType = 3;
        if(StringUtils.equals(cameraPojo.getStream(),"main")){
        	//主码流
            streamType=0;
        }
        //开启预览
        LLong lRealHandle= NetSDKConfig.netSdk.CLIENT_RealPlayEx(loginHandle, cameraPojo.getChannel(), null, streamType);
      
        if(lRealHandle.longValue()!=0){
            try {     
//设置视频流回调            if(NetSDKConfig.netSdk.CLIENT_SetRealDataCallBackEx(lRealHandle,RealDataCallBack.getInstance(),null, 31)){
                }
            } catch (IOException e) {
                log.error(e.getMessage());
            }
        }
    }
复制代码

在视频流回调中需要对大华视频流去大华头封装处理,得到H264裸码流,可以向官网获取解码库或者网上查询相应的办法。将处理后的裸码流放入到PipedOutputStream中,然后调用javacv的转封装处理(主要用到FFmpegFrameGrabber、FFmpegFrameRecorder两个类),从PipedInputStream获取视频流将h264格式的视频封装成flv格式,并推送到nms的1935端口,中间代码太多就不一一贴了,主要给大家提供一个思路。

maven引入javacv:

<dependency>
    <groupId>org.bytedeco</groupId>
    <artifactId>javacv</artifactId>
    <version>1.5.3</version>
</dependency>
<dependency>
    <groupId>org.bytedeco</groupId>
    <artifactId>ffmpeg</artifactId>
    <version>4.2.2-1.5.3</version>
</dependency>
<dependency>
    <groupId>org.bytedeco</groupId>
    <artifactId>ffmpeg</artifactId>
    <version>4.2.2-1.5.3</version>
    <classifier>windows-x86_64</classifier>
    //linux环境下使用
    <!--<classifier>linux-x86_64</classifier>-->
</dependency>
复制代码

2.5 实现效果

配制好摄像头,部署好nms之后,运行项目,等待摄像头主动注册上来


swagger调用实现的开启视频流的接口,将主码流的视频推送到nms

将推送成功后的flv地址复制,在vlc播放网络串流查看

同时也可以在其他流媒体播放器上播放,页面可以使用b站的flv.js等播放。可以在nms的管理页面也可以看到这一视频流。

三、小结

本文提供了一种通过大华netsdk实现大华摄像头实时直播的基本思路,如果需要云端录制功能,也可以基于javacv进行开发。另外可以通过javacv定时截图结合百度人工智能api实现一些简单的视频分析。大家可以基于这种思路,结合javacv对通过netsdk得到的视频流进行更多的处理。