387 lines
13 KiB
C++
387 lines
13 KiB
C++
#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();
|
||
}
|