首先,還看的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"啓動這個容器。
這下你可以通過服務器地址再次訪問剛纔的接口,有沒發現也是成功的!
好拉,這樣就可以做到一套代碼跨平臺支持了,就是一開始要整理這個代碼,確實是繁!!
另外,應該還有很多朋友,想實現實時預覽視頻把,這個百度下有一篇文章是這樣的:
�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即可。