#include "CamHandler.h" // ---------------------------------------------------------------------------------------------------- // 构造/析构 // ---------------------------------------------------------------------------------------------------- CamHandler::CamHandler(const QString &name,QObject *parent) : QObject(parent) , mDevice(nullptr) , mStream(nullptr) , mDeviceInfo(nullptr) { camName = name; // <<< 增加 camName 的初始化 state = false; saveFlag = false; // 连接定时器的超时信号到采集槽 connect(&mAcquireTimer, &QTimer::timeout, this, &CamHandler::onAcquireTimerTimeout); // 设置一个较小的间隔,让定时器尽可能快地被调用,以持续检查是否有新图像 mAcquireTimer.setInterval(17); // 约 60FPS } CamHandler::~CamHandler() { // 确保在销毁对象时清理资源 if (mStream) { if (!mDevice || !mStream) return; // 1. 停止计时器 if (mAcquireTimer.isActive()) { mAcquireTimer.stop(); // emit logMsg("Acquisition timer stopped."); } // 2. 获取 AcquisitionStop 命令 PvGenParameterArray *lDeviceParams = mDevice->GetParameters(); PvGenCommand *lStop = dynamic_cast(lDeviceParams->Get("AcquisitionStop")); // 3. 停止采集和禁用流 if (lStop != nullptr) { // emit logMsg("Sending AcquisitionStop command to the device"); lStop->Execute(); } // emit logMsg("Disable streaming on the controller."); mDevice->StreamDisable(); // 4. 清理缓冲区:终止队列中的所有缓冲区并将它们移到输出队列 // emit logMsg("Aborting buffers still in stream"); mStream->AbortQueuedBuffers(); // 5. 从输出队列中检索剩余的缓冲区,丢弃它们 PvBuffer *lBuffer = NULL; PvResult lOperationResult; // 必须清空队列,防止下次采集时使用错误的旧缓冲区 while (mStream->GetQueuedBufferCount() > 0) { mStream->RetrieveBuffer(&lBuffer, &lOperationResult); // 注意:这里没有释放 lBuffer,因为缓冲区资源将在析构或断开时处理 } // emit logMsg("Acquisition stopped and buffers cleaned up."); mStream->Close(); PvStream::Free(mStream); mStream = nullptr; } if (mDevice) { mDevice->Disconnect(); PvDevice::Free(mDevice); mDevice = nullptr; } // mDeviceInfo 由 mSystem 管理,无需手动释放 mDeviceInfo = nullptr; // mSystem 是成员变量,在 CamHandler 析构时自动释放其资源 } // ---------------------------------------------------------------------------------------------------- // 公有接口实现 // ---------------------------------------------------------------------------------------------------- // 修正:不再是 static,使用成员变量 mSystem QList CamHandler::listAvailableDevices() { QList deviceList; // 使用成员变量 mSystem 发现设备。mSystem 的生命周期与 CamHandler 一致。 mSystem.Find(); // 遍历发现的设备 for (uint32_t i = 0; i < mSystem.GetDeviceCount(); i++) { const PvDeviceInfo *lInfo = mSystem.GetDeviceInfo(i); // 获取设备的显示ID (例如:型号/序列号) QString lDisplayID = QString::fromLocal8Bit(lInfo->GetDisplayID().GetAscii()); deviceList.append(lDisplayID + " (ID: " + QString::fromLocal8Bit(lInfo->GetConnectionID().GetAscii()) + ")"); } return deviceList; } bool CamHandler::connectToDevice(const QString &aConnectionID) { QString errMsg; if (mDevice) { emit logMsg("Already connected. Please disconnect first."); return false; } // 1. 选择设备 // 确保在 selectDevice 前调用 Find() (虽然 listAvailableDevices 可能已经调用,但再次调用更安全) mDeviceInfo = selectDevice(aConnectionID); if (!mDeviceInfo) { errMsg = "Error: Device not found for ID:" + aConnectionID; emit logMsg(errMsg); return false; } // 2. 连接到设备 (使用静态工厂方法 PvDevice::CreateAndConnect) // mDeviceInfo 现在是有效的,不会导致访问冲突 PvResult lResult; mDevice = PvDevice::CreateAndConnect(mDeviceInfo, &lResult); if (mDevice == nullptr) { emit logMsg("Error: Unable to connect to device. Result:" + QString(lResult.GetCodeString())); return false; } // 3. 打开流 if (!openStream()) { disconnectDevice(); return false; } // 4. 配置流 (例如:GigE Vision 性能) // configureStream(); // 5. 创建和队列缓冲区 // createStreamBuffers(); emit logMsg("Successfully connected and configured stream."); state = true; return true; } void CamHandler::startAcquisition() { // 5. 创建和队列缓冲区 createStreamBuffers(); if (!mDevice || !mStream) { emit logMsg("Not connected. Cannot start acquisition."); return; } // 2. 获取 AcquisitionStart 命令 PvGenParameterArray *lDeviceParams = mDevice->GetParameters(); PvGenCommand *lStart = dynamic_cast(lDeviceParams->Get("AcquisitionStart")); if (lStart != nullptr) { emit logMsg("Enabling streaming and sending AcquisitionStart command."); mDevice->StreamEnable(); // 启用流 lStart->Execute(); // 发送命令 // 3. 启动定时器,驱动采集循环 mAcquireTimer.start(); } else { emit logMsg("Error: AcquisitionStart command not found on device."); } } void CamHandler::stopAcquisition() { if (!mDevice || !mStream) return; // 1. 停止计时器 if (mAcquireTimer.isActive()) { mAcquireTimer.stop(); emit logMsg("Acquisition timer stopped."); } // 2. 获取 AcquisitionStop 命令 PvGenParameterArray *lDeviceParams = mDevice->GetParameters(); PvGenCommand *lStop = dynamic_cast(lDeviceParams->Get("AcquisitionStop")); // 3. 停止采集和禁用流 if (lStop != nullptr) { emit logMsg("Sending AcquisitionStop command to the device"); lStop->Execute(); } emit logMsg("Disable streaming on the controller."); mDevice->StreamDisable(); // 4. 清理缓冲区:终止队列中的所有缓冲区并将它们移到输出队列 emit logMsg("Aborting buffers still in stream"); mStream->AbortQueuedBuffers(); // 5. 从输出队列中检索剩余的缓冲区,丢弃它们 PvBuffer *lBuffer = NULL; PvResult lOperationResult; // 必须清空队列,防止下次采集时使用错误的旧缓冲区 while (mStream->GetQueuedBufferCount() > 0) { mStream->RetrieveBuffer(&lBuffer, &lOperationResult); // 注意:这里没有释放 lBuffer,因为缓冲区资源将在析构或断开时处理 } emit logMsg("Acquisition stopped and buffers cleaned up."); } void CamHandler::disconnectDevice() { // 停止采集,清理流资源 if (mStream) { stopAcquisition(); // 确保流和缓冲区已清理 emit logMsg("Closing stream"); mStream->Close(); PvStream::Free(mStream); mStream = nullptr; } // 断开设备连接,释放设备资源 if (mDevice) { emit logMsg("Disconnecting device"); mDevice->Disconnect(); PvDevice::Free(mDevice); mDevice = nullptr; } // mDeviceInfo 由 mSystem 管理,置空指针 mDeviceInfo = nullptr; emit logMsg("Disconnected and resources freed."); state = false; } // ---------------------------------------------------------------------------------------------------- // 槽函数实现 (采集循环驱动) // ---------------------------------------------------------------------------------------------------- void CamHandler::onAcquireTimerTimeout() { // 如果没有连接或流,直接返回 if (!mStream) { logMsg("Stream not active."); return; } PvBuffer *lBuffer = nullptr; PvResult lOperationResult; // 尝试检索下一个缓冲区,使用较短的超时时间 (10ms) PvResult lResult = mStream->RetrieveBuffer(&lBuffer, &lOperationResult, 10); if (lResult.IsOK()) { if (lOperationResult.IsOK()) { // 成功采集到有效图像 PvPayloadType lType = lBuffer->GetPayloadType(); if (lType == PvPayloadTypeImage) { // 转换为 QImage 并发送信号 QImage image = convertPvBufferToQImage(lBuffer); if (!image.isNull()) { emit imageReady(image); } else { emit logMsg("Image conversion failed."); } } else { emit logMsg("Buffer retrieved, but does not contain an image payload. Requeuing."); } // 重新排队缓冲区 mStream->QueueBuffer(lBuffer); } else { // 检索到缓冲区但操作结果不成功(例如:超时、重发过多) emit logMsg("RetrieveBuffer operation result is not OK:" + QString(lOperationResult.GetCodeString()) + ". Requeuing."); mStream->QueueBuffer(lBuffer); // 必须重新排队 } } // else { // RetrieveBuffer 失败 (未取到缓冲区,例如超时) } } // ---------------------------------------------------------------------------------------------------- // 内部帮助函数实现 // ---------------------------------------------------------------------------------------------------- // 修正:使用成员变量 mSystem 查找设备 const PvDeviceInfo *CamHandler::selectDevice(const QString &aConnectionID) { mSystem.Find(); // mSystem.Find() 已经在 connectToDevice 中调用 for (uint32_t i = 0; i < mSystem.GetDeviceCount(); i++) { const PvDeviceInfo *lInfo = mSystem.GetDeviceInfo(i); // 从成员变量 mSystem 获取 QString tmp = QString::fromLocal8Bit(lInfo->GetConnectionID().GetAscii()); if (aConnectionID == tmp) { // 找到匹配的设备,返回的指针由 mSystem 管理,在 CamHandler 生命周期内有效 return lInfo; } } return nullptr; } bool CamHandler::openStream() { emit logMsg("Opening stream to device."); PvResult lResult; // 使用静态工厂方法 PvStream::CreateAndOpen mStream = PvStream::CreateAndOpen(mDeviceInfo->GetConnectionID(), &lResult); if (mStream == nullptr) { emit logMsg("Error: Unable to stream from device. Result:" + QString(lResult.GetCodeString())); return false; } return true; } void CamHandler::createStreamBuffers() { // 从设备获取 payload size (图像/数据的字节大小) uint32_t lSize = mDevice->GetPayloadSize(); // 确定要创建的缓冲区数量 uint32_t lBufferCount = (mStream->GetQueuedBufferMaximum() < BUFFER_COUNT) ? mStream->GetQueuedBufferMaximum() : BUFFER_COUNT; emit logMsg("Allocating" + QString::number(lBufferCount) + " buffers of size " + QString::number(lSize) + " bytes."); // 分配 PvBuffer 数组 // 注意:在析构函数或 disconnectDevice 中需要释放这个内存! PvBuffer *lBuffers = new PvBuffer[lBufferCount]; // **重要提示:您的原始代码没有在析构函数中释放 lBuffers 指针!** // 这是一个内存泄漏风险。要解决这个问题,您需要将 lBuffers 存储为一个成员变量 (例如 QList) // 或者只在析构函数/disconnectDevice 中确保释放它们。 for (uint32_t i = 0; i < lBufferCount; i++) { // 为每个缓冲区分配内存 (lBuffers + i)->Alloc(static_cast(lSize)); // 将缓冲区排队到流的输入队列 mStream->QueueBuffer(lBuffers + i); } } QImage CamHandler::convertPvBufferToQImage(PvBuffer *aBuffer) { // 获取图像信息 PvImage *lImage = aBuffer->GetImage(); uint32_t lWidth = lImage->GetWidth(); uint32_t lHeight = lImage->GetHeight(); // 使用全局线程池启动任务 (异步执行) // 简化实现:仅支持 Mono8 (8位灰度) 格式 if (lImage->GetPixelType() == PvPixelMono8) { if (saveFlag) { const void *lDataPointer = lImage->GetDataPointer(); quint32 lDataSize = lImage->GetImageSize(); // 3. 复制原始数据到 QByteArray(线程安全的关键) QByteArray rawDataCopy(reinterpret_cast(lDataPointer), lDataSize); // 4. 创建 SaveTask 并提交给全局线程池 SaveTask *task = new SaveTask(rawDataCopy,camName); QThreadPool::globalInstance()->start(task); } // 直接使用图像数据创建 QImage return QImage((uchar *) lImage->GetDataPointer(), lWidth, lHeight, lWidth, // 步长/字节数 QImage::Format_Grayscale8) .copy(); // .copy() 确保 QImage 拥有数据,安全地在 Qt 环境中使用 } emit logMsg("Unsupported pixel format"); return QImage(); }