目录
项目最近需要改造升级:操作海康摄像头(包括登录,拍照,录像)等基本功能。经过一段时间研究后,发现使用golang的cgo来进行开发,甚是方便,不用考虑生成多余的golang代码,直接调用海康sdk中的函数代码。
准备工作
开发环境信息
在Windows10
下进行开发,使用海康sdk是CH-HCNetSDKV6.0.2.35_build20190411_Win64
版本。go版本号go1.12.7
。
改写HCNetSDK.h头文件
海康威视提供的头文件是不能被cgo所识别的,而cgo是不能使用C++
相关的东西的,比如标准库或者C++的面向对象特性,导致其会疯狂的报语法错误.
查询资料后得知,该头文件中有以下情况,就不能通过编译:
- 注释里面套注释,例如这样的
//这里是注释1 /*这里是注释2*/
#define xxx
时,若后面函数被xxx
修饰,当xxx
无对应的值而仅仅是被定义的时候;- c++语法,例如联合嵌套在C++中是不支持的,c++的bool类型等
在开发的时候,发现原HCNetSDK.h
文件里面有五万多行,如果全部的改造,那么会花费大量的时间。在c++开发的同事的建议下:只取出与开发功能相关的代码进行改造(改造为cgo可以识别的代码)。
改造规则如下:
- 去掉所有注释
- 去掉函数前面的
NET_DVR_API
和__std
- 去掉CALLBACK
- 为没有tag的结构体加上tag前缀
- 删除无实现的函数
开发过程
基本数据类型转换
由于在开发过程中涉及到基本的golang和c的数据类型转换,查阅资料后,转换对应关系如下:
C语言类型 | CGO类型 | Go语言类型 |
---|---|---|
char | C.char | byte |
singed char | C.schar | int8 |
unsigned char | C.uchar | uint8 |
short | C.short | int16 |
unsigned short | C.ushort | uint16 |
int | C.int | int32 |
unsigned int | C.uint | uint32 |
long | C.long | int32 |
unsigned long | C.ulong | uint32 |
long long int | C.longlong | int64 |
unsigned long long int | C.ulonglong | uint64 |
float | C.float | float32 |
double | C.double | float64 |
size_t | C.size_t | uint |
注意 C 中的整形比如 int 在标准中是没有定义具体字长的,但一般默认认为是 4 字节,对应 CGO 类型中 C.int 则明确定义了字长是 4 ,但 golang 中的 int 字长则是 8 ,因此对应的 golang 类型不是 int 而是 int32 。为了避免误用,C 代码最好使用 C99 标准的数值类型,对应的转换关系如下:
C语言类型 | CGO类型 | Go语言类型 |
---|---|---|
int8_t | C.int8_t | int8 |
uint8_t | C.uint8_t | uint8 |
int16_t | C.int16_t | int16 |
uint16_t | C.uint16_t | uint16 |
int32_t | C.int32_t | int32 |
uint32_t | C.uint32_t | uint32 |
int64_t | C.int64_t | int64 |
uint64_t | C.uint64_t | uint64 |
业务开发
HCNetSDK.go
package main/*#cgo CFLAGS: -I.#cgo LDFLAGS: -L. -lHCCore#cgo LDFLAGS: -L. -lHCNetSDK#include "HCNetSDK.h"#include#include #include */import "C"import ( "errors" "fmt" "time" "unsafe")// 是否有错误func isErr(oper string) error { errno := int64(C.NET_DVR_GetLastError()) if errno > 0 { reMsg := fmt.Sprintf("%s摄像头失败,失败代码号:%d", oper, errno) return errors.New(reMsg) } return nil}// 初始化海康摄像头func Init() (err error) { C.NET_DVR_Init() if err = isErr("Init"); err != nil { return } // 设置连接时间 C.NET_DVR_SetConnectTime(C.DWORD(2000), C.DWORD(1)) if err = isErr("SetConnectTime"); err != nil { return } return nil}// 登录摄像头func Login() (int64,error) { var deviceinfoV30 C.NET_DVR_DEVICEINFO_V30 c_ip := C.CString("192.168.1.64") defer C.free(unsafe.Pointer(c_ip)) c_login := C.CString("admin") defer C.free(unsafe.Pointer(c_login)) c_password := C.CString("admin") defer C.free(unsafe.Pointer(c_password)) msgId := C.NET_DVR_Login_V30(c_ip,C.WORD(8080),c_login,c_password, (*C.NET_DVR_DEVICEINFO_V30)(unsafe.Pointer(&deviceinfoV30)), ) if int64(msgId) < 0 { if err := isErr("Login"); err != nil { return -1,err } return -1,errors.New("登录摄像头失败") } return int64(msgId),nil}// 退出摄像头登录// uid:摄像头登录成功的idfunc Logout(uid int64) error { C.NET_DVR_Logout_V30(C.LONG(uid)) if err := isErr("Logout"); err != nil { return err } return nil}// 播放视频// uid:摄像头登录成功的id// 返回播放视频标识 pidfunc Play(uid int64)(int64, error) { var pDetectInfo C.NET_DVR_CLIENTINFO pDetectInfo.lChannel = C.LONG(1) pid := C.NET_DVR_RealPlay_V30(C.LONG(uid),(*C.NET_DVR_CLIENTINFO)(unsafe.Pointer(&pDetectInfo)),nil,nil,C.BOOL(1)) if int64(pid) < 0 { if err := isErr("Play"); err != nil { return -1,err } return -1,errors.New("播放失败") } return int64(pid),nil}// 抓拍func Capture(uid int64) (string, error){ picPath := "D:\\" + time.Now().Format("20060102150405") + ".jpeg" var jpegpara C.NET_DVR_JPEGPARA var lChannel uint32 = 1 c_path := C.CString(picPath) defer C.free(unsafe.Pointer(c_path)) msgId := C.NET_DVR_CaptureJPEGPicture(C.LONG(uid), C.LONG(lChannel), (*C.NET_DVR_JPEGPARA)(unsafe.Pointer(&jpegpara)), c_path, ) if int64(msgId) < 0 { if err := isErr("Capture"); err != nil { return "",err } return "",errors.New("抓拍失败") } return picPath,nil}// 停止相机// pid 播放标识符func PtzStop(pid int64) error { msgId := C.NET_DVR_StopRealPlay(C.LONG(pid)) if int64(msgId) < 0 { if err := isErr("PtzStop"); err != nil { return err } return errors.New("停止相机失败") } return nil}func main() { var err error err = Init() defer Close() if err != nil { log.Fatal(err.Error()) } var uid int64 if uid,err = Login();err != nil { log.Fatal(err.Error()) } var picPath string if picPath,err = Capture(uid);err != nil { log.Fatal(err.Error()) } log.Println("图片路径:",picPath) var pid int64 if pid,err = Play(uid);err != nil { log.Fatal(err.Error()) } if err = PtzStop(pid);err != nil { log.Fatal(err.Error()) } if err = Logout(uid);err != nil { log.Fatal(err.Error()) }}
Makefile
export CGO_ENABLED=1export WDIR=${PWD}all: windowswindows: CGO_LDFLAGS_ALLOW=".*" CGO_CFLAGS="-I${WDIR}/include" CGO_LDFLAGS="-L${WDIR}/lib/Windows -Wl,--enable-stdcall-fixup,-rpath=${WDIR}/lib/Windows -lHCNetSDK" GOOS=windows CC=x86_64-w64-mingw32-gcc CXX=x86_64-w64-mingw32-g++ go build -ldflags "-s -w" -o build/Windows/hk.exe src/HCNetSDK.go cp lib/Windows/HCNetSDK.dll build/Windows/ cp lib/Windows/HCCore.dll build/Windows/ cp -r lib/Windows/HCNetSDKCom/ build/Windows/clean: rm -r build/
通过make命令该文件即可。(注意海康开发文档中的说明)