准备工作

开发环境信息

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 中的整形比如 int 在标准中是没有定义具体字长的,但一般默认认为是 4 字节,对应 CGO 类型中 C.int 则明确定义了字长是 4 ,但 golang 中的 int 字长则是 8 ,因此对应的 golang 类型不是 int 而是 int32 。为了避免误用,C 代码最好使用 C99 标准的数值类型,对应的转换关系如下:

业务开发

HCNetSDK.go

package main

/*
#cgo CFLAGS: -I.
#cgo LDFLAGS: -L. -lHCCore
#cgo LDFLAGS: -L. -lHCNetSDK
#include "HCNetSDK.h"
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

*/
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:摄像头登录成功的id
func Logout(uid int64) error {
    C.NET_DVR_Logout_V30(C.LONG(uid))
    if err := isErr("Logout"); err != nil {
        return err
    }
    return nil
}

// 播放视频
// uid:摄像头登录成功的id
// 返回播放视频标识 pid
func 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=1
export WDIR=${PWD}

all: windows

windows:
    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命令该文件即可。(注意海康开发文档中的说明)


参考

SWIG编译海康威视SDK 使用golang

golang cgo 使用总结

hikavision-recover

09-03 03:28