GrabBag/Device/HikDevice/Src/HikDevice.cpp

723 lines
23 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#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
}
}
}