#include "HikDevice.h" #include "VrLog.h" #include "VrError.h" #include #include #include #include #include // 海康相机默认账户密码 #define HIK_DEFAULT_USERNAME "admin" #define HIK_DEFAULT_PASSWORD "wood0908" // 静态成员变量定义 std::map CHikDevice::s_portDeviceMap; std::mutex CHikDevice::s_mapMutex; // ============ 静态工厂方法 ============ int IHikDevice::CreateObject(IHikDevice** ppDevice) { if (!ppDevice) { return ERR_CODE(DEV_ARG_INVAILD); } *ppDevice = new CHikDevice(); return (*ppDevice != nullptr) ? SUCCESS : ERR_CODE(DATA_ERR_MEM); } // ============ 构造函数和析构函数 ============ CHikDevice::CHikDevice() { LOG_INFO("CHikDevice created\n"); } CHikDevice::~CHikDevice() { // 停止预览 StopPreview(); // 登出 Logout(); // 清理SDK CleanupSDK(); LOG_INFO("CHikDevice destroyed\n"); } // ============ SDK 初始化 ============ int CHikDevice::InitSDK() { if (m_bSDKInitialized) { LOG_INFO("CHikDevice::InitSDK - SDK already initialized\n"); return SUCCESS; } LOG_INFO("CHikDevice::InitSDK - Initializing HCNetSDK\n"); // 初始化海康SDK if (!NET_DVR_Init()) { m_lastError = NET_DVR_GetLastError(); LOG_ERR("NET_DVR_Init failed, error code: %d\n", m_lastError); return ERR_CODE(DEV_OPEN_ERR); } // 设置连接超时和重连 NET_DVR_SetConnectTime(2000, 1); // 连接超时2秒 NET_DVR_SetReconnect(10000, TRUE); // 重连间隔10秒 // 设置异常回调 NET_DVR_SetExceptionCallBack_V30(0, NULL, HikSDKExceptionCallback, this); m_bSDKInitialized = true; LOG_INFO("CHikDevice::InitSDK - HCNetSDK initialized successfully\n"); return SUCCESS; } void CHikDevice::CleanupSDK() { if (!m_bSDKInitialized) { return; } NET_DVR_Cleanup(); m_bSDKInitialized = false; LOG_INFO("CHikDevice::CleanupSDK - HCNetSDK cleaned up\n"); } // ============ 登录/登出 ============ int CHikDevice::Login(const HikDeviceConfig& config) { LOG_INFO("CHikDevice::Login - IP: %s, Port: %d\n", config.ip.c_str(), config.port); if (!m_bSDKInitialized) { LOG_ERR("CHikDevice::Login - SDK not initialized\n"); return ERR_CODE(DEV_NO_OPEN); } if (m_lUserID >= 0) { LOG_WARN("CHikDevice::Login - Already logged in, logout first\n"); Logout(); } // 保存配置 m_config = config; // 如果用户名或密码为空,使用默认值 std::string username = config.username.empty() ? HIK_DEFAULT_USERNAME : config.username; std::string password = config.password.empty() ? HIK_DEFAULT_PASSWORD : config.password; if (config.username.empty() || config.password.empty()) { LOG_INFO("CHikDevice::Login - Using default credentials (username: %s)\n", username.c_str()); } // 登录参数 NET_DVR_USER_LOGIN_INFO loginInfo = {0}; NET_DVR_DEVICEINFO_V40 deviceInfo = {0}; loginInfo.bUseAsynLogin = FALSE; // 同步登录 strncpy(loginInfo.sDeviceAddress, config.ip.c_str(), NET_DVR_DEV_ADDRESS_MAX_LEN - 1); loginInfo.wPort = static_cast(config.port); strncpy(loginInfo.sUserName, username.c_str(), NAME_LEN - 1); strncpy(loginInfo.sPassword, password.c_str(), NAME_LEN - 1); // 执行登录 m_lUserID = NET_DVR_Login_V40(&loginInfo, &deviceInfo); if (m_lUserID < 0) { m_lastError = NET_DVR_GetLastError(); LOG_ERR("NET_DVR_Login_V40 failed, error code: %d\n", m_lastError); UpdateStatus(EHikDeviceStatus::Disconnected); if (m_lastError == NET_DVR_ERROR_DEVICE_NOT_ACTIVATED) { LOG_INFO("Device already activated\n"); return ERR_CODE(DEV_UNACTIVATE); } return ERR_CODE(NET_ERR_CONNECT); } UpdateStatus(EHikDeviceStatus::Connected); LOG_INFO("CHikDevice::Login - Login successful, UserID: %ld, Channels: %d\n", m_lUserID, deviceInfo.struDeviceV30.byChanNum); return SUCCESS; } void CHikDevice::Logout() { if (m_lUserID >= 0) { // 先停止预览 StopPreview(); NET_DVR_Logout(m_lUserID); LOG_INFO("CHikDevice::Logout - Logged out, UserID: %ld\n", m_lUserID); m_lUserID = -1; } UpdateStatus(EHikDeviceStatus::Disconnected); } bool CHikDevice::IsLoggedIn() const { return m_lUserID >= 0; } // ============ 预览控制 ============ int CHikDevice::StartPreview() { LOG_INFO("CHikDevice::StartPreview\n"); if (m_lUserID < 0) { LOG_ERR("CHikDevice::StartPreview - Not logged in\n"); return ERR_CODE(DEV_NO_OPEN); } if (m_lRealPlayHandle >= 0) { LOG_WARN("CHikDevice::StartPreview - Preview already started\n"); return SUCCESS; } // 设置预览参数 NET_DVR_PREVIEWINFO previewInfo = {0}; previewInfo.hPlayWnd = NULL; // 不使用窗口显示,通过回调获取数据 previewInfo.lChannel = m_config.channelNo; previewInfo.dwStreamType = m_config.streamType; previewInfo.dwLinkMode = 0; // TCP方式 previewInfo.bBlocked = TRUE; // 开始预览 m_lRealPlayHandle = NET_DVR_RealPlay_V40(m_lUserID, &previewInfo, HikRealDataCallback, this); if (m_lRealPlayHandle < 0) { m_lastError = NET_DVR_GetLastError(); LOG_ERR("NET_DVR_RealPlay_V40 failed, error code: %d\n", m_lastError); return ERR_CODE(DEV_CTRL_ERR); } m_bPreviewing = true; UpdateStatus(EHikDeviceStatus::Previewing); LOG_INFO("CHikDevice::StartPreview - Preview started, Handle: %ld\n", m_lRealPlayHandle); return SUCCESS; } int CHikDevice::StartPreviewEx(void* hWnd) { LOG_INFO("CHikDevice::StartPreviewEx - hWnd: %p\n", hWnd); if (m_lUserID < 0) { LOG_ERR("CHikDevice::StartPreviewEx - Not logged in\n"); return ERR_CODE(DEV_NO_OPEN); } if (m_lRealPlayHandle >= 0) { LOG_WARN("CHikDevice::StartPreviewEx - Preview already started\n"); return SUCCESS; } // 设置预览参数 NET_DVR_PREVIEWINFO previewInfo = {0}; #ifdef _WIN32 previewInfo.hPlayWnd = (HWND)hWnd; // Windows: HWND 是指针类型 #else // Linux: HWND 是 unsigned int (X11 窗口 ID),需要通过 uintptr_t 转换 previewInfo.hPlayWnd = static_cast(reinterpret_cast(hWnd)); #endif previewInfo.lChannel = m_config.channelNo; previewInfo.dwStreamType = m_config.streamType; previewInfo.dwLinkMode = 0; // TCP方式 previewInfo.bBlocked = TRUE; previewInfo.dwDisplayBufNum = 1; // 最小显示缓冲,降低延迟 // 开始预览(不需要回调,SDK直接渲染) m_lRealPlayHandle = NET_DVR_RealPlay_V40(m_lUserID, &previewInfo, NULL, NULL); if (m_lRealPlayHandle < 0) { m_lastError = NET_DVR_GetLastError(); LOG_ERR("NET_DVR_RealPlay_V40 failed, error code: %d\n", m_lastError); return ERR_CODE(DEV_CTRL_ERR); } // 设置预览延迟模式为实时(最低延迟) NET_DVR_SetPlayerBufNumber(m_lRealPlayHandle, 1); m_bPreviewing = true; UpdateStatus(EHikDeviceStatus::Previewing); LOG_INFO("CHikDevice::StartPreviewEx - Preview started with hardware rendering, Handle: %ld\n", m_lRealPlayHandle); return SUCCESS; } void CHikDevice::StopPreview() { // 先停止预览 if (m_lRealPlayHandle >= 0) { NET_DVR_StopRealPlay(m_lRealPlayHandle); LOG_INFO("CHikDevice::StopPreview - Preview stopped, Handle: %ld\n", m_lRealPlayHandle); m_lRealPlayHandle = -1; } // 停止 PlayM4 解码 if (m_lPlayM4Port >= 0) { // 先移除端口映射 { std::lock_guard lock(s_mapMutex); s_portDeviceMap.erase(m_lPlayM4Port); } if (m_bPlayM4Playing) { PlayM4_Stop(m_lPlayM4Port); m_bPlayM4Playing = false; } PlayM4_CloseStream(m_lPlayM4Port); PlayM4_FreePort(m_lPlayM4Port); m_lPlayM4Port = -1; LOG_INFO("CHikDevice::StopPreview - PlayM4 decoder stopped and port freed\n"); } m_bPreviewing = false; if (m_lUserID >= 0) { UpdateStatus(EHikDeviceStatus::Connected); } else { UpdateStatus(EHikDeviceStatus::Disconnected); } } bool CHikDevice::IsPreviewing() const { return m_bPreviewing; } // ============ 状态管理 ============ EHikDeviceStatus CHikDevice::GetStatus() const { return m_status; } void CHikDevice::UpdateStatus(EHikDeviceStatus status) { if (m_status != status) { m_status = status; LOG_INFO("CHikDevice status changed to: %d\n", static_cast(status)); if (m_statusCallback) { m_statusCallback(status, m_pStatusCallbackUser); } } } // ============ 回调设置 ============ void CHikDevice::SetDecodedFrameCallback(HikDecodedFrameCallback callback, void* pUser) { m_frameCallback = callback; m_pFrameCallbackUser = pUser; } void CHikDevice::SetStatusCallback(HikDeviceStatusCallback callback, void* pUser) { m_statusCallback = callback; m_pStatusCallbackUser = pUser; } void CHikDevice::SetExceptionCallback(HikExceptionCallback callback, void* pUser) { m_exceptionCallback = callback; m_pExceptionCallbackUser = pUser; } // ============ 帧数据获取 ============ int CHikDevice::GetCurrentFrame(unsigned char* pBuffer, int bufferSize, HikFrameInfo& frameInfo) { std::lock_guard lock(m_frameMutex); if (m_currentFrameData.empty()) { return ERR_CODE(DEV_RESULT_EMPTY); } int dataSize = static_cast(m_currentFrameData.size()); if (bufferSize < dataSize) { return ERR_CODE(DATA_ERR_LEN); } memcpy(pBuffer, m_currentFrameData.data(), dataSize); frameInfo = m_currentFrameInfo; return dataSize; } const HikDeviceConfig& CHikDevice::GetConfig() const { return m_config; } // ============ 抓图 ============ int CHikDevice::CaptureToFile(const std::string& filePath, bool isJpeg) { if (m_lUserID < 0) { LOG_ERR("CHikDevice::CaptureToFile - Not logged in\n"); return ERR_CODE(DEV_NO_OPEN); } if (isJpeg) { // JPEG抓图 NET_DVR_JPEGPARA jpegPara = {0}; jpegPara.wPicSize = 0xFF; // 使用当前分辨率 jpegPara.wPicQuality = 0; // 最高质量 if (!NET_DVR_CaptureJPEGPicture(m_lUserID, m_config.channelNo, &jpegPara, (char*)filePath.c_str())) { m_lastError = NET_DVR_GetLastError(); LOG_ERR("NET_DVR_CaptureJPEGPicture failed, error code: %d\n", m_lastError); return ERR_CODE(DEV_CTRL_ERR); } } else { // BMP抓图 (需要预览句柄) if (m_lRealPlayHandle < 0) { LOG_ERR("CHikDevice::CaptureToFile - Preview not started for BMP capture\n"); return ERR_CODE(DEV_NO_OPEN); } if (!NET_DVR_CapturePicture(m_lRealPlayHandle, (char*)filePath.c_str())) { m_lastError = NET_DVR_GetLastError(); LOG_ERR("NET_DVR_CapturePicture failed, error code: %d\n", m_lastError); return ERR_CODE(DEV_CTRL_ERR); } } LOG_INFO("CHikDevice::CaptureToFile - Captured to: %s\n", filePath.c_str()); return SUCCESS; } // ============ 云台控制 ============ int CHikDevice::PTZControl(int command, bool stop, int speed) { if (m_lRealPlayHandle < 0) { LOG_ERR("CHikDevice::PTZControl - Preview not started\n"); return ERR_CODE(DEV_NO_OPEN); } DWORD dwStop = stop ? 1 : 0; if (!NET_DVR_PTZControlWithSpeed(m_lRealPlayHandle, command, dwStop, speed)) { m_lastError = NET_DVR_GetLastError(); LOG_ERR("NET_DVR_PTZControlWithSpeed failed, error code: %d\n", m_lastError); return ERR_CODE(DEV_CTRL_ERR); } return SUCCESS; } // ============ OSD配置 ============ int CHikDevice::ConfigureOSD(bool showChanName, bool showOsd) { LOG_INFO("CHikDevice::ConfigureOSD - showChanName: %d, showOsd: %d\n", showChanName, showOsd); if (m_lUserID < 0) { LOG_ERR("CHikDevice::ConfigureOSD - Not logged in\n"); return ERR_CODE(DEV_NO_OPEN); } // 获取当前图像参数 DWORD uiReturnLen = 0; NET_DVR_PICCFG_V30 struPicCfg = {0}; struPicCfg.dwSize = sizeof(NET_DVR_PICCFG_V30); if (!NET_DVR_GetDVRConfig(m_lUserID, NET_DVR_GET_PICCFG_V30, m_config.channelNo, &struPicCfg, sizeof(NET_DVR_PICCFG_V30), &uiReturnLen)) { m_lastError = NET_DVR_GetLastError(); LOG_ERR("NET_DVR_GetDVRConfig(PICCFG_V30) failed, error: %d\n", m_lastError); return ERR_CODE(DEV_CTRL_ERR); } // 修改OSD设置 struPicCfg.dwShowChanName = showChanName ? 1 : 0; // 通道名称 struPicCfg.dwShowOsd = showOsd ? 1 : 0; // OSD时间 // 设置图像参数 if (!NET_DVR_SetDVRConfig(m_lUserID, NET_DVR_SET_PICCFG_V30, m_config.channelNo, &struPicCfg, sizeof(NET_DVR_PICCFG_V30))) { m_lastError = NET_DVR_GetLastError(); LOG_ERR("NET_DVR_SetDVRConfig(PICCFG_V30) failed, error: %d\n", m_lastError); return ERR_CODE(DEV_CTRL_ERR); } LOG_INFO("CHikDevice::ConfigureOSD - OSD configured successfully\n"); return SUCCESS; } // ============ 静态回调函数实现 ============ void CALLBACK CHikDevice::HikRealDataCallback(LONG lRealHandle, DWORD dwDataType, BYTE* pBuffer, DWORD dwBufSize, void* pUser) { CHikDevice* pDevice = static_cast(pUser); if (!pDevice) return; switch (dwDataType) { case NET_DVR_SYSHEAD: { // 系统头数据 - 初始化 PlayM4 解码器 LOG_DEBUG("CHikDevice received system header, size: %lu\n", dwBufSize); // 获取 PlayM4 端口 if (pDevice->m_lPlayM4Port < 0) { if (!PlayM4_GetPort(&pDevice->m_lPlayM4Port)) { LOG_ERR("PlayM4_GetPort failed, error: %lu\n", PlayM4_GetLastError(pDevice->m_lPlayM4Port)); return; } } // 设置流播放模式 if (!PlayM4_SetStreamOpenMode(pDevice->m_lPlayM4Port, STREAME_REALTIME)) { LOG_ERR("PlayM4_SetStreamOpenMode failed, error: %lu\n", PlayM4_GetLastError(pDevice->m_lPlayM4Port)); return; } // 打开流(减小缓冲区到350KB以降低延迟) if (!PlayM4_OpenStream(pDevice->m_lPlayM4Port, pBuffer, dwBufSize, 350 * 1024)) { LOG_ERR("PlayM4_OpenStream failed, error: %lu\n", PlayM4_GetLastError(pDevice->m_lPlayM4Port)); return; } // 设置解码输出为 RGB32 格式(避免手动YUV转换) if (!PlayM4_SetDecCBStream(pDevice->m_lPlayM4Port, T_RGB32)) { LOG_WARN("PlayM4_SetDecCBStream(T_RGB32) failed, will use YV12 conversion\n"); } // 注册端口到实例的映射(解决64位系统指针截断问题) { std::lock_guard lock(s_mapMutex); s_portDeviceMap[pDevice->m_lPlayM4Port] = pDevice; } // 设置解码回调(通过端口号映射查找实例,nUser 参数不使用) #ifdef _WIN32 if (!PlayM4_SetDecCallBackMend(pDevice->m_lPlayM4Port, PlayM4DecodeCallback, pDevice->m_lPlayM4Port)) { #else if (!PlayM4_SetDecCallBackMend(pDevice->m_lPlayM4Port, PlayM4DecodeCallback, nullptr)) { #endif LOG_ERR("PlayM4_SetDecCallBackMend failed, error: %lu\n", PlayM4_GetLastError(pDevice->m_lPlayM4Port)); // 移除映射 { std::lock_guard lock(s_mapMutex); s_portDeviceMap.erase(pDevice->m_lPlayM4Port); } return; } // 开始播放(不需要窗口句柄,仅解码) if (!PlayM4_Play(pDevice->m_lPlayM4Port, NULL)) { LOG_ERR("PlayM4_Play failed, error: %lu\n", PlayM4_GetLastError(pDevice->m_lPlayM4Port)); // 移除映射 { std::lock_guard lock(s_mapMutex); s_portDeviceMap.erase(pDevice->m_lPlayM4Port); } return; } pDevice->m_bPlayM4Playing = true; LOG_INFO("CHikDevice PlayM4 decoder initialized and started, port: %ld\n", pDevice->m_lPlayM4Port); break; } case NET_DVR_STREAMDATA: { // 流数据 - 输入到 PlayM4 解码器 if (pDevice->m_lPlayM4Port >= 0 && pDevice->m_bPlayM4Playing) { if (!PlayM4_InputData(pDevice->m_lPlayM4Port, pBuffer, dwBufSize)) { // 缓冲区满时可能会失败,这是正常的 DWORD dwError = PlayM4_GetLastError(pDevice->m_lPlayM4Port); if (dwError != PLAYM4_BUF_OVER) { LOG_WARN("PlayM4_InputData failed, error: %lu\n", dwError); } } } break; } default: break; } } #ifdef _WIN32 void CALLBACK CHikDevice::PlayM4DecodeCallback(long nPort, char* pBuf, long nSize, FRAME_INFO* pFrameInfo, long nUser, long nReserved2) #else void CALLBACK CHikDevice::PlayM4DecodeCallback(int nPort, char* pBuf, int nSize, FRAME_INFO* pFrameInfo, void* nUser, int nReserved2) #endif { (void)nUser; // 未使用,通过端口号查找实例 (void)nReserved2; // 未使用 if (!pBuf || !pFrameInfo) { return; } // 通过端口号查找实例 CHikDevice* pDevice = nullptr; { std::lock_guard lock(s_mapMutex); auto it = s_portDeviceMap.find(nPort); if (it != s_portDeviceMap.end()) { pDevice = it->second; } } if (!pDevice) { return; } // 只处理视频帧(YV12 或 RGB32 格式) if (pFrameInfo->nType == T_YV12 || pFrameInfo->nType == T_RGB32) { pDevice->ProcessDecodedFrame(pBuf, nSize, pFrameInfo); } } void CALLBACK CHikDevice::HikSDKExceptionCallback(DWORD dwType, LONG lUserID, LONG lHandle, void* pUser) { CHikDevice* pDevice = static_cast(pUser); if (!pDevice) { return; } pDevice->HandleException(dwType); } // ============ 内部方法实现 ============ void CHikDevice::ProcessDecodedFrame(char* pBuf, long nSize, FRAME_INFO* pFrameInfo) { if (!pBuf || !pFrameInfo) { return; } int width = pFrameInfo->nWidth; int height = pFrameInfo->nHeight; int rgbSize = width * height * 4; // RGB32 // 更新当前帧数据 { std::lock_guard lock(m_frameMutex); // 确保缓冲区大小足够 if (m_currentFrameData.size() != static_cast(rgbSize)) { m_currentFrameData.resize(rgbSize); } // 根据帧类型处理 if (pFrameInfo->nType == T_RGB32) { // 直接使用 RGB32 数据(PlayM4 已转换) memcpy(m_currentFrameData.data(), pBuf, rgbSize); } else if (pFrameInfo->nType == T_YV12) { // YV12 格式需要手动转换 unsigned char* yPlane = reinterpret_cast(pBuf); unsigned char* vPlane = yPlane + width * height; unsigned char* uPlane = vPlane + (width / 2) * (height / 2); ConvertYV12ToRGB32(yPlane, uPlane, vPlane, m_currentFrameData.data(), width, height); } else { LOG_WARN("Unsupported frame type: %d\n", pFrameInfo->nType); return; } // 更新帧信息 m_currentFrameInfo.width = width; m_currentFrameInfo.height = height; m_currentFrameInfo.frameType = pFrameInfo->nType; m_currentFrameInfo.timestamp = pFrameInfo->nStamp; } // 调用帧回调 if (m_frameCallback) { m_frameCallback(m_currentFrameData.data(), rgbSize, m_currentFrameInfo, m_pFrameCallbackUser); } } void CHikDevice::HandleException(DWORD dwType) { EHikExceptionType exType = EHikExceptionType::None; switch (dwType) { case EXCEPTION_RECONNECT: LOG_INFO("CHikDevice reconnecting (preview)...\n"); exType = EHikExceptionType::Reconnect; UpdateStatus(EHikDeviceStatus::Reconnecting); break; case EXCEPTION_PREVIEW: LOG_WARN("CHikDevice preview exception\n"); exType = EHikExceptionType::PreviewException; m_bPreviewing = false; UpdateStatus(EHikDeviceStatus::Error); break; case EXCEPTION_SERIAL: LOG_WARN("CHikDevice serial exception\n"); exType = EHikExceptionType::SerialException; break; case EXCEPTION_ALARMRECONNECT: LOG_INFO("CHikDevice alarm reconnecting...\n"); exType = EHikExceptionType::AlarmReconnect; break; case EXCEPTION_EXCHANGE: LOG_WARN("CHikDevice exchange exception\n"); exType = EHikExceptionType::ExchangeException; break; default: LOG_DEBUG("CHikDevice unknown exception: %lu\n", dwType); break; } // 调用异常回调 if (m_exceptionCallback && exType != EHikExceptionType::None) { m_exceptionCallback(exType, m_pExceptionCallbackUser); } } void CHikDevice::ConvertYV12ToRGB32(const unsigned char* yPlane, const unsigned char* uPlane, const unsigned char* vPlane, unsigned char* rgbData, int width, int height) { for (int y = 0; y < height; y++) { for (int x = 0; x < width; x++) { int yIndex = y * width + x; int uvIndex = (y / 2) * (width / 2) + (x / 2); int Y = yPlane[yIndex]; int U = uPlane[uvIndex] - 128; int V = vPlane[uvIndex] - 128; // YUV to RGB 转换公式 int R = static_cast(Y + 1.402 * V); int G = static_cast(Y - 0.344 * U - 0.714 * V); int B = static_cast(Y + 1.772 * U); // 限制范围 R = (std::max)(0, (std::min)(255, R)); G = (std::max)(0, (std::min)(255, G)); B = (std::max)(0, (std::min)(255, B)); // RGB32 格式: B G R A int rgbIndex = yIndex * 4; rgbData[rgbIndex + 0] = static_cast(B); rgbData[rgbIndex + 1] = static_cast(G); rgbData[rgbIndex + 2] = static_cast(R); rgbData[rgbIndex + 3] = 255; // Alpha } } }