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


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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM