使用golang對海康sdk進行業務開發
阿新 • • 發佈:2019-09-02
目錄
- 準備工作
- 開發環境資訊
- 改寫HCNetSDK.h標頭檔案
- 開發過程
- 基本資料型別轉換
- 業務開發
- 參考
專案最近需要改造升級:操作海康攝像頭(包括登入,拍照,錄影)等基本功能。經過一段時間研究後,發現使用golang的cgo來進行開發,甚是方便,不用考慮生成多餘的golang程式碼,直接呼叫海康sdk中的函式程式碼。
準備工作
開發環境資訊
在Windows10
下進行開發,使用海康sdk是CH-HCNetSDKV6.0.2.35_build20190411_Win64
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