海康摄像头监控视频播放详解

您所在的位置:网站首页 监控怎么正常播放 海康摄像头监控视频播放详解

海康摄像头监控视频播放详解

2024-06-03 13:18:03| 来源: 网络整理| 查看: 265

2019.12.09 更新(重要!!!)

一,此博文及对应代码写于2018年初,基于海康SDK V5.3.3.2版本(当时最新版本),只适用于2019年前海康监控设备;(海康监控产品更新换代,旧版SDK不再适用于新产品。故老款设备监控视频可正常查看,新款无法查看)

二,近期已重新开发此功能,已兼容新/旧款设备,支持单路/多路实时监控,以及单路历史监控视频回放,且采用不同方案实现。详情如下:

单路视频预览(使用xml布局SurfaceView控件)单路视频预览(动态new View 添加到 ViewGroup)多路视频预览(动态new View 添加到 ViewGroup)多路视频预览(使用RecyclerView)单路视频回放(选择时间段)多路视频预览集成示例-在指定ViewGroup显示(动态new View 添加到 ViewGroup)

部分效果图如下:

 

由于某些原因不便开源,有问题的朋友可私聊交流;

 

 本篇文章讲述的是基于海康SDK播放监控视频,包含以下几个方面:

播放海康视频用到的API详解;播放海康视频实践过程详解;播放海康视频Cordova插件封装;

github地址:https://github.com/fangxiaopeng/fxp-plugin-video,包含完整代码及详细注释,欢迎star or fork

 

最近项目上有海康监控视频播放需求,包括Android 和 IOS两个版本。今天讲讲Android上的实现过程,后面有空了再讲讲IOS。

 

思路:基于海康视频SDK,使用AsyncTask登录设备后,获取实时流音视频数据,解码显示到SurfaceView。通过startActivityForResult的resultCode设置回调。

 

以下为工程文件目录结构及效果截图:

 

 

下面分阶段讲监控视频播放过程及需要用到的API。

1,登录设备

调用以下方法登录设备:

public int NET_DVR_Login_V30(String sDvrIp, int iDvrPort, ava.lang.String sUserName, String sPassword, NET_DVR_DEVICEINFO_V30 DeviceInfo)

参数说明:

[in] sDvrIp 设备 IP 地址或静态域名 [in] iDvrPort 设备端口号 [in] sUserName 登录的用户名 [in] sPassword 用户密码 [out] DeviceInfo 设备信息

返回值:

 -1表示失败,其他值表示返回的用户ID值。该用户ID具有唯一性,后续对设备的操作都需要通过此ID实现。

 

这里需要讲下用于获取设备信息的NET_DVR_Login_V30类。

 新建NET_DVR_Login_V30类对象,作为登录设备的一个参数传入,登录成功后即可返回设备详细信息,包含以下信息:

sSerialNumber 设备序列号 byAlarmInPortNum 报警输入个数 byAlarmOutPortNum 报警输出个数 byDiskNum 硬盘个数 byDVRType 设备类型 byChanNum 设备模拟通道个数 byStartChan 模拟通道起始通道号 byAudioChanNum 设备语音通道数 byIPChanNum 设备最大数字通道个数,低 8 位 byZeroChanNum 零通道个数 wDevType 设备类型 byStartDChan 起始数字通道号 byHighDChanNum 数字通道个数,高 8 位

 

2,获取实时流音视频数据

 登录设备成功后,调用NET_DVR_RealPlay_V40  方法获取实时流音视频数据。

public int NET_DVR_RealPlay_V40(int lUserID, NET_DVR_PREVIEWINFO previewInfo, RealPlayCallBack CallBack)

参数说明:

[in] lUserID NET_DVR_Login_V30 的返回值 [in] previewInfo 预览参数,包括码流类型、取流协议、通道号等 [in] CallBack 码流数据回调函数

返回值:

-1 表示失败,其他值作为 NET_DVR_StopRealPlay 等函数的句柄参数。

这里需要重点讲下以下两个类:

(1)NET_DVR_PREVIEWINFO:用于设置预览参数。

可设置项如下:

lChannel 通道号,目前设备模拟通道号从 1 开始,数字通道的起始通道号一般从 33 开始,具体取值在登录接口返回 dwStreamType 码流类型:0-主码流,1-子码流,2-码流 3,3-虚拟码流,以此类推 dwLinkMode 连接方式:0- TCP 方式,1- UDP 方式,2- 多播方式,3- RTP 方式,4-RTP/RTSP,5-RSTP/HTTP bBlocked 0- 非阻塞取流,1- 阻塞取流 bPassbackRecord 0-不启用录像回传,1-启用录像回传。ANR 断网补录功能,客户端和设备之间网络异常恢复之后自动将前端数据同步过来,需要设备支持。 byPreviewMode 预览模式:0- 正常预览,1- 延迟预览 byProtoType 应用层取流协议:0- 私有协议,1- RTSP 协议 hHwnd 播放窗口的句柄,为 NULL 表示不解码显示

(2)RealPlayCallback:用于获取实时音视频数据。实现的以下接口

public interface RealPlayCallBack { public void fRealDataCallBack(int iRealHandle, int iDataType, byte[] pDataBuffer, int iDataSize); }

参数说明:

[out] iRealHandle 当前的预览句柄 [out] iDataType 数据类型 [out] pDataBuffer 存放数据的缓冲区指针 [out] iDataSize 缓冲区大小

3,解码播放实时流音视频

调用NET_DVR_RealPlay_V40 方法获取实时流音视频数据成功后,通过播放库进行解码显示。

实现RealPlayCallback接口的fRealDataCallback方法,拿到实时流音视频数据,调用以下方法解码播放。

private void processData(int iPlayViewNo, int iDataType, byte[] pDataBuffer, int iDataSize, int iStreamMode) { if (HCNetSDK.NET_DVR_SYSHEAD == iDataType) { if (m_iPort >= 0) { return; } m_iPort = Player.getInstance().getPort(); if (m_iPort == -1) { Log.e(TAG, "getPort is failed with: " + Player.getInstance().getLastError(m_iPort)); return; } Log.i(TAG, "getPort succ with: " + m_iPort); if (iDataSize > 0) { if (!Player.getInstance().setStreamOpenMode(m_iPort, iStreamMode)) //set stream mode { Log.e(TAG, "setStreamOpenMode failed"); return; } if (!Player.getInstance().openStream(m_iPort, pDataBuffer, iDataSize, 2 * 1024 * 1024)) //open stream { Log.e(TAG, "openStream failed"); return; } while (!m_bSurfaceCreated) { try { Thread.sleep(100); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } Log.i(TAG, "wait 100 for surface, handle:" + iPlayViewNo); } if (!Player.getInstance().play(m_iPort, getHolder())) { Log.e(TAG, "play failed,error:" + Player.getInstance().getLastError(m_iPort)); return; } if (!Player.getInstance().playSound(m_iPort)) { Log.e(TAG, "playSound failed with error code:" + Player.getInstance().getLastError(m_iPort)); return; } } } else { if (!Player.getInstance().inputData(m_iPort, pDataBuffer, iDataSize)) { Log.e(TAG, "inputData failed with: " + Player.getInstance().getLastError(m_iPort)); } } }

4,停止获取实时流音视频数据

 调用以下方法停止获取实时流:

public boolean NET_DVR_StopRealPlay(int iRealHandle)

参数说明:

[in] iRealHandle 预览句柄,NET_DVR_RealPlay_V40 的返回值

返回值:

 TRUE表示成功,FALSE表示失败。

 

5,停止本地播放  停止获取实时流音视频数据后,还需要手动关闭本地解码播放。可调用以下方法:

private void stopPlayer() { Player.getInstance().stopSound(); if (!Player.getInstance().stop(this.m_iPort)) { Log.e(TAG, "stop is failed!"); return; } if (!Player.getInstance().closeStream(this.m_iPort)) { Log.e(TAG, "closeStream is failed!"); return; } if (Player.getInstance().freePort(this.m_iPort)) { this.m_iPort = -1; return; } Log.e(TAG, "freePort is failed!" + this.m_iPort); }

6,注销登录

 单个设备可同时连接的用户数量是有限制的,达到限制后其他用户无法再登录(登录返回错误码5)。所以我们不再预览时,应该注销登录。调用以下方法即可:

public boolean NET_DVR_Logout_V30 (int lUserID)

参数说明:

[in]lUserID 用户 ID 号,NET_DVR_Login_V30 的返回值

返回值:

TRUE 表示成功,FALSE表示失败。 

 

至此,海康摄像头监控视频实时预览过程算是讲完了。下面讲部分具体实现:

 

1,项目架构

 为了更好的支持后续功能拓展,也为了降低类的复杂度,提升代码可维护性,尽可能的秉持了单一职责原则和接口隔离原则。以下为部分类说明:

LoginAsyncTask.java 异步任务类登录设备 AsyncTaskExecuteListener.java 异步任务类执行结果监听接口 PlaySurfaceView.java 自定义SurfaceView播放视频 MonitorVedioActivity.java 界面交互Activity MethodUtils.java 工具类 VideoInfo.java 设备实体类

2,登录设备

 登录设备,然后根据登录返回信息获取实时流,这里用异步任务类再合适不过了。我是写了一个异步任务类配合接口完成设备

登录及登录结果获取功能,具体实现可查看github代码。

3,自动适配单路/多路视频,自定义播放列数

/** * 开始播放实时监控视频 * * @param chanNum 通道数目 * @param columnNum 展示列数 */ private void startPreview(int chanNum, int columnNum) { playView = new PlaySurfaceView[chanNum]; // 建立frameLayout容纳所有通道视频画面 FrameLayout videoLayout = new FrameLayout(this); for (int i = 0; i < chanNum; i++) { if (playView[i] == null) { // 第i通道SurfaceView playView[i] = new PlaySurfaceView(this); // 设置第i通道监控画面尺寸,单路时全屏播放,多路时分列4:3播放 playView[i].setViewSize(metric.widthPixels / columnNum, columnNum == 1 ? metric.heightPixels : 3 * metric.widthPixels / (4 * columnNum)); // 设置第i通道监控画面布局参数 FrameLayout.LayoutParams videoItemParams = new FrameLayout.LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT); videoItemParams.topMargin = i / columnNum * playView[i].getCurHeight(); videoItemParams.leftMargin = i % columnNum * playView[i].getCurWidth(); videoItemParams.gravity = Gravity.TOP | Gravity.LEFT; // 将第i通道SurfaceView添加到FrameLayout videoLayout.addView(playView[i], videoItemParams); } // 播放第i通道视频 playView[i].startPreview(iLogId, i + iStartChan); } // 设置scrollView布局参数 FrameLayout.LayoutParams scrollViewParams = new FrameLayout.LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT); ScrollView scrollView = new ScrollView(this); // 将含有多通道视频画面的frameLayout添加到scrollView scrollView.addView(videoLayout); // 动态添加scrollView布局 addContentView(scrollView, scrollViewParams); // 获取起始通道监控视频播放状态码 iPlayId = playView[0].m_iPreviewHandle; }

4,获取实时流音视频数据

public void startPreview(int iLogId, int chanNum) { RealPlayCallBack realPlayCallBack = getRealPlayerCbf(); if (realPlayCallBack == null) { Log.e(TAG, "fRealDataCallBack object is failed!"); return; } Log.i(TAG, "preview channel:" + chanNum); NET_DVR_PREVIEWINFO netDVRPreviewInfo = new NET_DVR_PREVIEWINFO(); // 通道号,模拟通道号从1开始,数字通道号从33开始,具体取值在登录接口返回 netDVRPreviewInfo.lChannel = chanNum; // 码流类型 netDVRPreviewInfo.dwStreamType = 1; // 连接方式,0-TCP方式,1-UDP方式,2-多播方式,3-RTP方式,4-RTP/RTSP,5-RSTP/HTTP // previewInfo.dwLinkMode = 5; // 0-非阻塞取流,1-阻塞取流 netDVRPreviewInfo.bBlocked = 1; // 实时预览,返回值-1表示失败 this.m_iPreviewHandle = HCNetSDK.getInstance().NET_DVR_RealPlay_V40(iLogId, netDVRPreviewInfo, realPlayCallBack); if (m_iPreviewHandle < 0) { Log.e(TAG, "NET_DVR_RealPlay is failed!Err:" + HCNetSDK.getInstance().NET_DVR_GetLastError()); } }

5,解码播放实时流音视频

 见前面过程讲解部分,或github代码,不再赘述。

6,SurfaceView播放视频

 SurfaceView是View的子类,功能非常强大。它允许在其上添加层,允许其他线程更新视图对象,使用了双缓冲机制,非常适

合我们的需求。创建SurfaceView的时候需要实现SurfaceHolder.Callback接口,它可以用来监听SurfaceView的状态,比如:SurfaceView

的改变 、SurfaceView的创建 、SurfaceView 销毁等,我们可以在相应的方法中做一些操作。

 这里我实现了SurfaceHolder.Callback接口,并重写了以下几个方法:

@Override public void surfaceCreated(SurfaceHolder paramSurfaceHolder) { videoSurfaceview.getHolder().setFormat(-3); if ((-1 != iPort) && (paramSurfaceHolder.getSurface().isValid())) Player.getInstance().setVideoWindow(iPort, 0, paramSurfaceHolder); } @Override public void surfaceChanged(SurfaceHolder paramSurfaceHolder, int paramInt1, int paramInt2, int paramInt3) { } @Override public void surfaceDestroyed(SurfaceHolder paramSurfaceHolder) { if ((-1 != iPort) && (paramSurfaceHolder.getSurface().isValid())) Player.getInstance().setVideoWindow(iPort, 0, null); }

注意:必须实现SurfaceHolder.Callback接口并重写以上方法,不然会出现诸如锁屏再进入或切换到后台再进入后黑屏问题。

7,获取错误信息

(1)返回最后操作的错误码NET_DVR_GetLastError

// 调用 NET_DVR_GetLastError 获取错误码,通过错误码判断出错原因 int errorCode = HCNetSDK.getInstance().NET_DVR_GetLastError();

错误码详见《设备网络编程指南(Android)》第4章。

 

至此,Android平台的海康视频播放功能开发完成了。下面讲讲封装为Cordova插件。

 

 我以前写过几篇封装Cordova插件的博文,业务需求虽不同,但方法是一样的,所以这里就不再重复写了。海康视频播放插件已上传到github,所有代码均有详细注释,github地址:https://github.com/fangxiaopeng/fxp-plugin-video。如果大家对Cordova插件开发有兴趣的话,可以看看我的另外几篇博文:

 Cordova插件开发(1)-Android插件开发详解

 Cordova插件开发(2)-Android插件安装包制作详解

 Cordova插件开发(3)-将Cordova插件发布到npm

 

这里需要讲的是将视频播放结果设置到js回调。

 

思路:以startActivityForResult的方式启动视频播放Avtivity,在视频播放Activity退出时设置resultCode并传递信息,

在onActivityResult回调中获取对应Activity传递的信息,通过callbackContext设置js回调。

 

下面是具体实现:

(1)在继承于CordovaPlugin的类中,以startActivityForResult的方式启动视频播放Avtivity

private void toMonitorVideoActivity(VideoInfo videoInfo) { Log.e(TAG, "toMonitorVideoActivity"); Intent intent = new Intent(this.cordova.getActivity(), MonitorVedioActivity.class); Bundle bundle = new Bundle(); bundle.putSerializable("videoInfo", videoInfo); intent.putExtras(bundle); if (this.cordova != null) { this.cordova.startActivityForResult((CordovaPlugin) this, intent, REQUEST_MonitorVideo); } }

(2)在视频播放Activity退出时设置resultCode并传递信息

/** * 退出Activity,设置返回值 * * @param activity 待退出activity * @param resultCode 返回码 * @param msg 返回信息 */ public void quitActivity(Activity activity,int resultCode, String msg){ Intent intent = new Intent(); intent.putExtra("result",msg); activity.setResult(resultCode,intent); activity.finish(); }

(3)在onActivityResult回调中获取对应Activity传递的信息

@Override public void onActivityResult(int requestCode, int resultCode, Intent intent) { switch (requestCode){ case REQUEST_HCVideo: Log.i(TAG,"back from HCVideoActivity"); break; case REQUEST_MonitorVideo: Log.i(TAG,"back from MonitorVedioActivity"); break; } setCallBack(resultCode,intent); }

(4)通过callbackContext设置js回调

private void setCallBack(int resultCode, Intent intent){ if (this.callbackContext != null){ String resultMsg = intent.getExtras().getString("result"); Log.i(TAG,"resultCode:" + resultCode); Log.i(TAG,"resultMsg:" + resultMsg); switch (resultCode){ case RESULT_NORMAL: Log.i(TAG,"RESULT_NORMAL"); callbackContext.success("success"); break; case RESULT_ERROR: Log.e(TAG,"RESULT_ERROR"); callbackContext.error(resultMsg); break; } } }

OK。

 

欢迎关注微信公众号交流讨论!

 

 

 

 



【本文地址】

公司简介

联系我们

今日新闻


点击排行

实验室常用的仪器、试剂和
说到实验室常用到的东西,主要就分为仪器、试剂和耗
不用再找了,全球10大实验
01、赛默飞世尔科技(热电)Thermo Fisher Scientif
三代水柜的量产巅峰T-72坦
作者:寞寒最近,西边闹腾挺大,本来小寞以为忙完这
通风柜跟实验室通风系统有
说到通风柜跟实验室通风,不少人都纠结二者到底是不
集消毒杀菌、烘干收纳为一
厨房是家里细菌较多的地方,潮湿的环境、没有完全密
实验室设备之全钢实验台如
全钢实验台是实验室家具中较为重要的家具之一,很多

推荐新闻


图片新闻

实验室药品柜的特性有哪些
实验室药品柜是实验室家具的重要组成部分之一,主要
小学科学实验中有哪些教学
计算机 计算器 一般 打孔器 打气筒 仪器车 显微镜
实验室各种仪器原理动图讲
1.紫外分光光谱UV分析原理:吸收紫外光能量,引起分
高中化学常见仪器及实验装
1、可加热仪器:2、计量仪器:(1)仪器A的名称:量
微生物操作主要设备和器具
今天盘点一下微生物操作主要设备和器具,别嫌我啰嗦
浅谈通风柜使用基本常识
 众所周知,通风柜功能中最主要的就是排气功能。在

专题文章

    CopyRight 2018-2019 实验室设备网 版权所有 win10的实时保护怎么永久关闭