1. 程式人生 > >使用golang對海康sdk進行業務開發

使用golang對海康sdk進行業務開發

目錄

  • 準備工作
    • 開發環境資訊
    • 改寫HCNetSDK.h標頭檔案
  • 開發過程
    • 基本資料型別轉換
    • 業務開發
  • 參考

專案最近需要改造升級:操作海康攝像頭(包括登入,拍照,錄影)等基本功能。經過一段時間研究後,發現使用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 <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-rec