CameraGrabber/camhandler.cpp
2025-10-24 19:16:07 +08:00

387 lines
13 KiB
C++
Raw Permalink 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 "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<PvGenCommand *>(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<QString> CamHandler::listAvailableDevices()
{
QList<QString> 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<PvGenCommand *>(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<PvGenCommand *>(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<PvBuffer*>)
// 或者只在析构函数/disconnectDevice 中确保释放它们。
for (uint32_t i = 0; i < lBufferCount; i++) {
// 为每个缓冲区分配内存
(lBuffers + i)->Alloc(static_cast<uint32_t>(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<const char *>(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();
}