海康攝像頭SDK跨平臺通用解決方案

首先,還看的SDK分了好幾個版本,至少常用服務器來說有windows或者linux(centos),甚至可能是麒麟。那麼問題來了,從官網提供的例子來看,調用的庫在不同操作系統上不一樣,不能一套代碼發佈在2個平臺上。另外就是很多時候大家開發的環境是windows,而正式部署一般是linux的服務器,這就導致windows調好的代碼在正式環境不一定能正常。

官網提供的例子來看,首先,linux版本沒有c#的例子,用java,部署很麻煩,主要是庫文件的存放路徑配置,寫了一堆說明。然後,c#用的是winform,要改成B/S模式有些還是不太適用的。

講了這麼多,相信很多程序員還是想一套代碼能夠搞定一切,免得windows來一個版本,linux來一個版本,維護也麻煩。現在.net core也是支持跨平臺的,而且部署起來也很簡單,不管是windows還是linux服務器,所以,基於.net core3.1的基礎上將海康的例子改了下,變成網絡可以調用的api接口,給大家提供一個解決方案。

第一步,自己去官網下載海康的sdk包,windows和linux的都要下載,這裏我就不多說啥了;

第二步,創建一個webapi項目,基於.net core3.1平臺;

第三步,海康的CHCNetSDK.cs類,複製到自己項目裏,然後多複製2個,把複製的2個類分別改名爲WindowsCHCNetSDK和LinuxCHCNetSDK,當然,代碼也需要改造下:

�0�2 �0�2 �0�2 �0�2 (1)把WindowsCHCNetSDK和LinuxCHCNetSDK裏的常量、結構體、委託的定義全部刪掉,只留API申明,就比如這樣:

(2)刪除代碼後,API申明有些就會報錯,把這個對象引用指向CHCNetSDK類,也就是像這麼改:

[DllImport(HCNetSDK)]
public static extern bool NET_DVR_SetExceptionCallBack_V30(uint nMessage, IntPtr hWnd, CHCNetSDK.EXCEPYIONCALLBACK fExceptionCallBack, IntPtr pUser);

�0�2(3)CHCNetSDK類加一句代碼,用來識別當前的系統類型(OSPlatform裏還有個FreeBSD和OSX就不考慮了,應該沒人會用蘋果系統做服務器吧!!):

private static bool isWindows = RuntimeInformation.IsOSPlatform(OSPlatform.Windows);

(4)CHCNetSDK類裏的API申明全部按如下改法改一遍:

public static bool NET_DVR_Init()
{
    if (isWindows)
    {
        return WindowsCHCNetSDK.NET_DVR_Init();
    }
    return LinuxCHCNetSDK.NET_DVR_Init();
}

這樣,直接調用NET_DVR_Init()方法,程序就可以自動按平臺來選擇引用的類庫了。

SDK改好了,還需要再改一些結構體,不然你程序運行,可能就會有像是下面這個錯誤:加載類型“WIFI_AUTH_PARAM”,因爲它在 0 偏移位置處包含一個對象字段,該字段已由一個非對象字段不正確地對齊或重疊。

這個錯誤也比較好解決,只需要把接口體頭部的LayoutKind.Explicit改爲LayoutKind.Auto就好了,就像是這樣:

[StructLayoutAttribute(LayoutKind.Auto)]
public struct WIFI_AUTH_PARAM
{
    public UNION_EAP_TTLS EAP_TTLS;//WPA-enterprise/WPA2-enterpris模式適用
			
    public UNION_EAP_PEAP EAP_PEAP; //WPA-enterprise/WPA2-enterpris模式適用

    public UNION_EAP_TLS EAP_TLS; 
}

當然,不止這一處,全文搜下LayoutKind.Explicit,全部改了就好了。

都改好了,那麼這個SDK的方法就可以通用了。下面我們就寫個API接口來試試,我這裏用按時間下載文件的接口來測試:

�0�2 �0�2 �0�2 �0�2 新建一個controller,然後編寫接口如下:

[HttpGet("SaveByTime")]
public IActionResult SaveByTime(string ip, string userName, string password, int port = 8000, DateTime? startTime = null, DateTime? endTime = null, int recSecond = 20, int channelNum = 1)
{
    if (!CHCNetSDK.NET_DVR_Init())
    {
        return Ok(new { code = 998, msg = "NET_DVR_Init error!" });
    }

    DateTime now = DateTime.Now;
    DateTime start, end;
    end = endTime ?? DateTime.Now;
    if (!startTime.HasValue)
    {
        start = end.AddSeconds(recSecond * -1);
    }
    else
    {
        start = startTime.Value;
    }
    Device device = HKHelper.GetDevice(ip, userName, password, port);
    if (device == null)
    {
        return Ok(new { code = 999, msg = "NET_DVR_Login_V30 failed" });
    }
    if (device.DownHandle >= 0)
    {
        return Ok(new { code = 8022, msg = "Downloading, please stop firstly!" });//正在下載,請先停止下載
    }
    CHCNetSDK.NET_DVR_PLAYCOND struDownPara = new CHCNetSDK.NET_DVR_PLAYCOND();
    struDownPara.dwChannel = (uint)channelNum; //通道號 Channel number  

    //設置下載的開始時間 Set the starting time
    struDownPara.struStartTime.dwYear = (uint)start.Year;
    struDownPara.struStartTime.dwMonth = (uint)start.Month;
    struDownPara.struStartTime.dwDay = (uint)start.Day;
    struDownPara.struStartTime.dwHour = (uint)start.Hour;
    struDownPara.struStartTime.dwMinute = (uint)start.Minute;
    struDownPara.struStartTime.dwSecond = (uint)start.Second;
    //設置下載的結束時間 Set the stopping time
    struDownPara.struStopTime.dwYear = (uint)end.Year;
    struDownPara.struStopTime.dwMonth = (uint)end.Month;
    struDownPara.struStopTime.dwDay = (uint)end.Day;
    struDownPara.struStopTime.dwHour = (uint)end.Hour;
    struDownPara.struStopTime.dwMinute = (uint)end.Minute;
    struDownPara.struStopTime.dwSecond = (uint)end.Second;

    string sVideoFileName = Guid.NewGuid().ToString("n");  //錄像文件保存路徑和文件名 the path and file name to save      
    sVideoFileName = Path.Combine("路徑", sVideoFileName + ".mp4");
    device.DownHandle = CHCNetSDK.NET_DVR_GetFileByTime_V40(device.UserID, sVideoFileName, ref struDownPara);
    if (device.DownHandle < 0)
    {
        uint iLastErr = CHCNetSDK.NET_DVR_GetLastError();
        return Ok(new { code = iLastErr, msg = "NET_DVR_GetFileByTime_V40 failed" });
    }
    uint iOutValue = 0;
    if (!CHCNetSDK.NET_DVR_PlayBackControl_V40(device.DownHandle, CHCNetSDK.NET_DVR_PLAYSTART, IntPtr.Zero, 0, IntPtr.Zero, ref iOutValue))
    {
        uint iLastErr = CHCNetSDK.NET_DVR_GetLastError();
        return Ok(new { code = iLastErr, msg = "NET_DVR_PLAYSTART failed" }); //下載控制失敗,輸出錯誤號
    }
    device.DownHandle = -1;
    return Ok(new { code = 0, data = sVideoFileName });
}

寫好了,不要急着運行,把下載的海康開發文檔的相關dll拷貝到運行目錄下(正常來說是bin\Debug\netcoreapp3.1),這下可以運行了,啓動後,你可以調用SaveByTime接口試下,有沒發現成功了(前提當然是攝像頭需要能夠通過ip被訪問到)。

如果成功,我們再把這個代碼發佈到linux服務器上試下(同理,攝像頭需要能夠通過ip被訪問到),提醒下,你的保存路徑別忘了改,linux可是沒有cdef盤的說法!這裏我使用的是docker部署,先來寫個dockerfile文件:

# 基於.net core3.1鏡像
FROM mcr.microsoft.com/dotnet/core/aspnet:3.1-buster-slim
# 設置工作目錄
WORKDIR /app
# 端口號
EXPOSE 80
# 複製文件
COPY . .
# 入口點
ENTRYPOINT ["dotnet", "HKSuo.dll"]

將發佈的程序上傳到服務器,還有這個dockerfile,再加上各種海康的sdk文件(so文件),像這樣:

運行打包命令 "docker build -t hksdk:1.0.0 ."

成功後再運行"docker run -i -d -t --name=hksdk -p 80:80�0�2--restart=always hksdk:1.0.0"啓動這個容器。

這下你可以通過服務器地址再次訪問剛纔的接口,有沒發現也是成功的!

好拉,這樣就可以做到一套代碼跨平臺支持了,就是一開始要整理這個代碼,確實是繁!!

 

另外,應該還有很多朋友,想實現實時預覽視頻把,這個百度下有一篇文章是這樣的:

C# 實現海康攝像頭在任意瀏覽器中預覽_qq_31753779的博客-CSDN博客_c#海康攝像頭兩週以來一直研究海康視頻在谷歌、火狐等瀏覽器中的顯示問題。在今天終於有了一點小心得。發表出來,希望有問題或者其他建議的老師積極給我建議。在海康瀏覽器的平臺中,因爲他本身只支持在IE或者IE內核中顯示,這種問題不能根本性的解決在多瀏覽器裏面的問題,我在網上查找了IE-Tab在谷歌瀏覽器中顯示,但是由於需要用戶手動切換瀏覽模式,所以說用起來不是特別方便。後面研究了一下,可以通過海康PlantFor...https://blog.csdn.net/qq_31753779/article/details/82023916這個做法可以參考,這樣就不用裝插件了,我把也寫成了API接口:

�0�2就是調用後,啓動一個線程,按設定頻率定時抓圖,然後用websoket方法,定時將這張圖推送給客戶端就可以了。小夥伴們,可以不用考慮抓圖和推送的延遲率,websocket你只管把圖片讀取出來推送給客戶端就好,如果圖片太長時間沒更新,那就是攝像頭斷了,畫面也就是卡住的感覺。

我用java寫個websocket,主要代碼就像這樣:

@OnOpen
public void onOpen(@PathParam("deviceId") String deviceId, Session session) {
    deviceInfoService = applicationContext.getBean(TbDeviceInfoService.class);
    exceptionService = applicationContext.getBean(TbExceptionService.class);
    this.session = session;
    TbDeviceInfoVo device = deviceInfoService.getById(deviceId);
    if (device == null) {
        return;
    }
    try {
        String body = HttpRequest.get(String.format(SystemConfig.CameraUrl() + "camera/LiveView?ip=%s&userName=%s&password=%s",
                device.getIpaddr(), device.getLoginId(), device.getLoginPwd()))
                .keepAlive(true)
                .execute().body();
        if (StringUtils.isNotBlank(body)) {
            JSONObject jsonObject = JSON.parseObject(body);
            if(jsonObject.getInteger("code") == CodeEnum.SUCCESS){
                CameraSocketUtil.put(session.getId(), this, device.getIpaddr());
            }
        }
    }
    catch (Exception e){
        exceptionService.saveException("LiveViewHandle", "onOpen", e);
    }
}

CameraSocketUtil類裏啓動了一個線程,做定時推送:

public static void put(String key, LiveViewHandle liveViewHandle, String ip) {
    CameraSocketObject obj = new CameraSocketObject();
    obj.setSocket(liveViewHandle);
    obj.setIp(ip);
    if(!ipList.contains(ip)){
        ipList.add(ip);
        //創建圖像輸出線程
        Runner run = new Runner(ip);
        run.start();
    }
    webSocketMap.put(key, obj);
}
class Runner extends Thread {
    String ip;

    public Runner(String ip) {
        this.ip = ip;
    }

    @Override
    public void run() {
        while (true) {
            try {
                String imgBase64Str = convertFileToBase64(SystemConfig.FilePath() + ip.replace(".", "") + ".jpg");
                List<LiveViewHandle> handles = CameraSocketUtil.getHandleByIp(ip);
                if(handles != null && handles.size() > 0){
                    for(LiveViewHandle handle : handles){
                        try {
                            handle.sendMessage(imgBase64Str);
                        }
                        catch (Exception e){
                            e.printStackTrace();
                        }
                    }
                }else{
                    //沒有需要推送的客戶端,將線程結束
                    break;
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
            finally {
                sleep(500);
            }
        }
    }

    private void sleep(int millis) {
        try {
            Thread.sleep(millis);
        } catch (Exception e) {
        }
    }


    /**
     * 本地文件(圖片、excel等)轉換成Base64字符串
     *
     * @param imgPath
     */
    private static String convertFileToBase64(String imgPath) {
        byte[] data = null;
        // 讀取圖片字節數組
        try {
            InputStream in = new FileInputStream(imgPath);
            data = new byte[in.available()];
            in.read(data);
            in.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
        // 對字節數組進行Base64編碼,得到Base64編碼的字符串
        BASE64Encoder encoder = new BASE64Encoder();
        String base64Str = encoder.encode(data);
        return base64Str;
    }
}

 

最後,我已經把整理好的代碼上傳了,有興趣的朋友可以下載參考,地址是:

.netcore實現海康SDK跨平臺兼容-C#文檔類資源-CSDN下載.netcore實現對海康SDK的跨平臺兼容,支持windows和linux平臺,實現海康攝像頭在更多下載資源、學習資料請訪問CSDN下載頻道.https://download.csdn.net/download/ziyouli/84996531注意,appSettings.json裏有個SavePath,可以配置文件保存路徑,windows和linux路徑不同,只需要調整配置文件就可以啦。

 

在linux(CentOS7)下使用docker部署的時候,可能會出現SDK報錯誤29,大概提示爲:“海康SDK註冊失敗,userId:-1,錯誤號:29”,這是因爲so庫調取失敗造成的。但是這個錯誤並不是必然出現的,我在測試的時候就出現了一臺可以,一臺報錯的情況。

如果報錯,按以下步驟處理一次即可:

1. 將庫文件拷貝到/usr/lib64/hklib(32位的拷貝到lib目錄)下,然後在/etc/profile文件下增加該路徑的環境變量,然後通過命令source /etc/profile讓環境變量生效;

2. /etc/ld.so.conf 加上/hklib和子文件夾目錄,再用ldconfig命令使配置生效;

這2步驟可以看參考下海康官網的使用說明進行操作,完成後修改下dockerfile,加上ENV LD_LIBRARY_PATH /usr/lib64/hklib這句;

最後用docker run啓動的時候增加物理磁盤掛載:-v /usr/lib64/hklib:/usr/lib64/hklib即可。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章