723 lines
23 KiB
C++
723 lines
23 KiB
C++
#include "HikDevice.h"
|
||
#include "VrLog.h"
|
||
#include "VrError.h"
|
||
#include <cstring>
|
||
#include <cstdint>
|
||
#include <algorithm>
|
||
#include <thread>
|
||
#include <chrono>
|
||
|
||
// 海康相机默认账户密码
|
||
#define HIK_DEFAULT_USERNAME "admin"
|
||
#define HIK_DEFAULT_PASSWORD "wood0908"
|
||
|
||
// 静态成员变量定义
|
||
std::map<long, CHikDevice*> 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<WORD>(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<HWND>(reinterpret_cast<uintptr_t>(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<std::mutex> 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<int>(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<std::mutex> lock(m_frameMutex);
|
||
|
||
if (m_currentFrameData.empty()) {
|
||
return ERR_CODE(DEV_RESULT_EMPTY);
|
||
}
|
||
|
||
int dataSize = static_cast<int>(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<CHikDevice*>(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<std::mutex> 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<std::mutex> 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<std::mutex> 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<std::mutex> 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<CHikDevice*>(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<std::mutex> lock(m_frameMutex);
|
||
|
||
// 确保缓冲区大小足够
|
||
if (m_currentFrameData.size() != static_cast<size_t>(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<unsigned char*>(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<int>(Y + 1.402 * V);
|
||
int G = static_cast<int>(Y - 0.344 * U - 0.714 * V);
|
||
int B = static_cast<int>(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<unsigned char>(B);
|
||
rgbData[rgbIndex + 1] = static_cast<unsigned char>(G);
|
||
rgbData[rgbIndex + 2] = static_cast<unsigned char>(R);
|
||
rgbData[rgbIndex + 3] = 255; // Alpha
|
||
}
|
||
}
|
||
}
|