From 47e802893d40a56e29371b6750843f975ba4e0e1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=99=BE=E5=B7=9D8488?= Date: Mon, 8 Sep 2025 14:23:52 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=B5=B7=E5=BA=B7SDK=E6=8F=92=E4=BB=B6?= =?UTF-8?q?=20(#332)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- plugin/hiksdk/README.md | 15 + plugin/hiksdk/client.go | 45 + plugin/hiksdk/device.go | 80 ++ plugin/hiksdk/include/HCNetSDK.h | 1354 +++++++++++++++++++++++++++ plugin/hiksdk/index.go | 54 ++ plugin/hiksdk/lib/Linux/README.md | 6 + plugin/hiksdk/lib/Windows/README.md | 6 + plugin/hiksdk/pkg/HKDevice.go | 506 ++++++++++ plugin/hiksdk/pkg/audio.go | 416 ++++++++ plugin/hiksdk/pkg/device.go | 28 + plugin/hiksdk/pkg/transceiver.go | 164 ++++ plugin/hiksdk/pkg/video.go | 493 ++++++++++ 12 files changed, 3167 insertions(+) create mode 100644 plugin/hiksdk/README.md create mode 100644 plugin/hiksdk/client.go create mode 100644 plugin/hiksdk/device.go create mode 100644 plugin/hiksdk/include/HCNetSDK.h create mode 100644 plugin/hiksdk/index.go create mode 100644 plugin/hiksdk/lib/Linux/README.md create mode 100644 plugin/hiksdk/lib/Windows/README.md create mode 100644 plugin/hiksdk/pkg/HKDevice.go create mode 100644 plugin/hiksdk/pkg/audio.go create mode 100644 plugin/hiksdk/pkg/device.go create mode 100644 plugin/hiksdk/pkg/transceiver.go create mode 100644 plugin/hiksdk/pkg/video.go diff --git a/plugin/hiksdk/README.md b/plugin/hiksdk/README.md new file mode 100644 index 0000000..a3e5a30 --- /dev/null +++ b/plugin/hiksdk/README.md @@ -0,0 +1,15 @@ +需要将lib下的linux或windows下的文件复制到main.go同级目录下,才能正常运行 + +配置文件 + +hik: + loglevel: debug //根据实际情况修改 + client: + - ip: "172.16.9.35" + username: "admin" + password: "123456" + port: 8000 + - ip: "172.16.9.200" + username: "admin" + password: "123456" + port: 8000 \ No newline at end of file diff --git a/plugin/hiksdk/client.go b/plugin/hiksdk/client.go new file mode 100644 index 0000000..d0fd08e --- /dev/null +++ b/plugin/hiksdk/client.go @@ -0,0 +1,45 @@ +package plugin_hiksdk + +import ( + "m7s.live/v5" + "m7s.live/v5/pkg/config" + "m7s.live/v5/pkg/task" +) + +const ( + DIRECTION_PULL = "pull" + DIRECTION_PUSH = "push" +) + +type ClientPlugin struct { + task.Job + conf *HikPlugin + + pullCtx m7s.PullJob + pushCtx m7s.PushJob + direction string +} + +func (c *ClientPlugin) GetPullJob() *m7s.PullJob { + return &c.pullCtx +} + +func (c *ClientPlugin) GetPushJob() *m7s.PushJob { + return &c.pushCtx +} + +func NewPuller(_ config.Pull) m7s.IPuller { + client := &ClientPlugin{ + direction: DIRECTION_PULL, + } + client.SetDescription(task.OwnerTypeKey, "HikPuller") + return client +} + +func NewPusher() m7s.IPusher { + client := &ClientPlugin{ + direction: DIRECTION_PUSH, + } + client.SetDescription(task.OwnerTypeKey, "HikPusher") + return client +} \ No newline at end of file diff --git a/plugin/hiksdk/device.go b/plugin/hiksdk/device.go new file mode 100644 index 0000000..ac53044 --- /dev/null +++ b/plugin/hiksdk/device.go @@ -0,0 +1,80 @@ +package plugin_hiksdk + +import ( + "fmt" + "strings" + + "m7s.live/v5/pkg/task" + "m7s.live/v5/plugin/hiksdk/pkg" + + "github.com/prometheus/client_golang/prometheus" +) + +type HikDevice struct { + task.Job + IP string + UserName string + Password string + Port int + Device pkg.Device + Conf *HikPlugin +} + +func (d *HikDevice) Start() (err error) { + info := pkg.DeviceInfo{ + IP: d.IP, + UserName: d.UserName, + Password: d.Password, + Port: d.Port, + } + d.Device = pkg.NewHKDevice(info) + return +} + +func (d *HikDevice) Run() (err error) { + if _, err := d.Device.Login(); err != nil { + fmt.Println(err.Error()) + } else { + fmt.Println("success login") + } + deviceInfo, err := d.Device.GetDeiceInfo() // 获取设备参数 + if err != nil { + fmt.Println(err.Error()) + } else { + fmt.Println(deviceInfo) + } + + channelNames, err := d.Device.GetChannelName() // 获取通道 + if err != nil { + fmt.Println(err.Error()) + } else { + fmt.Println("通道:", channelNames) + } + d.AutoPullStream() + return +} + + +func (d *HikDevice) AutoPullStream() { + deviceInfo, _ := d.Device.GetDeiceInfo() // 获取设备参数 + channelNames, _ := d.Device.GetChannelName() // 获取通道 + for i := 1; i <= int(deviceInfo.ByChanNum); i++ { + d.PullStream(deviceInfo.DeviceID, channelNames[i], i) + } +} + +func (d *HikDevice) PullStream(ifname string, channelName string, channelId int) error { + // 生成流路径 + ifname = strings.ReplaceAll(ifname, "-", "_") + streamPath := fmt.Sprintf("%s/%s", ifname, channelName) + receiver := &pkg.PSReceiver{} + receiver.Device = d.Device + receiver.ChannelId = channelId + receiver.Publisher, _ = d.Conf.Publish(d.Conf, streamPath) + go d.Conf.RunTask(receiver) + return nil +} + +func (d *HikDevice) Describe(ch chan<- *prometheus.Desc) { + d.Device.Logout() +} diff --git a/plugin/hiksdk/include/HCNetSDK.h b/plugin/hiksdk/include/HCNetSDK.h new file mode 100644 index 0000000..a074923 --- /dev/null +++ b/plugin/hiksdk/include/HCNetSDK.h @@ -0,0 +1,1354 @@ +#ifndef _HC_NET_SDK_H_ +#define _HC_NET_SDK_H_ + +#ifndef _WINDOWS_ + #if (defined(_WIN32) || defined(_WIN64)) + #include + #include + #endif +#endif + +#ifndef __PLAYRECT_defined + #define __PLAYRECT_defined + typedef struct __PLAYRECT + { + int x; + int y; + int uWidth; + int uHeight; + }PLAYRECT; +#endif + +#if (defined(_WIN32)) //windows + typedef unsigned __int64 UINT64; + typedef signed __int64 INT64; +#elif defined(__linux__) || defined(__APPLE__) //linux + #define BOOL int + #include + typedef uint32_t DWORD; + typedef uint16_t WORD; + typedef uint16_t SHORT; + typedef uint16_t USHORT; + typedef int32_t LONG; + typedef uint8_t BYTE; + typedef uint32_t UINT; + typedef void* LPVOID; + typedef void* HANDLE; + typedef uint32_t * LPDWORD; + typedef uint64_t UINT64; + + #ifndef TRUE + #define TRUE 1 + #endif + #ifndef FALSE + #define FALSE 0 + #endif + #ifndef NULL + #define NULL 0 + #endif + + #define __stdcall + #define CALLBACK + + #define NET_DVR_API extern "C" + typedef unsigned int COLORKEY; + typedef unsigned int COLORREF; + + #ifndef __HWND_defined + #define __HWND_defined + #if defined(__linux__) + typedef unsigned int HWND; + #else + typedef void* HWND; + #endif + #endif + + #ifndef __HDC_defined + #define __HDC_defined + #if defined(__linux__) + typedef struct __DC + { + void* surface; //SDL Surface + HWND hWnd; //HDC window handle + }DC; + typedef DC* HDC; + #else + typedef void* HDC; + #endif + #endif + + typedef struct tagInitInfo + { + int uWidth; + int uHeight; + }INITINFO; +#endif + +#define SERIALNO_LEN 48 //序列号长度 +#define NET_DVR_DEV_ADDRESS_MAX_LEN 129 +#define NET_DVR_LOGIN_USERNAME_MAX_LEN 64 +#define NET_DVR_LOGIN_PASSWD_MAX_LEN 64 + + +#define LIGHT_PWRON 2 /* 接通灯光电源 */ +#define WIPER_PWRON 3 /* 接通雨刷开关 */ +#define FAN_PWRON 4 /* 接通风扇开关 */ +#define HEATER_PWRON 5 /* 接通加热器开关 */ +#define AUX_PWRON1 6 /* 接通辅助设备开关 */ +#define AUX_PWRON2 7 /* 接通辅助设备开关 */ +#define SET_PRESET 8 /* 设置预置点 */ +#define CLE_PRESET 9 /* 清除预置点 */ + +#define ZOOM_IN 11 /* 焦距以速度SS变大(倍率变大) */ +#define ZOOM_OUT 12 /* 焦距以速度SS变小(倍率变小) */ +#define FOCUS_NEAR 13 /* 焦点以速度SS前调 */ +#define FOCUS_FAR 14 /* 焦点以速度SS后调 */ +#define IRIS_OPEN 15 /* 光圈以速度SS扩大 */ +#define IRIS_CLOSE 16 /* 光圈以速度SS缩小 */ + +#define TILT_UP 21 /* 云台以SS的速度上仰 */ +#define TILT_DOWN 22 /* 云台以SS的速度下俯 */ +#define PAN_LEFT 23 /* 云台以SS的速度左转 */ +#define PAN_RIGHT 24 /* 云台以SS的速度右转 */ +#define UP_LEFT 25 /* 云台以SS的速度上仰和左转 */ +#define UP_RIGHT 26 /* 云台以SS的速度上仰和右转 */ +#define DOWN_LEFT 27 /* 云台以SS的速度下俯和左转 */ +#define DOWN_RIGHT 28 /* 云台以SS的速度下俯和右转 */ +#define PAN_AUTO 29 /* 云台以SS的速度左右自动扫描 */ + +#define FILL_PRE_SEQ 30 /* 将预置点加入巡航序列 */ +#define SET_SEQ_DWELL 31 /* 设置巡航点停顿时间 */ +#define SET_SEQ_SPEED 32 /* 设置巡航速度 */ +#define CLE_PRE_SEQ 33 /* 将预置点从巡航序列中删除 */ +#define STA_MEM_CRUISE 34 /* 开始记录轨迹 */ +#define STO_MEM_CRUISE 35 /* 停止记录轨迹 */ +#define RUN_CRUISE 36 /* 开始轨迹 */ +#define RUN_SEQ 37 /* 开始巡航 */ +#define STOP_SEQ 38 /* 停止巡航 */ +#define GOTO_PRESET 39 /* 快球转到预置点 */ + +#define DEL_SEQ 43 /* 删除巡航路径 */ +#define STOP_CRUISE 44 /* 停止轨迹 */ +#define DELETE_CRUISE 45 /* 删除单条轨迹 */ +#define DELETE_ALL_CRUISE 46/* 删除所有轨迹 */ + +#define PAN_CIRCLE 50 /* 云台以SS的速度自动圆周扫描 */ +#define DRAG_PTZ 51 /* 拖动PTZ */ +#define LINEAR_SCAN 52 /* 区域扫描 */ //2014-03-15 +#define CLE_ALL_PRESET 53 /* 预置点全部清除 */ +#define CLE_ALL_SEQ 54 /* 巡航全部清除 */ +#define CLE_ALL_CRUISE 55 /* 轨迹全部清除 */ + +#define POPUP_MENU 56 /* 显示操作菜单 */ + +#define TILT_DOWN_ZOOM_IN 58 /* 云台以SS的速度下俯&&焦距以速度SS变大(倍率变大) */ +#define TILT_DOWN_ZOOM_OUT 59 /* 云台以SS的速度下俯&&焦距以速度SS变小(倍率变小) */ +#define PAN_LEFT_ZOOM_IN 60 /* 云台以SS的速度左转&&焦距以速度SS变大(倍率变大)*/ +#define PAN_LEFT_ZOOM_OUT 61 /* 云台以SS的速度左转&&焦距以速度SS变小(倍率变小)*/ +#define PAN_RIGHT_ZOOM_IN 62 /* 云台以SS的速度右转&&焦距以速度SS变大(倍率变大) */ +#define PAN_RIGHT_ZOOM_OUT 63 /* 云台以SS的速度右转&&焦距以速度SS变小(倍率变小) */ +#define UP_LEFT_ZOOM_IN 64 /* 云台以SS的速度上仰和左转&&焦距以速度SS变大(倍率变大)*/ +#define UP_LEFT_ZOOM_OUT 65 /* 云台以SS的速度上仰和左转&&焦距以速度SS变小(倍率变小)*/ +#define UP_RIGHT_ZOOM_IN 66 /* 云台以SS的速度上仰和右转&&焦距以速度SS变大(倍率变大)*/ +#define UP_RIGHT_ZOOM_OUT 67 /* 云台以SS的速度上仰和右转&&焦距以速度SS变小(倍率变小)*/ +#define DOWN_LEFT_ZOOM_IN 68 /* 云台以SS的速度下俯和左转&&焦距以速度SS变大(倍率变大) */ +#define DOWN_LEFT_ZOOM_OUT 69 /* 云台以SS的速度下俯和左转&&焦距以速度SS变小(倍率变小) */ +#define DOWN_RIGHT_ZOOM_IN 70 /* 云台以SS的速度下俯和右转&&焦距以速度SS变大(倍率变大) */ +#define DOWN_RIGHT_ZOOM_OUT 71 /* 云台以SS的速度下俯和右转&&焦距以速度SS变小(倍率变小) */ +#define TILT_UP_ZOOM_IN 72 /* 云台以SS的速度上仰&&焦距以速度SS变大(倍率变大) */ +#define TILT_UP_ZOOM_OUT 73 + +//宏定义 修正后 +#define MAX_NAMELEN 16 //DVR本地登陆名 +#define MAX_RIGHT 32 //设备支持的权限(1-12表示本地权限,13-32表示远程权限) +#define NAME_LEN 32 //用户名长度 +#define MIN_PASSWD_LEN 8 //最小密码长度 +#define PASSWD_LEN 16 //密码长度 +#define STREAM_PASSWD_LEN 12 //码流加密密钥最大长度 +#define MAX_PASSWD_LEN_EX 64 //密码长度64位 +#define GUID_LEN 16 //GUID长度 +#define DEV_TYPE_NAME_LEN 24 //设备类型名称长度 +#define SERIALNO_LEN 48 //序列号长度 +#define MACADDR_LEN 6 //mac地址长度 +#define MAC_ADDRESS_NUM 48 //Mac地址长度 +#define MAX_SENCE_NUM 16 //场景数 +#define RULE_REGION_MAX 128 //最大区域 +#define MAX_ETHERNET 2 //设备可配以太网络 +#define MAX_NETWORK_CARD 4 //设备可配最大网卡数目 +#define MAX_NETWORK_CARD_EX 12 //设备可配最大网卡数目扩展 +#define PATHNAME_LEN 128 //路径长度 +#define MAX_PRESET_V13 16 //预置点 +#define MAX_TEST_COMMAND_NUM 32 //产线测试保留字段长度 +#define MAX_NUMBER_LEN 32 //号码最大长度 +#define MAX_NAME_LEN 128 //设备名称最大长度 +#define MAX_INDEX_LED 8 //LED索引最大值 2013-11-19 +#define MAX_CUSTOM_DIR 64 //自定义目录最大长度 +#define URL_LEN_V40 256 //最大URL长度 +#define CLOUD_NAME_LEN 48 //云存储服务器用户名长度 +#define CLOUD_PASSWD_LEN 48 //云存储服务器密码长度 +#define MAX_SENSORNAME_LEN 64 //传感器名称长度 +#define MAX_SENSORCHAN_LEN 32 //传感器通道长度 +#define MAX_DESCRIPTION_LEN 32 //传感器描述长度 +#define MAX_DEVNAME_LEN_EX 64 //设备名称长度扩展 +#define NET_SDK_MAX_FILE_PATH 256 //文件路径长度 +#define MAX_TMEVOICE_LEN 64 //TME语音播报内容长度 +#define ISO_8601_LEN 32 //ISO_8601时间长度 +#define MODULE_INFO_LEN 32 //模块信息长度 +#define VERSION_INFO_LEN 32 //版本信息长度 + +#define MAX_NUM_INPUT_BOARD 512 //输入板最大个数 +#define MAX_SHIPSDETE_REGION_NUM 8 // 船只检测区域列表最大数目 + +#define MAX_RES_NUM_ONE_VS_INPUT_CHAN 8 //一个虚拟屏输入通道支持的分辨率的最大数量 +#define MAX_VS_INPUT_CHAN_NUM 16 //虚拟屏输入通道最大数量 + +#define NET_SDK_MAX_FDID_LEN 256//人脸库ID最大长度 +#define NET_SDK_MAX_PICID_LEN 256 //人脸ID最大长度 +#define NET_SDK_FDPIC_CUSTOM_INFO_LEN 96 //人脸库图片自定义信息长度 +#define NET_DVR_MAX_FACE_ANALYSIS_NUM 32 //最大支持单张图片识别出的人脸区域个数 +#define NET_DVR_MAX_FACE_SEARCH_NUM 5 //最大支持搜索人脸区域个数 +#define NET_SDK_SECRETKEY_LEN 128 //配置文件密钥长度 +#define NET_SDK_CUSTOM_LEN 512 //自定义信息最大长度 +#define NET_SDK_CHECK_CODE_LEN 128//校验码长度 +#define RELATIVE_CHANNEL_LEN 2//报警关联的通道号的数量 +#define NET_SDK_MAX_CALLEDTARGET_NAME 32 //呗呼叫目标的用户名 +#define NET_SDK_MAX_HBDID_LEN 256 /*256 人体库ID最大长度*/ +//小间距LED控制器 +#define MAX_LEN_TEXT_CONTENT 128 //字符内容长度 +#define MAX_NUM_INPUT_SOURCE_TEXT 32 //信号源可叠加的文本数量 +#define MAX_NUM_OUTPUT_CHANNEL 512 //LED区域包含的输出口个数 + +//子窗口解码OSD +#define MAX_LEN_OSD_CONTENT 256 //OSD信息最大长度 +#define MAX_NUM_OSD_ONE_SUBWND 8 //单个子窗口支持的最大OSD数量 +#define MAX_NUM_SPLIT_WND 64 //单个窗口支持的最大分屏窗口数量(即子窗口数量) +#define MAX_NUM_OSD 8 + +//2013-11-19 +#define MAX_DEVNAME_LEN 32 //设备名称最大长度 +#define MAX_LED_INFO 256 //屏幕字体显示信息最大长度 +#define MAX_TIME_LEN 32 //时间最大长度 +#define MAX_CARD_LEN 24 //卡号最大长度 +#define MAX_OPERATORNAME_LEN 32 //操作人员名称最大长度 + +#define THERMOMETRY_ALARMRULE_NUM 40 //热成像报警规则数 +#define MAX_THERMOMETRY_REGION_NUM 40 //热度图检测区域最大支持数 +#define MAX_THERMOMETRY_DIFFCOMPARISON_NUM 40 //热成像温差报警规则数 +#define MAX_SHIPS_NUM 20 //船只检测最大船只数 +#define MAX_SHIPIMAGE_NUM 6 //船只最大抓图数 +#define KEY_WORD_NUM 3 //关键字个数 +#define KEY_WORD_LEN 128 //关键字长度 +//异步登录回调状态宏定义 +#define ASYN_LOGIN_SUCC 1 //异步登录成功 +#define ASYN_LOGIN_FAILED 0 //异步登录失败 + +#define NET_SDK_MAX_VERIFICATION_CODE_LEN 32 //萤石云验证码长度 +#define NET_SDK_MAX_OPERATE_CODE_LEN 64 //萤石云操作码长度 +#define MAX_TIMESEGMENT_V30 8 //9000设备最大时间段数 +#define MAX_TIMESEGMENT 4 //8000设备最大时间段数 +#define MAX_ICR_NUM 8 //抓拍机红外滤光片预置点数2013-07-09 +#define MAX_VEHICLEFLOW_INFO 24 //车流量信息最大个数 +#define MAX_SHELTERNUM 4 //8000设备最大遮挡区域数 +#define MAX_DAYS 7 //每周天数 +#define PHONENUMBER_LEN 32 //pppoe拨号号码最大长度 +#define MAX_ACCESSORY_CARD 256 //配件板信息最大长度 +#define MAX_DISKNUM_V30 33 //9000设备最大硬盘数/* 最多33个硬盘(包括16个内置SATA硬盘、1个eSATA硬盘和16个NFS盘) */ +#define NET_SDK_MAX_NET_USER_NUM 64 //网络用户 + +#define NET_SDK_DISK_LOCATION_LEN 16 //硬盘位置长度 +#define NET_SDK_SUPPLIER_NAME_LEN 32 //供应商名称长度 +#define NET_SDK_DISK_MODEL_LEN 64 //硬盘型号长度 +#define NET_SDK_MAX_DISK_VOLUME 33 //最大硬盘卷个数 +#define NET_SDK_DISK_VOLUME_LEN 36 //硬盘卷名称长度 + +#define MAX_DISKNUM 16 //8000设备最大硬盘数 +#define MAX_DISKNUM_V10 8 //1.2版本之前版本 +#define CARD_READER_DESCRIPTION 32 //读卡器描述 +#define MAX_FACE_NUM 2 //最大人脸数 + +#define MAX_WINDOW_V30 32 //9000设备本地显示最大播放窗口数 +#define MAX_WINDOW_V40 64 //Netra 2.3.1扩展 +#define MAX_WINDOW 16 //8000设备最大硬盘数 +#define MAX_VGA_V30 4 //9000设备最大可接VGA数 +#define MAX_VGA 1 //8000设备最大可接VGA数 + +#define MAX_USERNUM_V30 32 //9000设备最大用户数 +#define MAX_USERNUM 16 //8000设备最大用户数 +#define MAX_EXCEPTIONNUM_V30 32 //9000设备最大异常处理数 +#define MAX_EXCEPTIONNUM 16 //8000设备最大异常处理数 +#define MAX_LINK 6 //8000设备单通道最大视频流连接数 +#define MAX_ITC_EXCEPTIONOUT 32 //抓拍机最大报警输出 +#define MAX_SCREEN_DISPLAY_LEN 512 //屏幕显示字符长度 + +#define MAX_DECPOOLNUM 4 //单路解码器每个解码通道最大可循环解码数 +#define MAX_DECNUM 4 //单路解码器的最大解码通道数(实际只有一个,其他三个保留) +#define MAX_TRANSPARENTNUM 2 //单路解码器可配置最大透明通道数 +#define MAX_CYCLE_CHAN 16 //单路解码器最大轮巡通道数 +#define MAX_CYCLE_CHAN_V30 64 //最大轮巡通道数(扩展) +#define MAX_DIRNAME_LENGTH 80 //最大目录长度 +#define MAX_WINDOWS 16 //最大窗口数 + + +#define MAX_STRINGNUM_V30 8 //9000设备最大OSD字符行数数 +#define MAX_STRINGNUM 4 //8000设备最大OSD字符行数数 +#define MAX_STRINGNUM_EX 8 //8000定制扩展 +#define MAX_AUXOUT_V30 16 //9000设备最大辅助输出数 +#define MAX_AUXOUT 4 //8000设备最大辅助输出数 +#define MAX_HD_GROUP 16 //9000设备最大硬盘组数 +#define MAX_HD_GROUP_V40 32 //设备最大硬盘组数 +#define MAX_NFS_DISK 8 //8000设备最大NFS硬盘数 +#define NET_SDK_VERSION_LIST_LEN 64 //算法库版本最大值 +#define IW_ESSID_MAX_SIZE 32 //WIFI的SSID号长度 +#define IW_ENCODING_TOKEN_MAX 32 //WIFI密锁最大字节数 +#define MAX_SERIAL_NUM 64 //最多支持的透明通道路数 +#define MAX_DDNS_NUMS 10 //9000设备最大可配ddns数 +#define MAX_DOMAIN_NAME 64 /* 最大域名长度 */ +#define MAX_EMAIL_ADDR_LEN 48 //最大email地址长度 +#define MAX_EMAIL_PWD_LEN 32 //最大email密码长度 +#define MAX_SLAVECAMERA_NUM 8 //从摄像机个数 +#define MAX_CALIB_NUM 6 //标定点的个数 +#define MAX_CALIB_NUM_EX 20 //扩展标定点的个数 +#define MAX_LEDDISPLAYINFO_LEN 1024 //最大LED屏显示长度 +#define MAX_PEOPLE_DETECTION_NUM 8 //最大人员检测区域数 +#define MAXPROGRESS 100 //回放时的最大百分率 +#define MAX_SERIALNUM 2 //8000设备支持的串口数 1-232, 2-485 +#define CARDNUM_LEN 20 //卡号长度 +#define PATIENTID_LEN 64 +#define CARDNUM_LEN_OUT 32 //外部结构体卡号长度 +#define MAX_VIDEOOUT_V30 4 //9000设备的视频输出数 +#define MAX_VIDEOOUT 2 //8000设备的视频输出数 + +#define MAX_PRESET_V30 256 /* 9000设备支持的云台预置点数 */ +#define MAX_TRACK_V30 256 /* 9000设备支持的云台数 */ +#define MAX_CRUISE_V30 256 /* 9000设备支持的云台巡航数 */ +#define MAX_PRESET 128 /* 8000设备支持的云台预置点数 */ +#define MAX_TRACK 128 /* 8000设备支持的云台数 */ +#define MAX_CRUISE 128 /* 8000设备支持的云台巡航数 */ + +#define MAX_PRESET_V40 300 /* 云台支持的最大预置点数 */ +#define MAX_CRUISE_POINT_NUM 128 /* 最大支持的巡航点的个数 */ +#define MAX_CRUISEPOINT_NUM_V50 256 //最大支持的巡航点的个数扩展 + +#define CRUISE_MAX_PRESET_NUMS 32 /* 一条巡航最多的巡航点 */ +#define MAX_FACE_PIC_NUM 30 /*人脸子图个数*/ +#define LOCKGATE_TIME_NUM 4 //锁闸时间段个数 + +#define MAX_SERIAL_PORT 8 //9000设备支持232串口数 +#define MAX_PREVIEW_MODE 8 /* 设备支持最大预览模式数目 1画面,4画面,9画面,16画面.... */ +#define MAX_MATRIXOUT 16 /* 最大模拟矩阵输出个数 */ +#define LOG_INFO_LEN 11840 /* 日志附加信息 */ +#define DESC_LEN 16 /* 云台描述字符串长度 */ +#define PTZ_PROTOCOL_NUM 200 /* 9000最大支持的云台协议数 */ +#define IPC_PROTOCOL_NUM 50 //ipc 协议最大个数 + +#define MAX_AUDIO 1 //8000语音对讲通道数 +#define MAX_AUDIO_V30 2 //9000语音对讲通道数 +#define MAX_CHANNUM 16 //8000设备最大通道数 +#define MAX_ALARMIN 16 //8000设备最大报警输入数 +#define MAX_ALARMOUT 4 //8000设备最大报警输出数 +#define MAX_AUDIOCAST_CFG_TYPE 3 //支持广播参数配置的类型数量 MP3、MPEG2、AAC +//9000 IPC接入 +#define MAX_ANALOG_CHANNUM 32 //最大32个模拟通道 +#define MAX_ANALOG_ALARMOUT 32 //最大32路模拟报警输出 +#define MAX_ANALOG_ALARMIN 32 //最大32路模拟报警输入 + +#define MAX_IP_DEVICE 32 //允许接入的最大IP设备数 +#define MAX_IP_DEVICE_V40 64 // 允许接入的最大IP设备数 最多可添加64个 IVMS 2000等新设备 +#define MAX_IP_CHANNEL 32 //允许加入的最多IP通道数 +#define MAX_IP_ALARMIN 128 //允许加入的最多报警输入数 +#define MAX_IP_ALARMOUT 64 //允许加入的最多报警输出数 +#define MAX_IP_ALARMIN_V40 4096 //允许加入的最多报警输入数 +#define MAX_IP_ALARMOUT_V40 4096 //允许加入的最多报警输出数 + +#define MAX_RECORD_FILE_NUM 20 // 每次删除或者刻录的最大文件数 +//SDK_V31 ATM +#define MAX_ACTION_TYPE 12 //自定义协议叠加交易行为最大行为个数 +#define MAX_ATM_PROTOCOL_NUM 256 //每种输入方式对应的ATM最大协议数 +#define ATM_CUSTOM_PROTO 1025 //自定义协议 值为1025 +#define ATM_PROTOCOL_SORT 4 //ATM协议段数 +#define ATM_DESC_LEN 32 //ATM描述字符串长度 +// SDK_V31 ATM + + +#define MAX_IPV6_LEN 64 //IPv6地址最大长度 +#define MAX_EVENTID_LEN 64 //事件ID长度 + +#define INVALID_VALUE_UINT32 0xffffffff //无效值 +#define MAX_CHANNUM_V40 512 +#define MAX_MULTI_AREA_NUM 24 + +//SDK 录播主机 +#define COURSE_NAME_LEN 32 //课程名称 +#define INSTRUCTOR_NAME_LEN 16 //授课教师 +#define COURSE_DESCRIPTION_LEN 256 //课程信息 + +#define MAX_TIMESEGMENT_V40 16 //每节课信息 + + +#define MAX_MIX_CHAN_NUM 16 /*目前支持的最大混音通道数,背景通道 + MIC + LINE IN + 最多4个小画面*/ +#define MAX_LINE_IN_CHAN_NUM 16 //最大line in通道数 +#define MAX_MIC_CHAN_NUM 16 //最大MIC通道数 +#define INQUEST_CASE_NO_LEN 64 //审讯案件编号长度 +#define INQUEST_CASE_NAME_LEN 64 //审讯案件名称长度 +#define CUSTOM_INFO_LEN 64 //自定义信息长度 +#define INQUEST_CASE_LEN 64 //审讯信息长度 + + +#define MAX_FILE_ID_LEN 128 //视图库项目中文件ID的最大长度 +#define MAX_PIC_NAME_LEN 128 //图片名称长度 + +/* 最大支持的通道数 最大模拟加上最大IP支持 */ +#define MAX_CHANNUM_V30 ( MAX_ANALOG_CHANNUM + MAX_IP_CHANNEL )//64 +#define MAX_ALARMOUT_V40 (MAX_IP_ALARMOUT_V40 +MAX_ANALOG_ALARMOUT) //4128 +#define MAX_ALARMOUT_V30 ( MAX_ANALOG_ALARMOUT + MAX_IP_ALARMOUT )//96 +#define MAX_ALARMIN_V30 ( MAX_ANALOG_ALARMIN + MAX_IP_ALARMIN )//160 +#define MAX_ALARMIN_V40 (MAX_IP_ALARMIN_V40 +MAX_ANALOG_ALARMOUT) //4128 +#define MAX_ANALOG_ALARM_WITH_VOLT_LIMIT 16 //受电压限定的模拟报警最大输入数 + +#define MAX_ROIDETECT_NUM 8 //支持的ROI区域数 +#define MAX_LANERECT_NUM 5 //最大车牌识别区域数 +#define MAX_FORTIFY_NUM 10 //最大布防个数 +#define MAX_INTERVAL_NUM 4 //最大时间间隔个数 +#define MAX_CHJC_NUM 3 //最大车辆省份简称字符个数 +#define MAX_VL_NUM 5 //最大虚拟线圈个数 +#define MAX_DRIVECHAN_NUM 16 //最大车道数 +#define MAX_COIL_NUM 3 //最大线圈个数 +#define MAX_SIGNALLIGHT_NUM 6 //最大信号灯个数 +#define LEN_16 16 +#define LEN_32 32 +#define LEN_64 64 +#define LEN_31 31 +#define MAX_LINKAGE_CHAN_NUM 16 //报警联动的通道的最大数量 +#define MAX_CABINET_COUNT 8 //最大支持机柜数量 +#define MAX_ID_LEN 48 +#define MAX_PARKNO_LEN 16 +#define MAX_ALARMREASON_LEN 32 +#define MAX_UPGRADE_INFO_LEN 48 //获取升级文件匹配信息(模糊升级) +#define MAX_CUSTOMDIR_LEN 32 //自定义目录长度 +#define MAX_LED_INFO_LEN 512//LED内容长度 +#define MAX_VOICE_INFO_LEN 128//语音播报内容长度 +#define MAX_LITLE_INFO_LEN 64 //纸票标题内容长度 +#define MAX_CUSTOM_INFO_LEN 64 //纸票自定义信息内容长度 +#define MAX_PHONE_NUM_LEN 16 //联系电话内容长度 +#define MAX_APP_SERIALNUM_LEN 32 //应用序列号长度 + +#define AUDIOTALKTYPE_G722 0 +#define AUDIOTALKTYPE_G711_MU 1 +#define AUDIOTALKTYPE_G711_A 2 +#define AUDIOTALKTYPE_MP2L2 5 +#define AUDIOTALKTYPE_G726 6 +#define AUDIOTALKTYPE_AAC 7 +#define AUDIOTALKTYPE_PCM 8 +#define AUDIOTALKTYPE_G722C 9 +#define AUDIOTALKTYPE_MP3 15 + +//packet type +#define FILE_HEAD 0 //file head +#define VIDEO_I_FRAME 1 //video I frame +#define VIDEO_B_FRAME 2 //video B frame +#define VIDEO_P_FRAME 3 //video P frame +#define AUDIO_PACKET 10 //audio packet +#define PRIVT_PACKET 11 //private packet +//E frame +#define HIK_H264_E_FRAME (1 << 6) // 以前E帧不用了,深P帧也没用到 +#define MAX_TRANSPARENT_CHAN_NUM 4 //每个串口允许建立的最大透明通道数 +#define MAX_TRANSPARENT_ACCESS_NUM 4 //每个监听端口允许接入的最大主机数 + +//ITS +#define MAX_PARKING_STATUS 8 //车位状态 0代表无车,1代表有车,2代表压线(优先级最高), 3特殊车位 +#define MAX_PARKING_NUM 4 //一个通道最大4个车位 (从左到右车位 数组0~3) + +#define MAX_ITS_SCENE_NUM 16 //最大场景数量 +#define MAX_SCENE_TIMESEG_NUM 16 //最大场景时间段数量 +#define MAX_IVMS_IP_CHANNEL 128 //最大IP通道数 +#define DEVICE_ID_LEN 48 //设备编号长度 +#define MONITORSITE_ID_LEN 48 //显示点编号长度 +#define MAX_AUXAREA_NUM 16 //辅助区域最大数目 +#define MAX_SLAVE_CHANNEL_NUM 16 //最大从通道数量 +#define MAX_DEVDESC_LEN 64 //设备描述信息最大长度 +#define ILLEGAL_LEN 32 //违法代码长度 +#define MAX_TRUCK_AXLE_NUM 10 //货车轴最大数 +#define MAX_CATEGORY_LEN 8 //车牌附加信息最大字符 +#define SERIAL_NO_LEN 16 //泊车位编号 + + +#define MAX_SECRETKEY_LEN 512 //最大秘钥长度 +#define MAX_INDEX_CODE_LEN 64 //最大序号长度 +#define MAX_ILLEGAL_LEN 64 //违法代码最大字符长度 +#define CODE_LEN 64 //授权码 +#define ALIAS_LEN 32 //别名,只读 +#define MAX_SCH_TASKS_NUM 10 + +#define MAX_SERVERID_LEN 64 //最大服务器ID的长度 +#define MAX_SERVERDOMAIN_LEN 128 //服务器域名最大长度 +#define MAX_AUTHENTICATEID_LEN 64 //认证ID最大长度 +#define MAX_AUTHENTICATEPASSWD_LEN 32 //认证密码最大长度 +#define MAX_SERVERNAME_LEN 64 //最大服务器用户名 +#define MAX_COMPRESSIONID_LEN 64 //编码ID的最大长度 +#define MAX_SIPSERVER_ADDRESS_LEN 128 //SIP服务器地址支持域名和IP地址 +//压线报警 +#define MAX_PlATE_NO_LEN 32 //车牌号码最大长度 2013-09-27 +#define UPNP_PORT_NUM 12 //upnp端口映射端口数目 + +#define MAX_PEOPLE_DETECTION_NUM 8 //最大人员检测区域数 + +#define MAX_NOTICE_NUMBER_LEN 32 //公告编号最大长度 +#define MAX_NOTICE_THEME_LEN 64 //公告主题最大长度 +#define MAX_NOTICE_DETAIL_LEN 1024 //公告详情最大长度 +#define MAX_NOTICE_PIC_NUM 6 //公告信息最大图片数量 +#define MAX_DEV_NUMBER_LEN 32 //设备编号最大长度 +#define LOCK_NAME_LEN 32 //锁名称 + + +#define HOLIDAY_GROUP_NAME_LEN 32 //假日组名称长度 +#define MAX_HOLIDAY_PLAN_NUM 16 //假日组最大假日计划数 +#define TEMPLATE_NAME_LEN 32 //计划模板名称长度 +#define MAX_HOLIDAY_GROUP_NUM 16 //计划模板最大假日组数 +#define DOOR_NAME_LEN 32 //门名称 +#define STRESS_PASSWORD_LEN 8 //胁迫密码长度 +#define SUPER_PASSWORD_LEN 8 //胁迫密码长度 +#define GROUP_NAME_LEN 32 //群组名称长度 +#define GROUP_COMBINATION_NUM 8 //群组组合数 +#define MULTI_CARD_GROUP_NUM 4 //单门最大多重卡组数 +#define ACS_CARD_NO_LEN 32 //门禁卡号长度 +#define NET_SDK_EMPLOYEE_NO_LEN 32 //工号长度 +#define NET_SDK_UUID_LEN 36 //UUID长度 +#define NET_SDK_EHOME_KEY_LEN 32 //EHome Key长度 +#define CARD_PASSWORD_LEN 8 //卡密码长度 +#define MAX_DOOR_NUM 32 //最大门数 +#define MAX_CARD_RIGHT_PLAN_NUM 4 //卡权限最大计划个数 +#define MAX_GROUP_NUM_128 128 //最大群组数 +#define MAX_CARD_READER_NUM 64 //最大读卡器数 +#define MAX_SNEAK_PATH_NODE 8 //最大后续读卡器数 +#define MAX_MULTI_DOOR_INTERLOCK_GROUP 8 //最大多门互锁组数 +#define MAX_INTER_LOCK_DOOR_NUM 8 //一个多门互锁组中最大互锁门数 +#define MAX_CASE_SENSOR_NUM 8 //最大case sensor触发器数 +#define MAX_DOOR_NUM_256 256 //最大门数 +#define MAX_READER_ROUTE_NUM 16 //最大刷卡循序路径 +#define MAX_FINGER_PRINT_NUM 10 //最大指纹个数 +#define MAX_CARD_READER_NUM_512 512 //最大读卡器数 +#define NET_SDK_MULTI_CARD_GROUP_NUM_20 20 //单门最大多重卡组数 + +#define ERROR_MSG_LEN 32 //下发错误信息 +#define MAX_DOOR_CODE_LEN 8 //房间代码长度 +#define MAX_LOCK_CODE_LEN 8 //锁代码长度 +#define PER_RING_PORT_NUM 2 //每个环的端口数 +#define SENSORNAME_LEN 32 //传感器名称长度 +#define MAX_SENSORDESCR_LEN 64 //传感器描述长度 +#define MAX_DNS_SERVER_NUM 2 //最大DNS个数 +#define SENSORUNIT_LEN 32 //最大单位长度 + +#define WEP_KEY_MAX_SIZE 32 //最大WEP加密密钥长度 +#define WEP_KEY_MAX_NUM 4 //最大WEP加密密钥个数 +#define WPA_KEY_MAX_SIZE 64 //最大WPA共享密钥长度 + +#define MAX_SINGLE_FTPPICNAME_LEN 20 //最大单个FTP通道名称 +#define MAX_CAMNAME_LEN 32 //最大通道名称 +#define MAX_FTPNAME_NUM 12 //TFP名称数 + + +#define MAX_IDCODE_LEN 128 // 识别码最大长度 +#define MAX_VERSIIN_LEN 64 //版本最大长度 +#define MAX_IDCODE_NUM 32 // 识别码个数 +#define SDK_LEN_2048 2048 +#define SDK_MAX_IP_LEN 48 + +#define RECT_POINT_NUM 4 //矩形角数 + +#define MAX_PUBLIC_KEY_LEN 512 // 最大公钥长度 +#define CHIP_SERIALNO_LEN 32 //加密芯片序列号长度 +#define ENCRYPT_DEV_ID_LEN 20 //设备ID长度 + +//MCU相关的 +#define MAX_SEARCH_ID_LEN 36 //搜索标识符最大长度 +#define TERMINAL_NAME_LEN 64 //终端名称长度 +#define MAX_URL_LEN 512 //URL长度 +#define REGISTER_NAME_LEN 64 //终端注册GK名称最大长度 + +//光纤 +#define MAX_PORT_NUM 64 //最大端口数 +#define MAX_SINGLE_CARD_PORT_NO 4 //光纤收发器单卡最大端口数 +#define MAX_FUNC_CARD_NUM 32 //光纤收发器最大功能卡数 +#define MAX_FC_CARD_NUM 33 //光纤收发器最大卡数 +#define MAX_REMARKS_LEN 128 //注释最大长度 +#define MAX_OUTPUT_PORT_NUM 32 //单路输出包含的最大输出端口数 +#define MAX_SINGLE_PORT_RECVCARD_NUM 64 //单个端口连接的最大接收卡数 +#define MAX_GAMMA_X_VALUE 256 //GAMMA表X轴取值个数 +#define NET_DEV_NAME_LEN 64 //设备名称长度 +#define NET_DEV_TYPE_NAME_LEN 64 //设备类型名称长度 +#define ABNORMAL_INFO_NUM 4 //异常时间段个数 + +#define PLAYLIST_NAME_LEN 64 //播放表名称长度 +#define PLAYLIST_ITEM_NUM 64 //播放项数目 + +//后端相关 +#define NET_SDK_MAX_LOGIN_PASSWORD_LEN 128 //用户登录密码最大长度 +#define NET_SDK_MAX_ANSWER_LEN 256 //安全问题答案最大长度 +#define NET_SDK_MAX_QUESTION_LIST_LEN 32//安全问题列表最大长度 + +#define MAX_SCREEN_AREA_NUM 128 //屏幕区域最大数量 +#define NET_SDK_MAX_THERMOMETRYALGNAME 128//测温算法库版本最大长度 +#define NET_SDK_MAX_SHIPSALGNAME 128//船只算法库版本最大长度 +#define NET_SDK_MAX_FIRESALGNAME 128//火点算法库版本最大长度 + +#define MAX_PASSPORT_NUM_LEN 16 //最大护照证件号长度 +#define MAX_PASSPORT_INFO_LEN 128 //最大护照通用信息长度 +#define MAX_PASSPORT_NAME_LEN 64 //最大护照姓名长度 +#define MAX_PASSPORT_MONITOR_LEN 1024 //最大护照监护信息长度 +#define MAX_NATIONALITY_LEN 16 //最大护照国籍长度 +#define MAX_PASSPORT_TYPE_LEN 4 //最大护照证件类型长度 +//修正后结束 +// struct +//报警和异常处理结构(子结构)(多处使用) +typedef struct +{ + DWORD dwHandleType; /*处理方式,处理方式的"或"结果*/ + /*0x00: 无响应*/ + /*0x01: 显示器上警告*/ + /*0x02: 声音警告*/ + /*0x04: 上传中心*/ + /*0x08: 触发报警输出*/ + /*0x10: Jpeg抓图并上传EMail*/ + BYTE byRelAlarmOut[MAX_ALARMOUT]; //报警触发的输出通道,报警触发的输出,为1表示触发该输出 +}NET_DVR_HANDLEEXCEPTION, *LPNET_DVR_HANDLEEXCEPTION; +//时间段(子结构) +typedef struct +{ + //开始时间 + BYTE byStartHour; + BYTE byStartMin; + //结束时间 + BYTE byStopHour; + BYTE byStopMin; +}NET_DVR_SCHEDTIME, *LPNET_DVR_SCHEDTIME; + +typedef struct +{ + BYTE byBrightness; /*亮度,0-255*/ + BYTE byContrast; /*对比度,0-255*/ + BYTE bySaturation; /*饱和度,0-255*/ + BYTE byHue; /*色调,0-255*/ +}NET_DVR_COLOR, *LPNET_DVR_COLOR; + +//NET_DVR_Login_V30()参数结构 +typedef struct tagNET_DVR_DEVICEINFO_V30 +{ + BYTE sSerialNumber[SERIALNO_LEN]; //序列号 + BYTE byAlarmInPortNum; //报警输入个数 + BYTE byAlarmOutPortNum; //报警输出个数 + BYTE byDiskNum; //硬盘个数 + BYTE byDVRType; //设备类型, 1:DVR 2:ATM DVR 3:DVS ...... + BYTE byChanNum; //模拟通道个数 + BYTE byStartChan; //起始通道号,例如DVS-1,DVR - 1 + BYTE byAudioChanNum; //语音通道数 + BYTE byIPChanNum; //最大数字通道个数,低位 + BYTE byZeroChanNum; //零通道编码个数 //2010-01-16 + BYTE byMainProto; //主码流传输协议类型 0-private, 1-rtsp,2-同时支持private和rtsp + BYTE bySubProto; //子码流传输协议类型0-private, 1-rtsp,2-同时支持private和rtsp + BYTE bySupport; + BYTE bySupport1; + BYTE bySupport2; + WORD wDevType; + BYTE bySupport3; + BYTE byMultiStreamProto;//是否支持多码流,按位表示,0-不支持,1-支持,bit1-码流3,bit2-码流4,bit7-主码流,bit-8子码流 + BYTE byStartDChan; //起始数字通道号,0表示无效 + BYTE byStartDTalkChan; //起始数字对讲通道号,区别于模拟对讲通道号,0表示无效 + BYTE byHighDChanNum; //数字通道个数,高位 + BYTE bySupport4; + BYTE byLanguageType; + BYTE byVoiceInChanNum; //音频输入通道数 + BYTE byStartVoiceInChanNo; //音频输入起始通道号 0表示无效 + BYTE bySupport5; + BYTE bySupport6; + BYTE byMirrorChanNum; //镜像通道个数,<录播主机中用于表示导播通道> + WORD wStartMirrorChanNo; //起始镜像通道号 + BYTE bySupport7; + BYTE byRes2; //保留 +}NET_DVR_DEVICEINFO_V30, *LPNET_DVR_DEVICEINFO_V30; + +typedef struct tagNET_DVR_DEVICEINFO_V40 +{ + NET_DVR_DEVICEINFO_V30 struDeviceV30; + BYTE bySupportLock; //设备支持锁定功能,该字段由SDK根据设备返回值来赋值的。bySupportLock为1时,dwSurplusLockTime和byRetryLoginTime有效 + BYTE byRetryLoginTime; //剩余可尝试登陆的次数,用户名,密码错误时,此参数有效 + BYTE byPasswordLevel; //admin密码安全等级0-无效,1-默认密码,2-有效密码,3-风险较高的密码。当用户的密码为出厂默认密码(12345)或者风险较高的密码时,上层客户端需要提示用户更改密码。 + BYTE byProxyType; //代理类型,0-不使用代理, 1-使用socks5代理, 2-使用EHome代理 + DWORD dwSurplusLockTime; //剩余时间,单位秒,用户锁定时,此参数有效 + BYTE byCharEncodeType; //字符编码类型 + BYTE bySupportDev5;//支持v50版本的设备参数获取,设备名称和设备类型名称长度扩展为64字节 + BYTE bySupport; //能力集扩展,位与结果:0- 不支持,1- 支持 + BYTE byLoginMode; //登录模式 0-Private登录 1-ISAPI登录 + DWORD dwOEMCode; + int iResidualValidity; //该用户密码剩余有效天数,单位:天,返回负值,表示密码已经超期使用,例如“-3表示密码已经超期使用3天” + BYTE byResidualValidity; // iResidualValidity字段是否有效,0-无效,1-有效 + BYTE byRes2[243]; +}NET_DVR_DEVICEINFO_V40, *LPNET_DVR_DEVICEINFO_V40; + +typedef void (*fLoginResultCallBack) (LONG lUserID, DWORD dwResult, LPNET_DVR_DEVICEINFO_V30 lpDeviceInfo , void* pUser); +typedef void (*REALDATACALLBACK) (LONG lPlayHandle, DWORD dwDataType, BYTE *pBuffer, DWORD dwBufSize, void* pUser); + +typedef struct tagNET_DVR_USER_LOGIN_INFO +{ + char sDeviceAddress[NET_DVR_DEV_ADDRESS_MAX_LEN]; + BYTE byUseTransport; //是否启用能力集透传,0--不启用透传,默认,1--启用透传 + WORD wPort; + char sUserName[NET_DVR_LOGIN_USERNAME_MAX_LEN]; + char sPassword[NET_DVR_LOGIN_PASSWD_MAX_LEN]; + fLoginResultCallBack cbLoginResult; + void *pUser; + BOOL bUseAsynLogin; + BYTE byProxyType; //0:不使用代理,1:使用标准代理,2:使用EHome代理 + BYTE byUseUTCTime; //0-不进行转换,默认,1-接口上输入输出全部使用UTC时间,SDK完成UTC时间与设备时区的转换,2-接口上输入输出全部使用平台本地时间,SDK完成平台本地时间与设备时区的转换 + BYTE byLoginMode; //0-Private 1-ISAPI 2-自适应 + BYTE byHttps; //0-不适用tls,1-使用tls 2-自适应 + LONG iProxyID; //代理服务器序号,添加代理服务器信息时,相对应的服务器数组下表值 + BYTE byVerifyMode; //认证方式,0-不认证,1-双向认证,2-单向认证;认证仅在使用TLS的时候生效; + BYTE byRes3[119]; +}NET_DVR_USER_LOGIN_INFO,*LPNET_DVR_USER_LOGIN_INFO; + +//图片质量 +typedef struct tagNET_DVR_JPEGPARA +{ + WORD wPicSize; + WORD wPicQuality; /* 图片质量系数 0-最好 1-较好 2-一般 */ +}NET_DVR_JPEGPARA, *LPNET_DVR_JPEGPARA; + +//软解码预览参数 +typedef struct tagNET_DVR_CLIENTINFO +{ + LONG lChannel; + LONG lLinkMode; + HWND hPlayWnd; + char* sMultiCastIP; + BYTE byProtoType; + BYTE byRes[3]; +}NET_DVR_CLIENTINFO, *LPNET_DVR_CLIENTINFO; + +#define STREAM_ID_LEN 32 + +//预览V40接口 +typedef struct tagNET_DVR_PREVIEWINFO +{ + LONG lChannel; + DWORD dwStreamType; + DWORD dwLinkMode; + HWND hPlayWnd; + DWORD bBlocked; + DWORD bPassbackRecord; + BYTE byPreviewMode; + BYTE byStreamID[STREAM_ID_LEN]; + BYTE byProtoType; + BYTE byRes1; + BYTE byVideoCodingType; + DWORD dwDisplayBufNum; + BYTE byNPQMode; + BYTE byRes[215]; +}NET_DVR_PREVIEWINFO, *LPNET_DVR_PREVIEWINFO; + +typedef struct tagNET_DVR_BUF_INFO +{ + void* pBuf; //缓冲区指针 + DWORD nLen; //缓冲区长度 +}NET_DVR_BUF_INFO, *LPNET_DVR_BUF_INFO; + +typedef struct tagNET_DVR_IN_PARAM +{ + NET_DVR_BUF_INFO struCondBuf; + NET_DVR_BUF_INFO struInParamBuf; + DWORD dwRecvTimeout; + BYTE byRes[32]; +}NET_DVR_IN_PARAM,LPNET_DVR_IN_PARAM; + +typedef struct tagNET_DVR_OUT_PARAM +{ + NET_DVR_BUF_INFO struOutBuf; + void* lpStatusList; + BYTE byRes[32]; +}NET_DVR_OUT_PARAM,LPNET_DVR_OUT_PARAM; + + +typedef struct tagNET_DVR_SETUPALARM_PARAM +{ + DWORD dwSize; + BYTE byLevel; //布防优先级,0-一等级(高),1-二等级(中),2-三等级(低) + BYTE byAlarmInfoType; //上传报警信息类型(抓拍机支持),0-老报警信息(NET_DVR_PLATE_RESULT),1-新报警信息(NET_ITS_PLATE_RESULT)2012-9-28 + BYTE byRetAlarmTypeV40; //0--返回NET_DVR_ALARMINFO_V30或NET_DVR_ALARMINFO, 1--设备支持NET_DVR_ALARMINFO_V40则返回NET_DVR_ALARMINFO_V40,不支持则返回NET_DVR_ALARMINFO_V30或NET_DVR_ALARMINFO + BYTE byRetDevInfoVersion; //CVR上传报警信息回调结构体版本号 0-COMM_ALARM_DEVICE, 1-COMM_ALARM_DEVICE_V40 + BYTE byRetVQDAlarmType; //VQD报警上传类型,0-上传报报警NET_DVR_VQD_DIAGNOSE_INFO,1-上传报警NET_DVR_VQD_ALARM + //1-表示人脸侦测报警扩展(INTER_FACE_DETECTION),0-表示原先支持结构(INTER_FACESNAP_RESULT) + BYTE byFaceAlarmDetection; + //Bit0- 表示二级布防是否上传图片: 0-上传,1-不上传 + //Bit1- 表示开启数据上传确认机制;0-不开启,1-开启 + //Bit6- 表示雷达检测报警(eventType:radarDetection)是否开启实时上传;0-不开启,1-开启(用于web插件实时显示雷达目标轨迹) + BYTE bySupport; + //断网续传类型 + //bit0-车牌检测(IPC) (0-不续传,1-续传) + //bit1-客流统计(IPC) (0-不续传,1-续传) + //bit2-热度图统计(IPC) (0-不续传,1-续传) + //bit3-人脸抓拍(IPC) (0-不续传,1-续传) + //bit4-人脸对比(IPC) (0-不续传,1-续传) + BYTE byBrokenNetHttp; + WORD wTaskNo; //任务处理号 和 (上传数据NET_DVR_VEHICLE_RECOG_RESULT中的字段dwTaskNo对应 同时 下发任务结构 NET_DVR_VEHICLE_RECOG_COND中的字段dwTaskNo对应) + BYTE byDeployType; //布防类型:0-客户端布防,1-实时布防 + BYTE bySubScription; //订阅,按位表示,未开启订阅不上报 //占位 + //Bit7-移动侦测人车分类是否传图;0-不传图(V30上报),1-传图(V40上报) + BYTE byRes1[2]; + BYTE byAlarmTypeURL;//bit0-表示人脸抓拍报警上传(INTER_FACESNAP_RESULT);0-表示二进制传输,1-表示URL传输(设备支持的情况下,设备支持能力根据具体报警能力集判断,同时设备需要支持URL的相关服务,当前是”云存储“) + //bit1-表示EVENT_JSON中图片数据长传类型;0-表示二进制传输,1-表示URL传输(设备支持的情况下,设备支持能力根据具体报警能力集判断) + //bit2 - 人脸比对(报警类型为COMM_SNAP_MATCH_ALARM)中图片数据上传类型:0 - 二进制传输,1 - URL传输 + //bit3 - 行为分析(报警类型为COMM_ALARM_RULE)中图片数据上传类型:0 - 二进制传输,1 - URL传输,本字段设备是否支持,对应软硬件能力集中节点是否返回且为true + BYTE byCustomCtrl;//Bit0- 表示支持副驾驶人脸子图上传: 0-不上传,1-上传 +}NET_DVR_SETUPALARM_PARAM, *LPNET_DVR_SETUPALARM_PARAM; + + +//单IO触发抓拍功能配置 +typedef struct tagNET_DVR_SNAPCFG +{ + DWORD dwSize; + BYTE byRelatedDriveWay;//触发IO关联的车道号 + BYTE bySnapTimes; //线圈抓拍次数,0-不抓拍,非0-连拍次数,目前最大5次 + WORD wSnapWaitTime; //抓拍等待时间,单位ms,取值范围[0,60000] + WORD wIntervalTime[MAX_INTERVAL_NUM];//连拍间隔时间,ms + DWORD dwSnapVehicleNum; //抓拍车辆序号。 + NET_DVR_JPEGPARA struJpegPara;//抓拍图片参数 + BYTE byRes2[16];//保留字节 +}NET_DVR_SNAPCFG, *LPNET_DVR_SNAPCFG; + + +//报警设备信息 +typedef struct +{ + BYTE byUserIDValid; /* userid是否有效 0-无效,1-有效 */ + BYTE bySerialValid; /* 序列号是否有效 0-无效,1-有效 */ + BYTE byVersionValid; /* 版本号是否有效 0-无效,1-有效 */ + BYTE byDeviceNameValid; /* 设备名字是否有效 0-无效,1-有效 */ + BYTE byMacAddrValid; /* MAC地址是否有效 0-无效,1-有效 */ + BYTE byLinkPortValid; /* login端口是否有效 0-无效,1-有效 */ + BYTE byDeviceIPValid; /* 设备IP是否有效 0-无效,1-有效 */ + BYTE bySocketIPValid; /* socket ip是否有效 0-无效,1-有效 */ + LONG lUserID; /* NET_DVR_Login()返回值, 布防时有效 */ + BYTE sSerialNumber[SERIALNO_LEN]; /* 序列号 */ + DWORD dwDeviceVersion; /* 版本信息 高16位表示主版本,低16位表示次版本*/ + char sDeviceName[NAME_LEN]; /* 设备名字 */ + BYTE byMacAddr[MACADDR_LEN]; /* MAC地址 */ + WORD wLinkPort; /* link port */ + char sDeviceIP[128]; /* IP地址 */ + char sSocketIP[128]; /* 报警主动上传时的socket IP地址 */ + BYTE byIpProtocol; /* Ip协议 0-IPV4, 1-IPV6 */ + BYTE byRes1[2]; + BYTE bJSONBroken; //JSON断网续传标志。0:不续传;1:续传 + WORD wSocketPort; + BYTE byRes2[6]; +}NET_DVR_ALARMER, *LPNET_DVR_ALARMER; + +//信号丢失报警(子结构) +typedef struct +{ + BYTE byEnableHandleVILost; /* 是否处理信号丢失报警 */ + NET_DVR_HANDLEEXCEPTION strVILostHandleType; /* 处理方式 */ + NET_DVR_SCHEDTIME struAlarmTime[MAX_DAYS][MAX_TIMESEGMENT];//布防时间 +}NET_DVR_VILOST, *LPNET_DVR_VILOST; + +//移动侦测(子结构) +typedef struct +{ + BYTE byMotionScope[18][22]; /*侦测区域,共有22*18个小宏块,为1表示改宏块是移动侦测区域,0-表示不是*/ + BYTE byMotionSensitive; /*移动侦测灵敏度, 0 - 5,越高越灵敏,0xff关闭*/ + BYTE byEnableHandleMotion; /* 是否处理移动侦测 */ + BYTE byEnableDisplay; /*启用移动侦测高亮显示,0-否,1-是*/ + char reservedData; + NET_DVR_HANDLEEXCEPTION strMotionHandleType; /* 处理方式 */ + NET_DVR_SCHEDTIME struAlarmTime[MAX_DAYS][MAX_TIMESEGMENT];//布防时间 + BYTE byRelRecordChan[MAX_CHANNUM]; //报警触发的录象通道,为1表示触发该通道 +}NET_DVR_MOTION, *LPNET_DVR_MOTION; +//遮挡报警(子结构) 区域大小704*576 +typedef struct +{ + DWORD dwEnableHideAlarm; /* 是否启动遮挡报警 ,0-否,1-低灵敏度 2-中灵敏度 3-高灵敏度*/ + WORD wHideAlarmAreaTopLeftX; /* 遮挡区域的x坐标 */ + WORD wHideAlarmAreaTopLeftY; /* 遮挡区域的y坐标 */ + WORD wHideAlarmAreaWidth; /* 遮挡区域的宽 */ + WORD wHideAlarmAreaHeight; /*遮挡区域的高*/ + NET_DVR_HANDLEEXCEPTION strHideAlarmHandleType; /* 处理方式 */ + NET_DVR_SCHEDTIME struAlarmTime[MAX_DAYS][MAX_TIMESEGMENT];//布防时间 +}NET_DVR_HIDEALARM, *LPNET_DVR_HIDEALARM; + +typedef struct +{ + NET_DVR_COLOR struColor[MAX_TIMESEGMENT_V30];/*图象参数(第一个有效,其他三个保留)*/ + NET_DVR_SCHEDTIME struHandleTime[MAX_TIMESEGMENT_V30];/*处理时间段(保留)*/ +}NET_DVR_VICOLOR, *LPNET_DVR_VICOLOR; + +//遮挡区域(子结构) +typedef struct +{ + WORD wHideAreaTopLeftX; /* 遮挡区域的x坐标 */ + WORD wHideAreaTopLeftY; /* 遮挡区域的y坐标 */ + WORD wHideAreaWidth; /* 遮挡区域的宽 */ + WORD wHideAreaHeight; /*遮挡区域的高*/ +}NET_DVR_SHELTER, *LPNET_DVR_SHELTER; + +typedef struct +{ + BYTE byRed; //RGB颜色三分量中的红色 + BYTE byGreen; //RGB颜色三分量中的绿色 + BYTE byBlue; //RGB颜色三分量中的蓝色 + BYTE byRes; //保留 +}NET_DVR_RGB_COLOR, *LPNET_DVR_RGB_COLOR; + +typedef struct +{ + BYTE byObjectSize;//占比参数(0~100) + BYTE byMotionSensitive; /*移动侦测灵敏度, 0 - 5,越高越灵敏,0xff关闭*/ + BYTE byRes[6]; +}NET_DVR_DNMODE, *LPNET_DVR_DNMODE; + +//区域框结构 +typedef struct tagNET_VCA_RECT +{ + float fX; //边界框左上角点的X轴坐标, 0.000~1 + float fY; //边界框左上角点的Y轴坐标, 0.000~1 + float fWidth; //边界框的宽度, 0.000~1 + float fHeight; //边界框的高度, 0.000~1 +}NET_VCA_RECT, *LPNET_VCA_RECT; + +//球机位置信息 +typedef struct +{ + WORD wAction;//获取时该字段无效 + WORD wPanPos;//水平参数 + WORD wTiltPos;//垂直参数 + WORD wZoomPos;//变倍参数 +}NET_DVR_PTZPOS, *LPNET_DVR_PTZPOS; +//球机范围信息 +typedef struct +{ + WORD wPanPosMin;//水平参数min + WORD wPanPosMax;//水平参数max + WORD wTiltPosMin;//垂直参数min + WORD wTiltPosMax;//垂直参数max + WORD wZoomPosMin;//变倍参数min + WORD wZoomPosMax;//变倍参数max +}NET_DVR_PTZSCOPE, *LPNET_DVR_PTZSCOPE; + +typedef struct +{ + BYTE byAreaNo;//区域编号(IPC- 1~8) + BYTE byRes[3]; + NET_VCA_RECT struRect;//单个区域的坐标信息(矩形) size = 16; + NET_DVR_DNMODE struDayNightDisable;//关闭模式 + NET_DVR_DNMODE struDayModeParam;//白天模式 + NET_DVR_DNMODE struNightModeParam;//夜晚模式 + BYTE byRes1[8]; +}NET_DVR_MOTION_MULTI_AREAPARAM, *LPNET_DVR_MOTION_MULTI_AREAPARAM; + +typedef struct +{ + BYTE byHour;//0~24 + BYTE byMinute;//0~60 + BYTE bySecond;//0~60 + BYTE byRes; + WORD wMilliSecond; //0~1000 + BYTE byRes1[2]; +}NET_DVR_DAYTIME,*LPNET_DVR_DAYTIME; + +typedef struct +{ + NET_DVR_DAYTIME struStartTime; //开始时间 + NET_DVR_DAYTIME struStopTime; //结束时间 +}NET_DVR_SCHEDULE_DAYTIME, *LPNET_DVR_SCHEDULE_DAYTIME; + +typedef struct +{ + BYTE byDayNightCtrl;//日夜控制 0~关闭,1~自动切换,2~定时切换(默认关闭) + BYTE byAllMotionSensitive; /*移动侦测灵敏度, 0 - 5,越高越灵敏,0xff关闭,全部区域的灵敏度范围*/ + BYTE byRes[2];// + NET_DVR_SCHEDULE_DAYTIME struScheduleTime;//切换时间 16 + NET_DVR_MOTION_MULTI_AREAPARAM struMotionMultiAreaParam[MAX_MULTI_AREA_NUM];//最大支持24个区域 + BYTE byRes1[60]; +}NET_DVR_MOTION_MULTI_AREA,*LPNET_DVR_MOTION_MULTI_AREA; //1328 + +typedef struct +{ + BYTE byMotionScope[64][96]; /*侦测区域,0-96位,表示64行,共有96*64个小宏块,目前有效的是22*18,为1表示是移动侦测区域,0-表示不是*/ + BYTE byMotionSensitive; /*移动侦测灵敏度, 0 - 5,越高越灵敏,0xff关闭*/ + BYTE byRes[3]; +}NET_DVR_MOTION_SINGLE_AREA, *LPNET_DVR_MOTION_SINGLE_AREA; + +typedef struct +{ + NET_DVR_MOTION_SINGLE_AREA struMotionSingleArea; //普通模式下的单区域设 + NET_DVR_MOTION_MULTI_AREA struMotionMultiArea; //专家模式下的多区域设置 +}NET_DVR_MOTION_MODE_PARAM, *LPNET_DVR_MOTION_MODE_PARAM; + +typedef struct +{ + DWORD dwEnableHideAlarm; /* 是否启动遮挡报警,0-否,1-低灵敏度,2-中灵敏度,3-高灵敏度*/ + WORD wHideAlarmAreaTopLeftX; /* 遮挡区域的x坐标 */ + WORD wHideAlarmAreaTopLeftY; /* 遮挡区域的y坐标 */ + WORD wHideAlarmAreaWidth; /* 遮挡区域的宽 */ + WORD wHideAlarmAreaHeight; /*遮挡区域的高*/ + /* 信号丢失触发报警输出 */ + DWORD dwHandleType; //异常处理,异常处理方式的"或"结果 + /*0x00: 无响应*/ + /*0x01: 显示器上警告*/ + /*0x02: 声音警告*/ + /*0x04: 上传中心*/ + /*0x08: 触发报警输出*/ + /*0x10: 触发JPRG抓图并上传Email*/ + /*0x20: 无线声光报警器联动*/ + /*0x40: 联动电子地图(目前只有PCNVR支持)*/ + /*0x200: 抓图并上传FTP*/ + /*0x1000:抓图上传到云*/ + DWORD dwMaxRelAlarmOutChanNum ; //触发的报警输出通道数(只读)最大支持数量 + DWORD dwRelAlarmOut[MAX_ALARMOUT_V40]; /*触发报警输出号,按值表示,采用紧凑型排列,从下标0 - dwRelAlarmOut -1有效,如果中间遇到0xffffffff,则后续无效*/ + NET_DVR_SCHEDTIME struAlarmTime[MAX_DAYS][MAX_TIMESEGMENT_V30]; /*布防时间*/ + BYTE byRes[64]; //保留 +}NET_DVR_HIDEALARM_V40,*LPNET_DVR_HIDEALARM_V40; //遮挡报警 + +typedef struct +{ + NET_DVR_MOTION_MODE_PARAM struMotionMode; //(5.1.0新增) + BYTE byEnableHandleMotion; /* 是否处理移动侦测 0-否 1-是*/ + BYTE byEnableDisplay; /*启用移动侦测高亮显示,0-否,1-是*/ + BYTE byConfigurationMode; //0~普通,1~专家(5.1.0新增) + BYTE byKeyingEnable; //启用键控移动侦测 0-不启用,1-启用 + /* 异常处理方式 */ + DWORD dwHandleType; //异常处理,异常处理方式的"或"结果 + /*0x00: 无响应*/ + /*0x01: 显示器上警告*/ + /*0x02: 声音警告*/ + /*0x04: 上传中心*/ + /*0x08: 触发报警输出*/ + /*0x10: 触发JPRG抓图并上传Email*/ + /*0x20: 无线声光报警器联动*/ + /*0x40: 联动电子地图(目前只有PCNVR支持)*/ + /*0x200: 抓图并上传FTP*/ + /*0x1000: 抓图上传到云*/ + DWORD dwMaxRelAlarmOutChanNum ; //触发的报警输出通道数(只读)最大支持数量 + DWORD dwRelAlarmOut[MAX_ALARMOUT_V40]; //实际触发的报警输出号,按值表示,采用紧凑型排列,从下标0 - dwRelAlarmOut -1有效,如果中间遇到0xffffffff,则后续无效 + NET_DVR_SCHEDTIME struAlarmTime[MAX_DAYS][MAX_TIMESEGMENT_V30]; /*布防时间*/ + /*触发的录像通道*/ + DWORD dwMaxRecordChanNum; //设备支持的最大关联录像通道数-只读 + DWORD dwRelRecordChan[MAX_CHANNUM_V40]; /* 实际触发录像通道,按值表示,采用紧凑型排列,从下标0 - dwRelRecordChan -1有效,如果中间遇到0xffffffff,则后续无效*/ + BYTE byDiscardFalseAlarm; //启用去误报 0-无效,1-不启用,2-启用 + BYTE byRes[127]; //保留字节 +}NET_DVR_MOTION_V40,*LPNET_DVR_MOTION_V40; + +typedef struct +{ + DWORD dwEnableVILostAlarm; /* 是否启动信号丢失报警 ,0-否,1-是*/ + /* 信号丢失触发报警输出 */ + DWORD dwHandleType; //异常处理,异常处理方式的"或"结果 + /*0x00: 无响应*/ + /*0x01: 显示器上警告*/ + /*0x02: 声音警告*/ + /*0x04: 上传中心*/ + /*0x08: 触发报警输出*/ + /*0x10: 触发JPRG抓图并上传Email*/ + /*0x20: 无线声光报警器联动*/ + /*0x40: 联动电子地图(目前只有PCNVR支持)*/ + /*0x200: 抓图并上传FTP*/ + /*0x1000:抓图上传到云*/ + DWORD dwMaxRelAlarmOutChanNum ; //触发的报警输出通道数(只读)最大支持数量 + DWORD dwRelAlarmOut[MAX_ALARMOUT_V40]; /*触发报警输出号,按值表示,采用紧凑型排列,从下标0 - dwRelAlarmOut -1有效,如果中间遇到0xffffffff,则后续无效*/ + NET_DVR_SCHEDTIME struAlarmTime[MAX_DAYS][MAX_TIMESEGMENT_V30]; /*布防时间*/ + BYTE byVILostAlarmThreshold; /*信号丢失报警阈值,当值低于阈值,认为信号丢失,取值0-99*/ + BYTE byRes[63]; //保留 +}NET_DVR_VILOST_V40,*LPNET_DVR_VILOST_V40; //信号丢失报警 + +//DVR设备参数 +typedef struct +{ + DWORD dwSize; + BYTE sDVRName[NAME_LEN]; //DVR名称 + DWORD dwDVRID; //DVR ID,用于遥控器 //V1.4(0-99), V1.5(0-255) + DWORD dwRecycleRecord; //是否循环录像,0:不是; 1:是 + //以下不可更改 + BYTE sSerialNumber[SERIALNO_LEN]; //序列号 + DWORD dwSoftwareVersion; //软件版本号,高16位是主版本,低16位是次版本 + DWORD dwSoftwareBuildDate; //软件生成日期,0xYYYYMMDD + DWORD dwDSPSoftwareVersion; //DSP软件版本,高16位是主版本,低16位是次版本 + DWORD dwDSPSoftwareBuildDate; // DSP软件生成日期,0xYYYYMMDD + DWORD dwPanelVersion; // 前面板版本,高16位是主版本,低16位是次版本 + DWORD dwHardwareVersion; // 硬件版本,高16位是主版本,低16位是次版本 + BYTE byAlarmInPortNum; //DVR报警输入个数 + BYTE byAlarmOutPortNum; //DVR报警输出个数 + BYTE byRS232Num; //DVR 232串口个数 + BYTE byRS485Num; //DVR 485串口个数 + BYTE byNetworkPortNum; //网络口个数 + BYTE byDiskCtrlNum; //DVR 硬盘控制器个数 + BYTE byDiskNum; //DVR 硬盘个数 + BYTE byDVRType; //DVR类型, 1:DVR 2:ATM DVR 3:DVS ...... + BYTE byChanNum; //DVR 通道个数 + BYTE byStartChan; //起始通道号,例如DVS-1,DVR - 1 + BYTE byDecordChans; //DVR 解码路数 + BYTE byVGANum; //VGA口的个数 + BYTE byUSBNum; //USB口的个数 + BYTE byAuxoutNum; //辅口的个数 + BYTE byAudioNum; //语音口的个数 + BYTE byIPChanNum; //最大数字通道数 +}NET_DVR_DEVICECFG, *LPNET_DVR_DEVICECFG; + + +//DVR设备参数 +typedef struct +{ + DWORD dwSize; + BYTE sDVRName[NAME_LEN]; //DVR名称 + DWORD dwDVRID; //DVR ID,用于遥控器 //V1.4(0-99), V1.5(0-255) + DWORD dwRecycleRecord; //是否循环录像,0:不是; 1:是 + //以下不可更改 + BYTE sSerialNumber[SERIALNO_LEN]; //序列号 + DWORD dwSoftwareVersion; //软件版本号,高16位是主版本,低16位是次版本 + DWORD dwSoftwareBuildDate; //软件生成日期,0xYYYYMMDD + DWORD dwDSPSoftwareVersion; //DSP软件版本,高16位是主版本,低16位是次版本 + DWORD dwDSPSoftwareBuildDate; // DSP软件生成日期,0xYYYYMMDD + DWORD dwPanelVersion; // 前面板版本,高16位是主版本,低16位是次版本 + DWORD dwHardwareVersion; // 硬件版本,高16位是主版本,低16位是次版本 + BYTE byAlarmInPortNum; //DVR报警输入个数 + BYTE byAlarmOutPortNum; //DVR报警输出个数 + BYTE byRS232Num; //DVR 232串口个数 + BYTE byRS485Num; //DVR 485串口个数 + BYTE byNetworkPortNum; //网络口个数 + BYTE byDiskCtrlNum; //DVR 硬盘控制器个数 + BYTE byDiskNum; //DVR 硬盘个数 + BYTE byDVRType; //DVR类型, 1:DVR 2:ATM DVR 3:DVS ...... + BYTE byChanNum; //DVR 通道个数 + BYTE byStartChan; //起始通道号,例如DVS-1,DVR - 1 + BYTE byDecordChans; //DVR 解码路数 + BYTE byVGANum; //VGA口的个数 + BYTE byUSBNum; //USB口的个数 + BYTE byAuxoutNum; //辅口的个数 + BYTE byAudioNum; //语音口的个数 + BYTE byIPChanNum; //最大数字通道数 低8位,高8位见byHighIPChanNum + BYTE byZeroChanNum; //零通道编码个数 + BYTE bySupport; //能力,位与结果为0表示不支持,1表示支持, + //bySupport & 0x1, 表示是否支持智能搜索 + //bySupport & 0x2, 表示是否支持备份 + //bySupport & 0x4, 表示是否支持压缩参数能力获取 + //bySupport & 0x8, 表示是否支持多网卡 + //bySupport & 0x10, 表示支持远程SADP + //bySupport & 0x20, 表示支持Raid卡功能 + //bySupport & 0x40, 表示支持IPSAN搜索 + //bySupport & 0x80, 表示支持rtp over rtsp + BYTE byEsataUseage; //Esata的默认用途,0-默认备份,1-默认录像 + BYTE byIPCPlug; //0-关闭即插即用,1-打开即插即用 + BYTE byStorageMode; //0-盘组模式,1-磁盘配额, 2抽帧模式, 3-自动 + BYTE bySupport1; //能力,位与结果为0表示不支持,1表示支持 + //bySupport1 & 0x1, 表示是否支持snmp v30 + //bySupport1 & 0x2, 支持区分回放和下载 + //bySupport1 & 0x4, 是否支持布防优先级 + //bySupport1 & 0x8, 智能设备是否支持布防时间段扩展 + //bySupport1 & 0x10, 表示是否支持多磁盘数(超过33个) + //bySupport1 & 0x20, 表示是否支持rtsp over http + WORD wDevType;//设备型号 + BYTE byDevTypeName[DEV_TYPE_NAME_LEN];//设备型号名称 + BYTE bySupport2; //能力集扩展,位与结果为0表示不支持,1表示支持 + //bySupport2 & 0x1, 表示是否支持扩展的OSD字符叠加(终端和抓拍机扩展区分) + BYTE byAnalogAlarmInPortNum; //模拟报警输入个数 + BYTE byStartAlarmInNo; //模拟报警输入起始号 + BYTE byStartAlarmOutNo; //模拟报警输出起始号 + BYTE byStartIPAlarmInNo; //IP报警输入起始号 + BYTE byStartIPAlarmOutNo; //IP报警输出起始号 + BYTE byHighIPChanNum; //数字通道个数,高8位 + BYTE byEnableRemotePowerOn;//是否启用在设备休眠的状态下远程开机功能,0-不启用,1-启用 + WORD wDevClass; //设备大类备是属于哪个产品线,0 保留,1-50 DVR,51-100 DVS,101-150 NVR,151-200 IPC,65534 其他,具体分类方法见《设备类型对应序列号和类型值.docx》 + BYTE byRes2[6]; //保留 +}NET_DVR_DEVICECFG_V40, *LPNET_DVR_DEVICECFG_V40; + +//通道图象结构(SDK_V13及之前版本) +typedef struct +{ + DWORD dwSize; + BYTE sChanName[NAME_LEN]; + DWORD dwVideoFormat; /* 只读 视频制式 1-NTSC 2-PAL*/ + BYTE byBrightness; /*亮度,0-255*/ + BYTE byContrast; /*对比度,0-255*/ + BYTE bySaturation; /*饱和度,0-255 */ + BYTE byHue; /*色调,0-255*/ + //显示通道名 + DWORD dwShowChanName; // 预览的图象上是否显示通道名称,0-不显示,1-显示 区域大小704*576 + WORD wShowNameTopLeftX; /* 通道名称显示位置的x坐标 */ + WORD wShowNameTopLeftY; /* 通道名称显示位置的y坐标 */ + //信号丢失报警 + NET_DVR_VILOST struVILost; + //移动侦测 + NET_DVR_MOTION struMotion; + //遮挡报警 + NET_DVR_HIDEALARM struHideAlarm; + //遮挡 区域大小704*576 + DWORD dwEnableHide; /* 是否启动遮挡 ,0-否,1-是*/ + WORD wHideAreaTopLeftX; /* 遮挡区域的x坐标 */ + WORD wHideAreaTopLeftY; /* 遮挡区域的y坐标 */ + WORD wHideAreaWidth; /* 遮挡区域的宽 */ + WORD wHideAreaHeight; /*遮挡区域的高*/ + //OSD + DWORD dwShowOsd;// 预览的图象上是否显示OSD,0-不显示,1-显示 区域大小704*576 + WORD wOSDTopLeftX; /* OSD的x坐标 */ + WORD wOSDTopLeftY; /* OSD的y坐标 */ + BYTE byOSDType; /* OSD类型(主要是年月日格式) */ + /* 0: XXXX-XX-XX 年月日 */ + /* 1: XX-XX-XXXX 月日年 */ + /* 2: XXXX年XX月XX日 */ + /* 3: XX月XX日XXXX年 */ + /* 4: XX-XX-XXXX 日月年*/ + /* 5: XX日XX月XXXX年 */ + /*6: xx/xx/xxxx(月/日/年) */ + /*7: xxxx/xx/xx(年/月/日) */ + /*8: xx/xx/xxxx(日/月/年)*/ + BYTE byDispWeek; /* 是否显示星期 */ + BYTE byOSDAttrib; /* OSD属性:透明,闪烁 */ + /* 1: 透明,闪烁 */ + /* 2: 透明,不闪烁 */ + /* 3: 闪烁,不透明 */ + /* 4: 不透明,不闪烁 */ + char reservedData2; +}NET_DVR_PICCFG, *LPNET_DVR_PICCFG; + + +typedef struct +{ + DWORD dwSize; + BYTE sChanName[NAME_LEN]; + DWORD dwVideoFormat; /* 只读 视频制式 1-NTSC 2-PAL */ + NET_DVR_VICOLOR struViColor;// 图像参数按时间段设置 + //显示通道名 + DWORD dwShowChanName; // 预览的图象上是否显示通道名称,0-不显示,1-显示 + WORD wShowNameTopLeftX; /* 通道名称显示位置的x坐标 */ + WORD wShowNameTopLeftY; /* 通道名称显示位置的y坐标 */ + //隐私遮挡 + DWORD dwEnableHide; /* 是否启动遮挡 ,0-否,1-是*/ + NET_DVR_SHELTER struShelter[MAX_SHELTERNUM]; + //OSD + DWORD dwShowOsd;// 预览的图象上是否显示OSD,0-不显示,1-显示 + WORD wOSDTopLeftX; /* OSD的x坐标 */ + WORD wOSDTopLeftY; /* OSD的y坐标 */ + BYTE byOSDType; /* OSD类型(主要是年月日格式) */ + /* 0: XXXX-XX-XX 年月日 */ + /* 1: XX-XX-XXXX 月日年 */ + /* 2: XXXX年XX月XX日 */ + /* 3: XX月XX日XXXX年 */ + /* 4: XX-XX-XXXX 日月年*/ + /* 5: XX日XX月XXXX年 */ + /*6: xx/xx/xxxx(月/日/年) */ + /*7: xxxx/xx/xx(年/月/日) */ + /*8: xx/xx/xxxx(日/月/年)*/ + BYTE byDispWeek; /* 是否显示星期 */ + BYTE byOSDAttrib; /* OSD属性:透明,闪烁 */ + /* 0: 不显示OSD */ + /* 1: 透明,闪烁 */ + /* 2: 透明,不闪烁 */ + /* 3: 不透明,闪烁 */ + /* 4: 不透明,不闪烁 */ + BYTE byHourOSDType; /* OSD小时制:0-24小时制,1-12小时制 */ + BYTE byFontSize; //16*16(中)/8*16(英),1-32*32(中)/16*32(英),2-64*64(中)/32*64(英) 3-48*48(中)/24*48(英) 4-24*24(中)/12*24(英) 5-96*96(中)/48*96(英) 6-128*128(中)/64*128(英) 7-80*80(中)/40*80(英) 8-112*112(中)/56*112(英) 0xff-自适应(adaptive) + BYTE byOSDColorType; //0-默认(黑白);1-自定义;2-勾边 + /*当对齐方式选择国标模式时,可以分别对右下角、左下角两个区域做字符叠加。 + 右下角区域: + 共支持6行字符叠加,可以通过NET_DVR_SET_SHOWSTRING_V30/ NET_DVR_GET_SHOWSTRING_V30字符叠加接口,对应NET_DVR_SHOWSTRINGINFO结构体数组中的第0至第5个下标的值。叠加字符的方式为从下到上的方式。 + 左下角区域: + 共支持2行字符叠加,可以通过NET_DVR_SET_SHOWSTRING_V3/ NET_DVR_GET_SHOWSTRING_V30字符叠加接口,对应NET_DVR_SHOWSTRINGINFO结构体数组中的第6和第7个下标的值。叠加字符的方式为从下到上的方式。 + */ + BYTE byAlignment;//对齐方式 0-自适应,1-右对齐, 2-左对齐,3-国标模式,4-全部右对齐(包含叠加字符、时间以及标题等所有OSD字符),5-全部左对齐(包含叠加字符、时间以及标题等所有OSD字符) + BYTE byOSDMilliSecondEnable;//视频叠加时间支持毫秒;0~不叠加, 1-叠加 + NET_DVR_VILOST_V40 struVILost; //视频信号丢失报警(支持组) + NET_DVR_VILOST_V40 struAULost; /*音频信号丢失报警(支持组)*/ + NET_DVR_MOTION_V40 struMotion; //移动侦测报警(支持组) + NET_DVR_HIDEALARM_V40 struHideAlarm; //遮挡报警(支持组) + NET_DVR_RGB_COLOR struOsdColor;//OSD颜色 + DWORD dwBoundary; //边界值,左对齐,右对齐以及国标模式的边界值,0-表示默认值,单位:像素;在国标模式下,单位修改为字符个数(范围是,0,1,2) + NET_DVR_RGB_COLOR struOsdBkColor; //自定义OSD背景色 + BYTE byOSDBkColorMode; //OSD背景色模式,0-默认,1-自定义OSD背景色 + BYTE byUpDownBoundary; //上下最小边界值选项,单位为字符个数(范围是,0,1,2),国标模式下无效。byAlignment=3该字段无效,通过dwBoundary进行边界配置,.byAlignment不等于3的情况下, byUpDownBoundary/byLeftRightBoundary配置成功后,dwBoundary值将不生效 + BYTE byLeftRightBoundary; //左右最小边界值选项,单位为字符个数(范围是,0,1,2), 国标模式下无效。byAlignment=3该字段无效,通过dwBoundary进行边界配置,.byAlignment不等于3的情况下, byUpDownBoundary/byLeftRightBoundary配置成功后,dwBoundary值将不生效 + BYTE byAngleEnabled;//OSD是否叠加俯仰角信息,0~不叠加, 1-叠加 + WORD wTiltAngleTopLeftX; /* 俯仰角信息显示位置的x坐标 */ + WORD wTiltAngleTopLeftY; /* 俯仰角信息显示位置的y坐标 */ + BYTE byRes[108]; +}NET_DVR_PICCFG_V40,*LPNET_DVR_PICCFG_V40; + + +//sdk function +DWORD NET_DVR_GetSDKVersion(); +DWORD NET_DVR_GetSDKBuildVersion(); +int NET_DVR_IsSupport(); + +// sdk init +BOOL NET_DVR_Init(); +BOOL NET_DVR_Cleanup(); + +// login device +LONG NET_DVR_Login_V30(char *sDVRIP, WORD wDVRPort, char *sUserName, char *sPassword, LPNET_DVR_DEVICEINFO_V30 lpDeviceInfo); +LONG NET_DVR_Login_V40(LPNET_DVR_USER_LOGIN_INFO pLoginInfo,LPNET_DVR_DEVICEINFO_V40 lpDeviceInfo); +BOOL NET_DVR_Logout(LONG lUserID); +BOOL NET_DVR_Logout_V30(LONG lUserID); + +// connect device +BOOL NET_DVR_SetConnectTime(DWORD dwWaitTime, DWORD dwTryTimes); +BOOL NET_DVR_SetReconnect(DWORD dwInterval, BOOL bEnableRecon); + +// sdk capture +BOOL NET_DVR_CaptureJPEGPicture(LONG lUserID, LONG lChannel, LPNET_DVR_JPEGPARA lpJpegPara, char *sPicFileName); +BOOL NET_DVR_CaptureJPEGPicture(LONG lUserID, LONG lChannel, LPNET_DVR_JPEGPARA lpJpegPara, char *sPicFileName); + +// stream play control +LONG NET_DVR_RealPlay_V30(LONG lUserID, LPNET_DVR_CLIENTINFO lpClientInfo, void(*fRealDataCallBack_V30) (LONG lRealHandle, DWORD dwDataType, BYTE *pBuffer, DWORD dwBufSize, void* pUser), void* pUser, BOOL bBlocked); +BOOL NET_DVR_ClosePreview(LONG lUserID, DWORD nSessionID); +BOOL NET_DVR_ClosePlayBack(LONG lUserID, DWORD nSessionID); +LONG NET_DVR_RealPlay_V40(LONG lUserID, LPNET_DVR_PREVIEWINFO lpPreviewInfo, REALDATACALLBACK fRealDataCallBack_V30, void* pUser); +BOOL NET_DVR_StopRealPlay(LONG lRealHandle); + +BOOL NET_DVR_SaveRealData(LONG lRealHandle,char *sFileName); +BOOL NET_DVR_StopSaveRealData(LONG lRealHandle); + +// device ptz control +BOOL NET_DVR_PTZControlWithSpeed(LONG lRealHandle, DWORD dwPTZCommand, DWORD dwStop, DWORD dwSpeed); +BOOL NET_DVR_PTZControlWithSpeed_Other(LONG lUserID, LONG lChannel, DWORD dwPTZCommand, DWORD dwStop, DWORD dwSpeed); + //云台控制相关接口 +BOOL NET_DVR_PTZControl(LONG lRealHandle, DWORD dwPTZCommand, DWORD dwStop); + +BOOL NET_DVR_PTZControl_Other(LONG lUserID, LONG lChannel, DWORD dwPTZCommand, DWORD dwStop); + +DWORD NET_DVR_GetLastError(); + +// alarm +BOOL NET_DVR_GetDeviceAbility(LONG lUserID, DWORD dwAbilityType, char* pInBuf, DWORD dwInLength, char* pOutBuf, DWORD dwOutLength); + +BOOL NET_DVR_GetDVRConfig(LONG lUserID, DWORD dwCommand,LONG lChannel, LPVOID lpOutBuffer, DWORD dwOutBufferSize, LPDWORD lpBytesReturned); +BOOL NET_DVR_SetDVRConfig(LONG lUserID, DWORD dwCommand,LONG lChannel, LPVOID lpInBuffer, DWORD dwInBufferSize); + +BOOL NET_DVR_GetDeviceConfig(LONG lUserID, DWORD dwCommand, DWORD dwCount, LPVOID lpInBuffer, DWORD dwInBufferSize, LPVOID lpStatusList, LPVOID lpOutBuffer, DWORD dwOutBufferSize); +BOOL NET_DVR_SetDeviceConfigEx(LONG lUserID, DWORD dwCommand, DWORD dwCount, NET_DVR_IN_PARAM *lpInParam, NET_DVR_OUT_PARAM *lpOutParam); + +// 报警消息回调 +typedef void (CALLBACK *MSGCallBack)(LONG lCommand, NET_DVR_ALARMER *pAlarmer, char *pAlarmInfo, DWORD dwBufLen, void* pUser); +BOOL NET_DVR_SetDVRMessageCallBack_V30(MSGCallBack fMessageCallBack, void* pUser); +typedef BOOL (CALLBACK *MSGCallBack_V31)(LONG lCommand, NET_DVR_ALARMER *pAlarmer, char *pAlarmInfo, DWORD dwBufLen, void* pUser); +BOOL NET_DVR_SetDVRMessageCallBack_V31(MSGCallBack_V31 fMessageCallBack, void* pUser); +BOOL NET_DVR_SetDVRMessageCallBack_V50(int iIndex, MSGCallBack fMessageCallBack, void* pUser); +BOOL NET_DVR_SetDVRMessageCallBack_V51(int iIndex, MSGCallBack fMsgCallBack, void* pUser); + + +// 报警消息监听 +BOOL NET_DVR_StartListen(char *sLocalIP,WORD wLocalPort); +BOOL NET_DVR_StopListen(); + +LONG NET_DVR_StartListen_V30(char *sLocalIP, WORD wLocalPort, MSGCallBack DataCallback, void* pUserData); +BOOL NET_DVR_StopListen_V30(LONG lListenHandle); +BOOL NET_DVR_ContinuousShoot(LONG lUserID, LPNET_DVR_SNAPCFG lpInter); + +//报警 +LONG NET_DVR_SetupAlarmChan(LONG lUserID); +BOOL NET_DVR_CloseAlarmChan(LONG lAlarmHandle); +LONG NET_DVR_SetupAlarmChan_V30(LONG lUserID); +BOOL NET_DVR_CloseAlarmChan_V30(LONG lAlarmHandle); +LONG NET_DVR_SetupAlarmChan_V41(LONG lUserID, LPNET_DVR_SETUPALARM_PARAM lpSetupParam); + + +// car capture + +//BOOL NET_DVR_ManualSnap(LONG lUserID, NET_DVR_MANUALSNAP const* lpInter, LPNET_DVR_PLATE_RESULT lpOuter); +BOOL NET_DVR_ContinuousShoot(LONG lUserID, LPNET_DVR_SNAPCFG lpInter); + +#endif \ No newline at end of file diff --git a/plugin/hiksdk/index.go b/plugin/hiksdk/index.go new file mode 100644 index 0000000..460aa65 --- /dev/null +++ b/plugin/hiksdk/index.go @@ -0,0 +1,54 @@ +package plugin_hiksdk + +import ( + "fmt" + + "github.com/prometheus/client_golang/prometheus" + m7s "m7s.live/v5" + "m7s.live/v5/plugin/hiksdk/pkg" +) + +type HikPlugin struct { + m7s.Plugin + Clients []Client `yaml:"client,omitempty"` //共享的通道,格式为GBID,流地址 + list []HikDevice +} + +type Client struct { + IP string `yaml:"ip"` + UserName string `yaml:"username"` + Password string `yaml:"password"` + Port int `yaml:"port"` +} + +var _ = m7s.InstallPlugin[HikPlugin](m7s.PluginMeta{ + NewPuller: NewPuller, + NewPusher: NewPusher, +}) + +func init() { + fmt.Println("success 初始化海康SDK") + pkg.InitHikSDK() +} + +func (hik *HikPlugin) Start() (err error) { + for i, client := range hik.Clients { + fmt.Printf("Client[%d]: IP=%s, UserName=%s, Password=%s, Port=%d\n", i, client.IP, client.UserName, client.Password, client.Port) + device := HikDevice{ + IP: client.IP, + UserName: client.UserName, + Password: client.Password, + Port: client.Port, + Conf: hik, + } + hik.list = append(hik.list, device) + } + for _, device := range hik.list { + go hik.AddTask(&device) + } + return +} + +func (hik *HikPlugin) Describe(ch chan<- *prometheus.Desc) { + pkg.HKExit() +} diff --git a/plugin/hiksdk/lib/Linux/README.md b/plugin/hiksdk/lib/Linux/README.md new file mode 100644 index 0000000..3855969 --- /dev/null +++ b/plugin/hiksdk/lib/Linux/README.md @@ -0,0 +1,6 @@ +linux环境下使用,从海康开放平台下载 +地址:https://open.hikvision.com/download/5cda567cf47ae80dd41a54b3?type=20&id=5cda5902f47ae80dd41a54b7 +硬件产品/设备网络SDK +下载对应环境的设备网络SDK文件 +CH-HCNetSDKV6.1.9.48_buildxxxxxxx_linux64.zip +将库文件解压到当前目录下 \ No newline at end of file diff --git a/plugin/hiksdk/lib/Windows/README.md b/plugin/hiksdk/lib/Windows/README.md new file mode 100644 index 0000000..3694eb4 --- /dev/null +++ b/plugin/hiksdk/lib/Windows/README.md @@ -0,0 +1,6 @@ +windows环境下使用,从海康开放平台下载 +地址:https://open.hikvision.com/download/5cda567cf47ae80dd41a54b3?type=20&id=5cda5902f47ae80dd41a54b7 +硬件产品/设备网络SDK +下载对应环境的设备网络SDK文件 +CH-HCNetSDKV6.1.9.48_buildxxxxxxx_win64.zip +将库文件解压到当前目录下 \ No newline at end of file diff --git a/plugin/hiksdk/pkg/HKDevice.go b/plugin/hiksdk/pkg/HKDevice.go new file mode 100644 index 0000000..c9d3fee --- /dev/null +++ b/plugin/hiksdk/pkg/HKDevice.go @@ -0,0 +1,506 @@ +package pkg + +/* +#cgo CFLAGS: -I../include + +// Linux平台的链接配置 +#cgo linux LDFLAGS: -L../lib/Linux -lHCCore -lhpr -lhcnetsdk + +// Windows平台的链接配置 +#cgo windows LDFLAGS: -L../lib/Windows -lHCCore -lHCNetSDK +#include +#include +#include "HCNetSDK.h" +extern void AlarmCallBack(LONG lCommand, NET_DVR_ALARMER *pAlarmer, char *pAlarmInfo, DWORD dwBufLen, void* pUser); +extern void RealDataCallBack_V30(LONG lRealHandle, DWORD dwDataType, BYTE *pBuffer, DWORD dwBufSize, void *pUser); +*/ +import "C" +import ( + "errors" + "fmt" + "log" + "strings" + "sync" + "unsafe" + + "golang.org/x/text/encoding/simplifiedchinese" +) + +// 全局指针映射,用于安全地在CGO中传递Go指针 +var ( + pointerMap = make(map[uintptr]*Receiver) + pointerMutex sync.RWMutex + pointerCounter uintptr = 1 +) + +// 存储Go指针,返回一个唯一的标识符 +func storePointer(receiver *Receiver) uintptr { + pointerMutex.Lock() + defer pointerMutex.Unlock() + id := pointerCounter + pointerCounter++ + pointerMap[id] = receiver + return id +} + +// 根据标识符获取Go指针 +func getPointer(id uintptr) *Receiver { + pointerMutex.RLock() + defer pointerMutex.RUnlock() + return pointerMap[id] +} + +// 删除存储的指针 +func removePointer(id uintptr) { + pointerMutex.Lock() + defer pointerMutex.Unlock() + delete(pointerMap, id) +} + +/*************************参数配置命令 begin*******************************/ +//用于NET_DVR_SetDVRConfig和NET_DVR_GetDVRConfig,注意其对应的配置结构 +const ( + NET_DVR_GET_DEVICECFG = 100 // 获取设备参数 + NET_DVR_SET_DEVICECFG = 101 // 设置设备参数 + NET_DVR_GET_NETCFG = 102 // 获取网络参数 + NET_DVR_SET_NETCFG = 103 // 设置网络参数 + NET_DVR_GET_PICCFG = 104 // 获取图象参数 + NET_DVR_SET_PICCFG = 105 // 设置图象参数 + NET_DVR_GET_COMPRESSCFG = 106 // 获取压缩参数 + NET_DVR_SET_COMPRESSCFG = 107 // 设置压缩参数 + NET_DVR_GET_RECORDCFG = 108 // 获取录像时间参数 + NET_DVR_SET_RECORDCFG = 109 // 设置录像时间参数 + NET_DVR_GET_DECODERCFG = 110 // 获取解码器参数 + NET_DVR_SET_DECODERCFG = 111 // 设置解码器参数 + NET_DVR_GET_RS232CFG = 112 // 获取232串口参数 + NET_DVR_SET_RS232CFG = 113 // 设置232串口参数 + NET_DVR_GET_ALARMINCFG = 114 // 获取报警输入参数 + NET_DVR_SET_ALARMINCFG = 115 // 设置报警输入参数 + NET_DVR_GET_ALARMOUTCFG = 116 // 获取报警输出参数 + NET_DVR_SET_ALARMOUTCFG = 117 // 设置报警输出参数 + NET_DVR_GET_TIMECFG = 118 // 获取DVR时间 + NET_DVR_SET_TIMECFG = 119 // 设置DVR时间 + NET_DVR_GET_PREVIEWCFG = 120 // 获取预览参数 + NET_DVR_SET_PREVIEWCFG = 121 // 设置预览参数 + NET_DVR_GET_VIDEOOUTCFG = 122 // 获取视频输出参数 + NET_DVR_SET_VIDEOOUTCFG = 123 // 设置视频输出参数 + NET_DVR_GET_USERCFG = 124 // 获取用户参数 + NET_DVR_SET_USERCFG = 125 // 设置用户参数 + NET_DVR_GET_EXCEPTIONCFG = 126 // 获取异常参数 + NET_DVR_SET_EXCEPTIONCFG = 127 // 设置异常参数 + NET_DVR_GET_ZONEANDDST = 128 // 获取时区和夏时制参数 + NET_DVR_SET_ZONEANDDST = 129 // 设置时区和夏时制参数 + NET_DVR_GET_DEVICECFG_V40 = 1100 // 获取设备参数 + NET_DVR_SET_PTZPOS = 292 //云台设置PTZ位置 + NET_DVR_GET_PTZPOS = 293 //云台获取PTZ位置 + NET_DVR_GET_PTZSCOPE = 294 //云台获取PTZ范围 +) + +// strcpy safely copies a Go string to a C character array +func strcpy(dst unsafe.Pointer, src string, maxLen int) { + if len(src) >= maxLen { + copy((*[1 << 30]byte)(dst)[:maxLen-1], src) + (*[1 << 30]byte)(dst)[maxLen-1] = 0 + } else { + copy((*[1 << 30]byte)(dst)[:len(src)], src) + (*[1 << 30]byte)(dst)[len(src)] = 0 + } +} + +// GBK → UTF-8 +func GBKToUTF8(b []byte) (string, error) { + r, err := simplifiedchinese.GBK.NewDecoder().Bytes(b) + return string(r), err +} + +// UTF-8 → GBK +func UTF8ToGBK(s string) ([]byte, error) { + return simplifiedchinese.GBK.NewEncoder().Bytes([]byte(s)) +} + +//export AlarmCallBack +func AlarmCallBack(command C.LONG, alarm *C.NET_DVR_ALARMER, info *C.char, len C.DWORD, user unsafe.Pointer) { + fmt.Println("receive alarm") +} + +//export RealDataCallBack_V30 +func RealDataCallBack_V30(lRealHandle C.LONG, dwDataType C.DWORD, pBuffer *C.BYTE, dwBufSize C.DWORD, pUser unsafe.Pointer) { + // 从指针映射中获取Receiver + receiverID := uintptr(pUser) + receiver := getPointer(receiverID) + if receiver == nil { + fmt.Println("Error: receiver not found for ID", receiverID) + return + } + + size := int(dwBufSize) + if size > 0 && pBuffer != nil { + // 将C指针转换为Go的byte切片 + buffer := (*[1 << 30]C.BYTE)(unsafe.Pointer(pBuffer))[:size:size] + // 使用unsafe.Pointer高效转换[]C.BYTE为[]byte + goBuffer := (*[1 << 30]byte)(unsafe.Pointer(&buffer[0]))[:len(buffer):len(buffer)] + receiver.ReadPSData(goBuffer) + } +} + +type HKDevice struct { + ip string + port int + username string + password string + loginId int + alarmHandle int + lRealHandle int + byChanNum int + receiverID uintptr // 存储指针映射ID +} + +// InitHikSDK hk sdk init +func InitHikSDK() { + // 初始化SDK + C.NET_DVR_Init() + C.NET_DVR_SetConnectTime(2000, 5) + C.NET_DVR_SetReconnect(10000, 1) +} + +// HKExit hk sdk clean +func HKExit() { + C.NET_DVR_Cleanup() +} + +// NewHKDevice new hk-device instance +func NewHKDevice(info DeviceInfo) Device { + return &HKDevice{ + ip: info.IP, + port: info.Port, + username: info.UserName, + password: info.Password} +} + +// Login hk device loin +func (device *HKDevice) Login() (int, error) { + // init data + var deviceInfoV30 C.NET_DVR_DEVICEINFO_V30 + ip := C.CString(device.ip) + usr := C.CString(device.username) + passwd := C.CString(device.password) + defer func() { + C.free(unsafe.Pointer(ip)) + C.free(unsafe.Pointer(usr)) + C.free(unsafe.Pointer(passwd)) + }() + + device.loginId = int(C.NET_DVR_Login_V30(ip, C.WORD(device.port), usr, passwd, + (*C.NET_DVR_DEVICEINFO_V30)(unsafe.Pointer(&deviceInfoV30)), + )) + // 正确地将_Ctype_BYTE数组转换为string + serialNumber := string(unsafe.Slice(&deviceInfoV30.sSerialNumber[0], len(deviceInfoV30.sSerialNumber))) + // 去除可能的空字符 + serialNumber = strings.Trim(serialNumber, "\x00") + fmt.Println("设备序列号:", serialNumber) + fmt.Println("登录成功,设备信息已获取") + if device.loginId < 0 { + return -1, device.HKErr("login ") + } + log.Println("login success") + return device.loginId, nil +} + +// Logout hk device logout +func (device *HKDevice) Logout() error { + C.NET_DVR_Logout_V30(C.LONG(device.loginId)) + if err := device.HKErr("NVRLogout"); err != nil { + return err + } + return nil +} + +// Login hk device loin +func (device *HKDevice) LoginV4() (int, error) { + // init data + var deviceInfoV40 C.NET_DVR_DEVICEINFO_V40 + var userLoginInfo C.NET_DVR_USER_LOGIN_INFO + // 使用strcpy函数将字符串复制到C结构体的字符数组中 + strcpy(unsafe.Pointer(&userLoginInfo.sDeviceAddress[0]), device.ip, len(userLoginInfo.sDeviceAddress)) + userLoginInfo.wPort = C.WORD(device.port) + strcpy(unsafe.Pointer(&userLoginInfo.sUserName[0]), device.username, len(userLoginInfo.sUserName)) + strcpy(unsafe.Pointer(&userLoginInfo.sPassword[0]), device.password, len(userLoginInfo.sPassword)) + + // 正确调用NET_DVR_Login_V40函数 + device.loginId = int(C.NET_DVR_Login_V40(&userLoginInfo, (*C.NET_DVR_DEVICEINFO_V40)(unsafe.Pointer(&deviceInfoV40)))) + // 正确地将_Ctype_BYTE数组转换为string + serialNumber := string(unsafe.Slice(&deviceInfoV40.struDeviceV30.sSerialNumber[0], len(deviceInfoV40.struDeviceV30.sSerialNumber))) + // 去除可能的空字符 + serialNumber = strings.Trim(serialNumber, "\x00") + // fmt.Println("设备序列号:", serialNumber) + // bySupportDev5是一个字节值而不是数组,直接输出其数值 + // fmt.Println("支持的设备类型标志:", int(deviceInfoV40.bySupportDev5)) + // fmt.Println("登录成功,设备信息已获取") + if device.loginId < 0 { + return -1, device.HKErr("login ") + } + log.Println("login success") + return device.loginId, nil +} + +func (device *HKDevice) GetDeiceInfo() (*DeviceInfo, error) { + // BOOL NET_DVR_GetDVRConfig(LONG lUserID, DWORD dwCommand,LONG lChannel, LPVOID lpOutBuffer, DWORD dwOutBufferSize, LPDWORD lpBytesReturned); + var deviceInfo C.NET_DVR_DEVICECFG + var bytesReturned C.DWORD + if C.NET_DVR_GetDVRConfig(C.LONG(device.loginId), C.DWORD(NET_DVR_GET_DEVICECFG), C.LONG(0), (C.LPVOID)(unsafe.Pointer(&deviceInfo)), C.DWORD(unsafe.Sizeof(deviceInfo)), &bytesReturned) != C.TRUE { + // fmt.Println("获取设备信息失败") + } + sDVRName := string(unsafe.Slice(&deviceInfo.sDVRName[0], len(deviceInfo.sDVRName))) + sSerialNumber := string(unsafe.Slice(&deviceInfo.sSerialNumber[0], len(deviceInfo.sSerialNumber))) + + // 清理字符串中的空字符和空格 + sDVRName = strings.TrimRight(sDVRName, "\x00") + sDVRName = strings.TrimSpace(sDVRName) + sSerialNumber = strings.TrimRight(sSerialNumber, "\x00") + sSerialNumber = strings.TrimSpace(sSerialNumber) + + sDVRName, _ = GBKToUTF8([]byte(sDVRName)) + device.byChanNum = int(deviceInfo.byChanNum) + return &DeviceInfo{ + IP: device.ip, + Port: device.port, + UserName: device.username, + Password: device.password, + DeviceID: sSerialNumber, + DeviceName: sDVRName, + ByChanNum: int(deviceInfo.byChanNum), + }, nil +} + +// 获取通道名称,俯仰角,横滚角 +func (device *HKDevice) GetChannelName() (map[int]string, error) { + channelNames := make(map[int]string) + for i := 1; i <= int(device.byChanNum); i++ { + var channelInfo C.NET_DVR_PICCFG + var bytesReturned C.DWORD + var sDVRName string + // if C.NET_DVR_GetChannelInfo(C.LONG(device.loginId), C.LONG(i), (*C.NET_DVR_CHANNELINFO)(unsafe.Pointer(&channelInfo))) != C.TRUE { + // return nil, device.HKErr("get channel info") + // } + if C.NET_DVR_GetDVRConfig(C.LONG(device.loginId), C.DWORD(NET_DVR_GET_PICCFG), C.LONG(i), (C.LPVOID)(unsafe.Pointer(&channelInfo)), C.DWORD(unsafe.Sizeof(channelInfo)), &bytesReturned) != C.TRUE { + // fmt.Println("获取通道名称失败") + // return nil, device.HKErr("get device info") + sDVRName = "camera" + fmt.Sprintf("%d", i) + } else { + sDVRName = string(unsafe.Slice(&channelInfo.sChanName[0], len(channelInfo.sChanName))) + // 清理字符串中的空字符和空格 + sDVRName = strings.TrimRight(sDVRName, "\x00") + sDVRName = strings.TrimSpace(sDVRName) + sDVRName, _ = GBKToUTF8([]byte(sDVRName)) + } + channelNames[i] = sDVRName + } + return channelNames, nil +} + +// 获取通道名称,俯仰角,横滚角 +func (device *HKDevice) GetChannelPTZ(channel int) { + var ptzPos C.NET_DVR_PTZPOS + var ptzScope C.NET_DVR_PTZSCOPE + var bytesReturned C.DWORD + // if C.NET_DVR_GetChannelInfo(C.LONG(device.loginId), C.LONG(i), (*C.NET_DVR_CHANNELINFO)(unsafe.Pointer(&channelInfo))) != C.TRUE { + // return nil, device.HKErr("get channel info") + // } + if C.NET_DVR_GetDVRConfig(C.LONG(device.loginId), C.DWORD(NET_DVR_GET_PTZPOS), C.LONG(channel), (C.LPVOID)(unsafe.Pointer(&ptzPos)), C.DWORD(unsafe.Sizeof(ptzPos)), &bytesReturned) != C.TRUE { + // fmt.Println("获取PTZ位置信息失败") + } + // fmt.Println("PTZ位置信息:", ptzPos) + + if C.NET_DVR_GetDVRConfig(C.LONG(device.loginId), C.DWORD(NET_DVR_GET_PTZSCOPE), C.LONG(channel), (C.LPVOID)(unsafe.Pointer(&ptzScope)), C.DWORD(unsafe.Sizeof(ptzScope)), &bytesReturned) != C.TRUE { + // fmt.Println("获取PTZ范围信息失败") + } + fmt.Println("PTZ范围信息:", ptzScope) + // 计算PTZ位置 - 显示原始值用于调试 + fmt.Println("原始PTZ值 - wPanPos:", ptzPos.wPanPos, "wPanPosMin:", ptzScope.wPanPosMin, "wPanPosMax:", ptzScope.wPanPosMax) + fmt.Println("原始PTZ值 - wTiltPos:", ptzPos.wTiltPos, "wTiltPosMin:", ptzScope.wTiltPosMin, "wTiltPosMax:", ptzScope.wTiltPosMax) + fmt.Println("原始PTZ值 - wZoomPos:", ptzPos.wZoomPos, "wZoomPosMin:", ptzScope.wZoomPosMin, "wZoomPosMax:", ptzScope.wZoomPosMax) + + // 计算差值 + deltaPan := ptzScope.wPanPosMax - ptzScope.wPanPosMin + deltaTilt := ptzScope.wTiltPosMax - ptzScope.wTiltPosMin + deltaZoom := ptzScope.wZoomPosMax - ptzScope.wZoomPosMin + + fmt.Println("差值计算 - deltaPan:", deltaPan, "deltaTilt:", deltaTilt, "deltaZoom:", deltaZoom) + fmt.Println("位置差值 - Pan:", ptzPos.wPanPos-ptzScope.wPanPosMin, "Tilt:", ptzPos.wTiltPos-ptzScope.wTiltPosMin, "Zoom:", ptzPos.wZoomPos-ptzScope.wZoomPosMin) + + // 添加除零检查和边界处理 + // 计算水平位置 (Pan) + if deltaPan > 0 { + // 计算实际比例 + panRatio := float64(ptzPos.wPanPos-ptzScope.wPanPosMin) / float64(deltaPan) + fmt.Println("Pan比例:", panRatio) + // 计算Pan位置(0-360度) + panPos := int(panRatio * 360) + // 确保结果在0-360范围内 + if panPos < 0 { + panPos = 0 + } else if panPos > 360 { + panPos = 360 + } + ptzPos.wPanPos = C.WORD(panPos) + fmt.Println("计算后Pan位置:", panPos) + } else { + ptzPos.wPanPos = 0 // 当范围无效时设置默认值 + fmt.Println("警告: PTZ水平范围无效,设置为默认值") + } + + // 计算垂直位置 (Tilt) + if deltaTilt > 0 { + // 计算实际比例 + tiltRatio := float64(ptzPos.wTiltPos-ptzScope.wTiltPosMin) / float64(deltaTilt) + fmt.Println("Tilt比例:", tiltRatio) + // 计算Tilt位置(0-360度) + tiltPos := int(tiltRatio * 90) + // 确保结果在0-360范围内 + if tiltPos < 0 { + tiltPos = 0 + } else if tiltPos > 90 { + tiltPos = 90 + } + ptzPos.wTiltPos = C.WORD(tiltPos) + fmt.Println("计算后Tilt位置:", tiltPos) + } else { + ptzPos.wTiltPos = 0 // 当范围无效时设置默认值 + fmt.Println("警告: PTZ垂直范围无效,设置为默认值") + } + + // 计算缩放位置 (Zoom) + if deltaZoom > 0 { + // 计算实际比例 + zoomRatio := float64(ptzPos.wZoomPos-ptzScope.wZoomPosMin) / float64(deltaZoom) + fmt.Println("Zoom比例:", zoomRatio) + // 计算Zoom位置(0-100%) + zoomPos := int(zoomRatio * 100) + // 确保结果在0-100范围内 + if zoomPos < 0 { + zoomPos = 0 + } else if zoomPos > 100 { + zoomPos = 100 + } + ptzPos.wZoomPos = C.WORD(zoomPos) + fmt.Println("计算后Zoom位置:", zoomPos) + } else { + ptzPos.wZoomPos = 0 // 当范围无效时设置默认值 + fmt.Println("警告: PTZ缩放范围无效,设置为默认值") + } + fmt.Println("PTZ位置信息:", ptzPos) + +} + +func (device *HKDevice) SetAlarmCallBack() error { //监听报警信息 + if C.NET_DVR_SetDVRMessageCallBack_V30(C.MSGCallBack(C.AlarmCallBack), C.NULL) != C.TRUE { + return device.HKErr(device.ip + ":set alarm callback") + } + return nil +} +func (device *HKDevice) StartListenAlarmMsg() error { + var struAlarmParam C.NET_DVR_SETUPALARM_PARAM + // 根据平台使用正确的类型 + struAlarmParam.dwSize = C.DWORD(unsafe.Sizeof(struAlarmParam)) + struAlarmParam.byAlarmInfoType = 0 + + // 转换为正确的类型 + device.alarmHandle = int(C.NET_DVR_SetupAlarmChan_V41(C.LONG(device.loginId), &struAlarmParam)) + if device.alarmHandle < 0 { + return device.HKErr("setup alarm chan") + } + return nil +} + +func (device *HKDevice) StopListenAlarmMsg() error { + if C.NET_DVR_CloseAlarmChan_V30(C.LONG(device.alarmHandle)) != C.TRUE { + return device.HKErr("stop alarm chan") + } + return nil +} + +// HKErr Detect success of operation +func (device *HKDevice) HKErr(operation string) error { + errno := int64(C.NET_DVR_GetLastError()) + if errno > 0 { + reMsg := fmt.Sprintf("%s:%s摄像头失败,失败代码号:%d", device.ip, operation, errno) + return errors.New(reMsg) + } + return nil +} + +// // Login hk device loin +func (device *HKDevice) PTZControlWithSpeed(dwPTZCommand, dwStop, dwSpeed int) (bool, error) { + // init data + if C.NET_DVR_PTZControlWithSpeed(C.LONG(device.loginId), C.DWORD(dwPTZCommand), C.DWORD(dwStop), C.DWORD(dwSpeed)) != C.TRUE { + return false, nil + } + + log.Println("control success") + return true, nil +} + +// // Login hk device loin +func (device *HKDevice) PTZControlWithSpeed_Other(lChannel, dwPTZCommand, dwStop, dwSpeed int) (bool, error) { + if C.NET_DVR_PTZControlWithSpeed_Other(C.LONG(device.loginId), C.LONG(lChannel), C.DWORD(dwPTZCommand), C.DWORD(dwStop), C.DWORD(dwSpeed)) != C.TRUE { + return false, nil + } + + log.Println("control success") + return true, nil +} + +// // Login hk device loin +func (device *HKDevice) PTZControl(dwPTZCommand, dwStop int) (bool, error) { + // init data + if C.NET_DVR_PTZControl(C.LONG(device.loginId), C.DWORD(dwPTZCommand), C.DWORD(dwStop)) != C.TRUE { + return false, nil + } + + log.Println("control success") + return true, nil +} + +// // Login hk device loin +func (device *HKDevice) PTZControl_Other(lChannel, dwPTZCommand, dwStop int) (bool, error) { + // init data + if C.NET_DVR_PTZControl_Other(C.LONG(device.loginId), C.LONG(lChannel), C.DWORD(dwPTZCommand), C.DWORD(dwStop)) != C.TRUE { + return false, nil + } + + log.Println("control success") + return true, nil +} + +func (device *HKDevice) RealPlay_V40(ChannelId int, receiver *Receiver) (int, error) { + previewInfo := C.NET_DVR_PREVIEWINFO{} + previewInfo.hPlayWnd = nil + previewInfo.lChannel = C.LONG(ChannelId) + previewInfo.dwStreamType = C.DWORD(0) + previewInfo.dwLinkMode = C.DWORD(0) + previewInfo.bBlocked = C.DWORD(0) + previewInfo.byProtoType = C.BYTE(0) + + // 存储receiver指针并获取安全ID + receiverID := storePointer(receiver) + device.receiverID = receiverID + + //LONG NET_DVR_RealPlay_V40(LONG lUserID, LPNET_DVR_PREVIEWINFO lpPreviewInfo, REALDATACALLBACK fRealDataCallBack_V30, void* pUser); + device.lRealHandle = int(C.NET_DVR_RealPlay_V40(C.LONG(device.loginId), &previewInfo, C.REALDATACALLBACK(C.RealDataCallBack_V30), unsafe.Pointer(receiverID))) + log.Println("Play success", device.lRealHandle) + return device.lRealHandle, nil +} + +// StopRealPlay 停止预览 +func (device *HKDevice) StopRealPlay() { + if device.lRealHandle != 0 { + C.NET_DVR_StopRealPlay(C.LONG(device.lRealHandle)) + device.lRealHandle = 0 + // 清理指针映射 + if device.receiverID != 0 { + removePointer(device.receiverID) + device.receiverID = 0 + } + log.Println("Stop preview success") + } +} diff --git a/plugin/hiksdk/pkg/audio.go b/plugin/hiksdk/pkg/audio.go new file mode 100644 index 0000000..4881878 --- /dev/null +++ b/plugin/hiksdk/pkg/audio.go @@ -0,0 +1,416 @@ +package pkg + +import ( + "encoding/hex" + "fmt" + "strings" + "time" + "unsafe" + + "github.com/bluenviron/mediacommon/pkg/bits" + + "github.com/pion/rtp" + "github.com/pion/webrtc/v4" + . "m7s.live/v5/pkg" + "m7s.live/v5/pkg/codec" + "m7s.live/v5/pkg/util" +) + +type RTPData struct { + Sample + Packets util.ReuseArray[rtp.Packet] +} + +func (r *RTPData) Recycle() { + r.RecyclableMemory.Recycle() + r.Packets.Reset() +} + +func (r *RTPData) String() (s string) { + for p := range r.Packets.RangePoint { + s += fmt.Sprintf("t: %d, s: %d, p: %02X %d\n", p.Timestamp, p.SequenceNumber, p.Payload[0:2], len(p.Payload)) + } + return +} + +func (r *RTPData) GetSize() (s int) { + for p := range r.Packets.RangePoint { + s += p.MarshalSize() + } + return +} + +type ( + RTPCtx struct { + webrtc.RTPCodecParameters + Fmtp map[string]string + SequenceNumber uint16 + SSRC uint32 + } + PCMACtx struct { + RTPCtx + *codec.PCMACtx + } + PCMUCtx struct { + RTPCtx + *codec.PCMUCtx + } + OPUSCtx struct { + RTPCtx + *codec.OPUSCtx + } + AACCtx struct { + RTPCtx + *codec.AACCtx + SizeLength int // 通常为13 + IndexLength int + IndexDeltaLength int + } + IRTPCtx interface { + GetRTPCodecParameter() webrtc.RTPCodecParameters + } +) + +func (r *RTPCtx) ParseFmtpLine(cp *webrtc.RTPCodecParameters) { + r.RTPCodecParameters = *cp + r.Fmtp = make(map[string]string) + kvs := strings.Split(r.SDPFmtpLine, ";") + for _, kv := range kvs { + if kv = strings.TrimSpace(kv); kv == "" { + continue + } + if key, value, found := strings.Cut(kv, "="); found { + r.Fmtp[strings.TrimSpace(key)] = strings.TrimSpace(value) + } + } +} + +func (r *RTPCtx) GetInfo() string { + return r.GetRTPCodecParameter().SDPFmtpLine +} +func (r *AACCtx) GetInfo() string { + return r.AACCtx.GetInfo() +} +func (r *OPUSCtx) GetInfo() string { + return r.OPUSCtx.GetInfo() +} +func (r *RTPCtx) GetRTPCodecParameter() webrtc.RTPCodecParameters { + return r.RTPCodecParameters +} + +func (r *RTPData) Append(ctx *RTPCtx, ts uint32, payload []byte) *rtp.Packet { + ctx.SequenceNumber++ + r.Packets = append(r.Packets, rtp.Packet{ + Header: rtp.Header{ + Version: 2, + SequenceNumber: ctx.SequenceNumber, + Timestamp: ts, + SSRC: ctx.SSRC, + PayloadType: uint8(ctx.PayloadType), + }, + Payload: payload, + }) + return &r.Packets[len(r.Packets)-1] +} + +var _ IAVFrame = (*AudioFrame)(nil) + +type AudioFrame struct { + RTPData +} + +func (r *AudioFrame) Parse(data IAVFrame) (err error) { + input := data.(*AudioFrame) + r.Packets = append(r.Packets[:0], input.Packets...) + return +} + +func payloadLengthInfoDecode(buf []byte) (int, int, error) { + lb := len(buf) + l := 0 + n := 0 + + for { + if (lb - n) == 0 { + return 0, 0, fmt.Errorf("not enough bytes") + } + + b := buf[n] + n++ + l += int(b) + + if b != 255 { + break + } + } + + return l, n, nil +} + +func (r *AudioFrame) Demux() (err error) { + if len(r.Packets) == 0 { + return ErrSkip + } + data := r.GetAudioData() + // 从编解码器上下文获取 MimeType + var mimeType string + if rtpCtx, ok := r.ICodecCtx.(IRTPCtx); ok { + mimeType = rtpCtx.GetRTPCodecParameter().MimeType + } + switch mimeType { + case "audio/MP4A-LATM": + var fragments util.Memory + var fragmentsExpected int + var fragmentsSize int + for packet := range r.Packets.RangePoint { + if len(packet.Payload) == 0 { + continue + } + if packet.Padding { + packet.Padding = false + } + buf := packet.Payload + if fragments.Size == 0 { + pl, n, err := payloadLengthInfoDecode(buf) + if err != nil { + return err + } + + buf = buf[n:] + bl := len(buf) + + if pl <= bl { + data.PushOne(buf[:pl]) + // there could be other data, due to otherDataPresent. Ignore it. + } else { + if pl > 5*1024 { + fragments = util.Memory{} // discard pending fragments + return fmt.Errorf("access unit size (%d) is too big, maximum is %d", + pl, 5*1024) + } + + fragments.PushOne(buf) + fragmentsSize = pl + fragmentsExpected = pl - bl + continue + } + } else { + bl := len(buf) + + if fragmentsExpected > bl { + fragments.PushOne(buf) + fragmentsExpected -= bl + continue + } + + fragments.PushOne(buf[:fragmentsExpected]) + // there could be other data, due to otherDataPresent. Ignore it. + data.Push(fragments.Buffers...) + if fragments.Size != fragmentsSize { + return fmt.Errorf("fragmented AU size is not correct %d != %d", data.Size, fragmentsSize) + } + fragments = util.Memory{} + } + } + case "audio/MPEG4-GENERIC": + var fragments util.Memory + for packet := range r.Packets.RangePoint { + if len(packet.Payload) < 2 { + continue + } + auHeaderLen := util.ReadBE[int](packet.Payload[:2]) + if auHeaderLen == 0 { + data.PushOne(packet.Payload) + } else { + dataLens, err := r.readAUHeaders(r.ICodecCtx.(*AACCtx), packet.Payload[2:], auHeaderLen) + if err != nil { + return err + } + payload := packet.Payload[2:] + pos := auHeaderLen >> 3 + if (auHeaderLen % 8) != 0 { + pos++ + } + payload = payload[pos:] + if fragments.Size == 0 { + if packet.Marker { + for _, dataLen := range dataLens { + if len(payload) < int(dataLen) { + return fmt.Errorf("invalid data len %d", dataLen) + } + data.PushOne(payload[:dataLen]) + payload = payload[dataLen:] + } + } else { + if len(dataLens) != 1 { + return fmt.Errorf("a fragmented packet can only contain one AU") + } + fragments.PushOne(payload) + } + } else { + if len(dataLens) != 1 { + return fmt.Errorf("a fragmented packet can only contain one AU") + } + fragments.PushOne(payload) + if !packet.Header.Marker { + continue + } + if uint64(fragments.Size) != dataLens[0] { + return fmt.Errorf("fragmented AU size is not correct %d != %d", dataLens[0], fragments.Size) + } + data.Push(fragments.Buffers...) + fragments = util.Memory{} + } + } + break + } + default: + for packet := range r.Packets.RangePoint { + data.PushOne(packet.Payload) + } + } + return nil +} + +func (r *AudioFrame) Mux(from *Sample) (err error) { + data := from.Raw.(*AudioData) + var ctx *RTPCtx + var lastPacket *rtp.Packet + switch base := from.GetBase().(type) { + case *codec.AACCtx: + var c *AACCtx + if r.ICodecCtx == nil { + c = &AACCtx{} + c.SSRC = uint32(uintptr(unsafe.Pointer(&ctx))) + c.AACCtx = base + c.MimeType = "audio/MPEG4-GENERIC" + c.SDPFmtpLine = fmt.Sprintf("profile-level-id=1;mode=AAC-hbr;sizelength=13;indexlength=3;indexdeltalength=3;config=%s", hex.EncodeToString(c.ConfigBytes)) + c.IndexLength = 3 + c.IndexDeltaLength = 3 + c.SizeLength = 13 + c.RTPCtx.Channels = uint16(base.GetChannels()) + c.PayloadType = 97 + c.ClockRate = uint32(base.CodecData.SampleRate()) + r.ICodecCtx = c + } else { + c = r.ICodecCtx.(*AACCtx) + } + ctx = &c.RTPCtx + pts := uint32(from.Timestamp * time.Duration(ctx.ClockRate) / time.Second) + //AU_HEADER_LENGTH,因为单位是bit, 除以8就是auHeader的字节长度;又因为单个auheader字节长度2字节,所以再除以2就是auheader的个数。 + auHeaderLen := []byte{0x00, 0x10, (byte)((data.Size & 0x1fe0) >> 5), (byte)((data.Size & 0x1f) << 3)} // 3 = 16-13, 5 = 8-3 + for reader := data.NewReader(); reader.Length > 0; { + payloadLen := MTUSize + if reader.Length+4 < MTUSize { + payloadLen = reader.Length + 4 + } + mem := r.NextN(payloadLen) + copy(mem, auHeaderLen) + reader.Read(mem[4:]) + lastPacket = r.Append(ctx, pts, mem) + } + lastPacket.Header.Marker = true + return + case *codec.PCMACtx: + if r.ICodecCtx == nil { + var ctx PCMACtx + ctx.SSRC = uint32(uintptr(unsafe.Pointer(&ctx))) + ctx.PCMACtx = base + ctx.MimeType = webrtc.MimeTypePCMA + ctx.PayloadType = 8 + ctx.ClockRate = uint32(ctx.SampleRate) + r.ICodecCtx = &ctx + } else { + ctx = &r.ICodecCtx.(*PCMACtx).RTPCtx + } + case *codec.PCMUCtx: + if r.ICodecCtx == nil { + var ctx PCMUCtx + ctx.SSRC = uint32(uintptr(unsafe.Pointer(&ctx))) + ctx.PCMUCtx = base + ctx.MimeType = webrtc.MimeTypePCMU + ctx.PayloadType = 0 + ctx.ClockRate = uint32(ctx.SampleRate) + r.ICodecCtx = &ctx + } else { + ctx = &r.ICodecCtx.(*PCMUCtx).RTPCtx + } + } + pts := uint32(from.Timestamp * time.Duration(ctx.ClockRate) / time.Second) + if reader := data.NewReader(); reader.Length > MTUSize { + for reader.Length > 0 { + payloadLen := MTUSize + if reader.Length < MTUSize { + payloadLen = reader.Length + } + mem := r.NextN(payloadLen) + reader.Read(mem) + lastPacket = r.Append(ctx, pts, mem) + } + } else { + mem := r.NextN(reader.Length) + reader.Read(mem) + lastPacket = r.Append(ctx, pts, mem) + } + lastPacket.Header.Marker = true + return +} + +func (r *AudioFrame) readAUHeaders(ctx *AACCtx, buf []byte, headersLen int) ([]uint64, error) { + firstRead := false + + count := 0 + for i := 0; i < headersLen; { + if i == 0 { + i += ctx.SizeLength + i += ctx.IndexLength + } else { + i += ctx.SizeLength + i += ctx.IndexDeltaLength + } + count++ + } + + dataLens := make([]uint64, count) + + pos := 0 + i := 0 + + for headersLen > 0 { + dataLen, err := bits.ReadBits(buf, &pos, ctx.SizeLength) + if err != nil { + return nil, err + } + headersLen -= ctx.SizeLength + + if !firstRead { + firstRead = true + if ctx.IndexLength > 0 { + auIndex, err := bits.ReadBits(buf, &pos, ctx.IndexLength) + if err != nil { + return nil, err + } + headersLen -= ctx.IndexLength + + if auIndex != 0 { + return nil, fmt.Errorf("AU-index different than zero is not supported") + } + } + } else if ctx.IndexDeltaLength > 0 { + auIndexDelta, err := bits.ReadBits(buf, &pos, ctx.IndexDeltaLength) + if err != nil { + return nil, err + } + headersLen -= ctx.IndexDeltaLength + + if auIndexDelta != 0 { + return nil, fmt.Errorf("AU-index-delta different than zero is not supported") + } + } + + dataLens[i] = dataLen + i++ + } + + return dataLens, nil +} diff --git a/plugin/hiksdk/pkg/device.go b/plugin/hiksdk/pkg/device.go new file mode 100644 index 0000000..2fbf347 --- /dev/null +++ b/plugin/hiksdk/pkg/device.go @@ -0,0 +1,28 @@ +package pkg + +type Device interface { + Login() (int, error) + LoginV4() (int, error) + GetDeiceInfo() (*DeviceInfo, error) + GetChannelName() (map[int]string, error) + Logout() error + SetAlarmCallBack() error + StartListenAlarmMsg() error + StopListenAlarmMsg() error + PTZControlWithSpeed(dwPTZCommand, dwStop, dwSpeed int) (bool, error) + PTZControlWithSpeed_Other(lChannel, dwPTZCommand, dwStop, dwSpeed int) (bool, error) + PTZControl(dwPTZCommand, dwStop int) (bool, error) + PTZControl_Other(lChannel, dwPTZCommand, dwStop int) (bool, error) + GetChannelPTZ(channel int) + RealPlay_V40(ChannelId int,receiver *Receiver) (int, error) + StopRealPlay() +} +type DeviceInfo struct { + IP string + Port int + UserName string + Password string + DeviceID string //序列号 + DeviceName string //DVR名称 + ByChanNum int //通道数量 +} diff --git a/plugin/hiksdk/pkg/transceiver.go b/plugin/hiksdk/pkg/transceiver.go new file mode 100644 index 0000000..91d59fd --- /dev/null +++ b/plugin/hiksdk/pkg/transceiver.go @@ -0,0 +1,164 @@ +package pkg + +import ( + "fmt" + "io" + + mpegps "m7s.live/v5/pkg/format/ps" + "m7s.live/v5/pkg/task" + "m7s.live/v5/pkg/util" +) + +type ChanReader chan []byte + +func (r ChanReader) Read(buf []byte) (n int, err error) { + b, ok := <-r + if !ok { + return 0, io.EOF + } + copy(buf, b) + return len(b), nil +} + +type Receiver struct { + task.Task + *util.BufReader + SSRC uint32 // RTP SSRC + PSMouth chan []byte // 直接处理PS数据的通道 + psBuffer []byte // PS数据缓冲区,用于处理跨包的PS起始码 +} + +type PSReceiver struct { + Device Device // 设备 + ChannelId int // 通道号 + Receiver + mpegps.MpegPsDemuxer +} + +func (p *PSReceiver) Start() error { + err := p.Receiver.Start() + if err == nil { + p.Using(p.Publisher) + } + // 初始化播放控制通道(如果未初始化) + p.Device.RealPlay_V40(p.ChannelId, &p.Receiver) + return err +} + +func (p *PSReceiver) Run() error { + p.MpegPsDemuxer.Allocator = util.NewScalableMemoryAllocator(1 << util.MinPowerOf2) + p.Using(p.MpegPsDemuxer.Allocator) + + // 确保Publisher已设置 + if p.MpegPsDemuxer.Publisher == nil { + return fmt.Errorf("Publisher未设置") + } + + err := p.MpegPsDemuxer.Feed(p.BufReader) + return err +} + +func (p *PSReceiver) Dispose() { + p.Device.StopRealPlay() + // 停止设备播放 +} + +func (p *Receiver) Start() (err error) { + p.PSMouth = make(chan []byte, 500) // 增加PS数据通道缓冲区到500,避免数据丢失 + psReader := (ChanReader)(p.PSMouth) // 直接使用PS数据通道 + p.BufReader = util.NewBufReader(psReader) + return +} + +func (p *Receiver) ReadPSData(data util.Buffer) (err error) { + // 将新数据添加到缓冲区 + p.psBuffer = append(p.psBuffer, data...) + + // 处理缓冲区中的完整PS包 + for { + syncedData, remaining := p.extractSynchronizedPSData(p.psBuffer) + if syncedData == nil { + // 没有找到完整的PS包,保留剩余数据等待更多数据 + p.psBuffer = remaining + break + } + + // 发送同步后的PS数据到处理通道 + select { + case p.PSMouth <- syncedData: + // 成功发送数据到PS处理通道 + // fmt.Printf("发送同步PS数据到通道,长度: %d\n", len(syncedData)) + default: + // 通道满了,跳过这个数据包 + fmt.Printf("PS通道满了,跳过数据包,当前缓冲区大小: %d/%d\n", len(p.PSMouth), cap(p.PSMouth)) + // 跳过当前数据包,但不返回错误,避免阻塞 + } + + // 更新缓冲区为剩余数据 + p.psBuffer = remaining + } + + return nil +} + +// extractSynchronizedPSData 从缓冲区中提取同步的PS数据包 +func (p *Receiver) extractSynchronizedPSData(buffer []byte) ([]byte, []byte) { + if len(buffer) < 4 { + return nil, buffer // 数据不足,返回所有数据等待更多 + } + + // 寻找PS起始码 + startIndex := -1 + for i := 0; i <= len(buffer)-4; i++ { + if buffer[i] == 0x00 && buffer[i+1] == 0x00 && buffer[i+2] == 0x01 { + // 检查第四个字节是否为有效的PS起始码 + startCode := uint32(buffer[i])<<24 | uint32(buffer[i+1])<<16 | + uint32(buffer[i+2])<<8 | uint32(buffer[i+3]) + + switch startCode { + case mpegps.StartCodePS, mpegps.StartCodeVideo, mpegps.StartCodeVideo1, + mpegps.StartCodeVideo2, mpegps.StartCodeAudio, mpegps.StartCodeMAP, + mpegps.StartCodeSYS, mpegps.PrivateStreamCode: + startIndex = i + // fmt.Println("在数据源头找到PS起始码:", fmt.Sprintf("0x%08x", startCode)) + goto found + } + } + } + +found: + if startIndex == -1 { + // 没有找到有效起始码 + if len(buffer) > 3 { + // 保留最后3个字节,丢弃其余数据 + return nil, buffer[len(buffer)-3:] + } + return nil, buffer + } + + // 寻找下一个起始码来确定当前包的结束位置 + nextStartIndex := -1 + for i := startIndex + 4; i <= len(buffer)-4; i++ { + if buffer[i] == 0x00 && buffer[i+1] == 0x00 && buffer[i+2] == 0x01 { + startCode := uint32(buffer[i])<<24 | uint32(buffer[i+1])<<16 | + uint32(buffer[i+2])<<8 | uint32(buffer[i+3]) + + switch startCode { + case mpegps.StartCodePS, mpegps.StartCodeVideo, mpegps.StartCodeVideo1, + mpegps.StartCodeVideo2, mpegps.StartCodeAudio, mpegps.StartCodeMAP, + mpegps.StartCodeSYS, mpegps.PrivateStreamCode: + nextStartIndex = i + goto nextFound + } + } + } + +nextFound: + if nextStartIndex == -1 { + // 没有找到下一个起始码,返回从当前起始码到缓冲区末尾的所有数据 + return buffer[startIndex:], nil + } + + // 返回从当前起始码到下一个起始码之间的数据 + return buffer[startIndex:nextStartIndex], buffer[nextStartIndex:] +} diff --git a/plugin/hiksdk/pkg/video.go b/plugin/hiksdk/pkg/video.go new file mode 100644 index 0000000..134928d --- /dev/null +++ b/plugin/hiksdk/pkg/video.go @@ -0,0 +1,493 @@ +package pkg + +import ( + "bytes" + "encoding/base64" + "fmt" + "io" + "slices" + "time" + "unsafe" + + "github.com/deepch/vdk/codec/h264parser" + "github.com/deepch/vdk/codec/h265parser" + + "github.com/pion/rtp" + "github.com/pion/webrtc/v4" + . "m7s.live/v5/pkg" + "m7s.live/v5/pkg/codec" + "m7s.live/v5/pkg/util" +) + +type ( + H26xCtx struct { + RTPCtx + seq uint16 + dtsEst util.DTSEstimator + } + H264Ctx struct { + H26xCtx + *codec.H264Ctx + } + H265Ctx struct { + H26xCtx + *codec.H265Ctx + DONL bool + } + AV1Ctx struct { + RTPCtx + *codec.AV1Ctx + } + VP9Ctx struct { + RTPCtx + } + VideoFrame struct { + RTPData + } +) + +var ( + _ IAVFrame = (*VideoFrame)(nil) + _ IVideoCodecCtx = (*H264Ctx)(nil) + _ IVideoCodecCtx = (*H265Ctx)(nil) + _ IVideoCodecCtx = (*AV1Ctx)(nil) +) + +const ( + H265_NALU_AP = h265parser.NAL_UNIT_UNSPECIFIED_48 + H265_NALU_FU = h265parser.NAL_UNIT_UNSPECIFIED_49 + startBit = 1 << 7 + endBit = 1 << 6 + MTUSize = 1460 +) + +func (r *VideoFrame) Parse(data IAVFrame) (err error) { + input := data.(*VideoFrame) + r.Packets = append(r.Packets[:0], input.Packets...) + return +} + +func (r *VideoFrame) Recycle() { + r.RecyclableMemory.Recycle() + r.Packets.Reset() +} + +func (r *VideoFrame) CheckCodecChange() (err error) { + if len(r.Packets) == 0 { + return ErrSkip + } + old := r.ICodecCtx + // 解复用数据 + if err = r.Demux(); err != nil { + return + } + // 处理时间戳和序列号 + pts := r.Packets[0].Timestamp + nalus := r.Raw.(*Nalus) + switch ctx := old.(type) { + case *H264Ctx: + dts := ctx.dtsEst.Feed(pts) + r.SetDTS(time.Duration(dts)) + r.SetPTS(time.Duration(pts)) + + // 检查 SPS、PPS 和 IDR 帧 + var sps, pps []byte + var hasSPSPPS bool + for nalu := range nalus.RangePoint { + nalType := codec.ParseH264NALUType(nalu.Buffers[0][0]) + switch nalType { + case h264parser.NALU_SPS: + sps = nalu.ToBytes() + defer nalus.Remove(nalu) + case h264parser.NALU_PPS: + pps = nalu.ToBytes() + defer nalus.Remove(nalu) + case codec.NALU_IDR_Picture: + r.IDR = true + } + } + + // 如果发现新的 SPS/PPS,更新编解码器上下文 + if hasSPSPPS = sps != nil && pps != nil; hasSPSPPS && (len(ctx.Record) == 0 || !bytes.Equal(sps, ctx.SPS()) || !bytes.Equal(pps, ctx.PPS())) { + var newCodecData h264parser.CodecData + if newCodecData, err = h264parser.NewCodecDataFromSPSAndPPS(sps, pps); err != nil { + return + } + newCtx := &H264Ctx{ + H26xCtx: ctx.H26xCtx, + H264Ctx: &codec.H264Ctx{ + CodecData: newCodecData, + }, + } + // 保持原有的 RTP 参数 + if oldCtx, ok := old.(*H264Ctx); ok { + newCtx.RTPCtx = oldCtx.RTPCtx + } + r.ICodecCtx = newCtx + } else { + // 如果是 IDR 帧但没有 SPS/PPS,需要插入 + if r.IDR && len(ctx.SPS()) > 0 && len(ctx.PPS()) > 0 { + spsRTP := rtp.Packet{ + Header: rtp.Header{ + Version: 2, + SequenceNumber: ctx.SequenceNumber, + Timestamp: pts, + SSRC: ctx.SSRC, + PayloadType: uint8(ctx.PayloadType), + }, + Payload: ctx.SPS(), + } + ppsRTP := rtp.Packet{ + Header: rtp.Header{ + Version: 2, + SequenceNumber: ctx.SequenceNumber, + Timestamp: pts, + SSRC: ctx.SSRC, + PayloadType: uint8(ctx.PayloadType), + }, + Payload: ctx.PPS(), + } + r.Packets = slices.Insert(r.Packets, 0, spsRTP, ppsRTP) + } + } + + // 更新序列号 + for p := range r.Packets.RangePoint { + p.SequenceNumber = ctx.seq + ctx.seq++ + } + case *H265Ctx: + dts := ctx.dtsEst.Feed(pts) + r.SetDTS(time.Duration(dts)) + r.SetPTS(time.Duration(pts)) + // 检查 VPS、SPS、PPS 和 IDR 帧 + var vps, sps, pps []byte + var hasVPSSPSPPS bool + for nalu := range nalus.RangePoint { + switch codec.ParseH265NALUType(nalu.Buffers[0][0]) { + case h265parser.NAL_UNIT_VPS: + vps = nalu.ToBytes() + defer nalus.Remove(nalu) + case h265parser.NAL_UNIT_SPS: + sps = nalu.ToBytes() + defer nalus.Remove(nalu) + case h265parser.NAL_UNIT_PPS: + pps = nalu.ToBytes() + defer nalus.Remove(nalu) + case h265parser.NAL_UNIT_CODED_SLICE_BLA_W_LP, + h265parser.NAL_UNIT_CODED_SLICE_BLA_W_RADL, + h265parser.NAL_UNIT_CODED_SLICE_BLA_N_LP, + h265parser.NAL_UNIT_CODED_SLICE_IDR_W_RADL, + h265parser.NAL_UNIT_CODED_SLICE_IDR_N_LP, + h265parser.NAL_UNIT_CODED_SLICE_CRA: + r.IDR = true + } + } + + // 如果发现新的 VPS/SPS/PPS,更新编解码器上下文 + if hasVPSSPSPPS = vps != nil && sps != nil && pps != nil; hasVPSSPSPPS && (len(ctx.Record) == 0 || !bytes.Equal(vps, ctx.VPS()) || !bytes.Equal(sps, ctx.SPS()) || !bytes.Equal(pps, ctx.PPS())) { + var newCodecData h265parser.CodecData + if newCodecData, err = h265parser.NewCodecDataFromVPSAndSPSAndPPS(vps, sps, pps); err != nil { + return + } + newCtx := &H265Ctx{ + H26xCtx: ctx.H26xCtx, + H265Ctx: &codec.H265Ctx{ + CodecData: newCodecData, + }, + } + // 保持原有的 RTP 参数 + if oldCtx, ok := old.(*H265Ctx); ok { + newCtx.RTPCtx = oldCtx.RTPCtx + } + r.ICodecCtx = newCtx + } else { + // 如果是 IDR 帧但没有 VPS/SPS/PPS,需要插入 + if r.IDR && len(ctx.VPS()) > 0 && len(ctx.SPS()) > 0 && len(ctx.PPS()) > 0 { + vpsRTP := rtp.Packet{ + Header: rtp.Header{ + Version: 2, + SequenceNumber: ctx.SequenceNumber, + Timestamp: pts, + SSRC: ctx.SSRC, + PayloadType: uint8(ctx.PayloadType), + }, + Payload: ctx.VPS(), + } + spsRTP := rtp.Packet{ + Header: rtp.Header{ + Version: 2, + SequenceNumber: ctx.SequenceNumber, + Timestamp: pts, + SSRC: ctx.SSRC, + PayloadType: uint8(ctx.PayloadType), + }, + Payload: ctx.SPS(), + } + ppsRTP := rtp.Packet{ + Header: rtp.Header{ + Version: 2, + SequenceNumber: ctx.SequenceNumber, + Timestamp: pts, + SSRC: ctx.SSRC, + PayloadType: uint8(ctx.PayloadType), + }, + Payload: ctx.PPS(), + } + r.Packets = slices.Insert(r.Packets, 0, vpsRTP, spsRTP, ppsRTP) + } + } + + // 更新序列号 + for p := range r.Packets.RangePoint { + p.SequenceNumber = ctx.seq + ctx.seq++ + } + } + return +} + +func (h264 *H264Ctx) GetInfo() string { + return h264.SDPFmtpLine +} + +func (h265 *H265Ctx) GetInfo() string { + return h265.SDPFmtpLine +} + +func (av1 *AV1Ctx) GetInfo() string { + return av1.SDPFmtpLine +} + +func (r *VideoFrame) Mux(baseFrame *Sample) error { + // 获取编解码器上下文 + codecCtx := r.ICodecCtx + if codecCtx == nil { + switch base := baseFrame.GetBase().(type) { + case *codec.H264Ctx: + var ctx H264Ctx + ctx.H264Ctx = base + ctx.PayloadType = 96 + ctx.MimeType = webrtc.MimeTypeH264 + ctx.ClockRate = 90000 + spsInfo := ctx.SPSInfo + ctx.SDPFmtpLine = fmt.Sprintf("sprop-parameter-sets=%s,%s;profile-level-id=%02x%02x%02x;level-asymmetry-allowed=1;packetization-mode=1", base64.StdEncoding.EncodeToString(ctx.SPS()), base64.StdEncoding.EncodeToString(ctx.PPS()), spsInfo.ProfileIdc, spsInfo.ConstraintSetFlag, spsInfo.LevelIdc) + ctx.SSRC = uint32(uintptr(unsafe.Pointer(&ctx))) + codecCtx = &ctx + case *codec.H265Ctx: + var ctx H265Ctx + ctx.H265Ctx = base + ctx.PayloadType = 98 + ctx.MimeType = webrtc.MimeTypeH265 + ctx.SDPFmtpLine = fmt.Sprintf("profile-id=1;sprop-sps=%s;sprop-pps=%s;sprop-vps=%s", base64.StdEncoding.EncodeToString(ctx.SPS()), base64.StdEncoding.EncodeToString(ctx.PPS()), base64.StdEncoding.EncodeToString(ctx.VPS())) + ctx.ClockRate = 90000 + ctx.SSRC = uint32(uintptr(unsafe.Pointer(&ctx))) + codecCtx = &ctx + } + r.ICodecCtx = codecCtx + } + // 获取时间戳信息 + pts := uint32(baseFrame.GetPTS()) + + switch c := codecCtx.(type) { + case *H264Ctx: + ctx := &c.RTPCtx + var lastPacket *rtp.Packet + if baseFrame.IDR && len(c.RecordInfo.SPS) > 0 && len(c.RecordInfo.PPS) > 0 { + r.Append(ctx, pts, c.SPS()) + r.Append(ctx, pts, c.PPS()) + } + for nalu := range baseFrame.Raw.(*Nalus).RangePoint { + if reader := nalu.NewReader(); reader.Length > MTUSize { + payloadLen := MTUSize + if reader.Length+1 < payloadLen { + payloadLen = reader.Length + 1 + } + //fu-a + mem := r.NextN(payloadLen) + reader.Read(mem[1:]) + fuaHead, naluType := codec.NALU_FUA.Or(mem[1]&0x60), mem[1]&0x1f + mem[0], mem[1] = fuaHead, naluType|startBit + lastPacket = r.Append(ctx, pts, mem) + for payloadLen = MTUSize; reader.Length > 0; lastPacket = r.Append(ctx, pts, mem) { + if reader.Length+2 < payloadLen { + payloadLen = reader.Length + 2 + } + mem = r.NextN(payloadLen) + reader.Read(mem[2:]) + mem[0], mem[1] = fuaHead, naluType + } + lastPacket.Payload[1] |= endBit + } else { + mem := r.NextN(reader.Length) + reader.Read(mem) + lastPacket = r.Append(ctx, pts, mem) + } + } + lastPacket.Header.Marker = true + case *H265Ctx: + ctx := &c.RTPCtx + var lastPacket *rtp.Packet + if baseFrame.IDR && len(c.RecordInfo.SPS) > 0 && len(c.RecordInfo.PPS) > 0 && len(c.RecordInfo.VPS) > 0 { + r.Append(ctx, pts, c.VPS()) + r.Append(ctx, pts, c.SPS()) + r.Append(ctx, pts, c.PPS()) + } + for nalu := range baseFrame.Raw.(*Nalus).RangePoint { + if reader := nalu.NewReader(); reader.Length > MTUSize { + var b0, b1 byte + _ = reader.ReadByteTo(&b0, &b1) + //fu + naluType := byte(codec.ParseH265NALUType(b0)) + b0 = (byte(H265_NALU_FU) << 1) | (b0 & 0b10000001) + + payloadLen := MTUSize + if reader.Length+3 < payloadLen { + payloadLen = reader.Length + 3 + } + mem := r.NextN(payloadLen) + reader.Read(mem[3:]) + mem[0], mem[1], mem[2] = b0, b1, naluType|startBit + lastPacket = r.Append(ctx, pts, mem) + + for payloadLen = MTUSize; reader.Length > 0; lastPacket = r.Append(ctx, pts, mem) { + if reader.Length+3 < payloadLen { + payloadLen = reader.Length + 3 + } + mem = r.NextN(payloadLen) + reader.Read(mem[3:]) + mem[0], mem[1], mem[2] = b0, b1, naluType + } + lastPacket.Payload[2] |= endBit + } else { + mem := r.NextN(reader.Length) + reader.Read(mem) + lastPacket = r.Append(ctx, pts, mem) + } + } + lastPacket.Header.Marker = true + } + return nil +} + +func (r *VideoFrame) Demux() (err error) { + if len(r.Packets) == 0 { + return ErrSkip + } + switch c := r.ICodecCtx.(type) { + case *H264Ctx: + nalus := r.GetNalus() + nalu := nalus.GetNextPointer() + var naluType codec.H264NALUType + gotNalu := func() { + if nalu.Size > 0 { + nalu = nalus.GetNextPointer() + } + } + for packet := range r.Packets.RangePoint { + if len(packet.Payload) < 2 { + continue + } + if packet.Padding { + packet.Padding = false + } + if len(packet.Payload) == 0 { + continue + } + b0 := packet.Payload[0] + if t := codec.ParseH264NALUType(b0); t < 24 { + nalu.PushOne(packet.Payload) + gotNalu() + } else { + offset := t.Offset() + switch t { + case codec.NALU_STAPA, codec.NALU_STAPB: + if len(packet.Payload) <= offset { + return fmt.Errorf("invalid nalu size %d", len(packet.Payload)) + } + for buffer := util.Buffer(packet.Payload[offset:]); buffer.CanRead(); { + if nextSize := int(buffer.ReadUint16()); buffer.Len() >= nextSize { + nalu.PushOne(buffer.ReadN(nextSize)) + gotNalu() + } else { + return fmt.Errorf("invalid nalu size %d", nextSize) + } + } + case codec.NALU_FUA, codec.NALU_FUB: + b1 := packet.Payload[1] + if util.Bit1(b1, 0) { + naluType.Parse(b1) + nalu.PushOne([]byte{naluType.Or(b0 & 0x60)}) + } + if nalu.Size > 0 { + nalu.PushOne(packet.Payload[offset:]) + } else { + continue + } + if util.Bit1(b1, 1) { + gotNalu() + } + default: + return fmt.Errorf("unsupported nalu type %d", t) + } + } + } + nalus.Reduce() + return nil + case *H265Ctx: + nalus := r.GetNalus() + nalu := nalus.GetNextPointer() + gotNalu := func() { + if nalu.Size > 0 { + nalu = nalus.GetNextPointer() + } + } + for _, packet := range r.Packets { + if len(packet.Payload) == 0 { + continue + } + b0 := packet.Payload[0] + if t := codec.ParseH265NALUType(b0); t < H265_NALU_AP { + nalu.PushOne(packet.Payload) + gotNalu() + } else { + var buffer = util.Buffer(packet.Payload) + switch t { + case H265_NALU_AP: + buffer.ReadUint16() + if c.DONL { + buffer.ReadUint16() + } + for buffer.CanRead() { + nalu.PushOne(buffer.ReadN(int(buffer.ReadUint16()))) + gotNalu() + } + if c.DONL { + buffer.ReadByte() + } + case H265_NALU_FU: + if buffer.Len() < 3 { + return io.ErrShortBuffer + } + first3 := buffer.ReadN(3) + fuHeader := first3[2] + if c.DONL { + buffer.ReadUint16() + } + if naluType := fuHeader & 0b00111111; util.Bit1(fuHeader, 0) { + nalu.PushOne([]byte{first3[0]&0b10000001 | (naluType << 1), first3[1]}) + } + nalu.PushOne(buffer) + if util.Bit1(fuHeader, 1) { + gotNalu() + } + default: + return fmt.Errorf("unsupported nalu type %d", t) + } + } + } + nalus.Reduce() + return nil + } + return ErrUnsupportCodec +}