diff --git a/Device/RsLidarDevice/Inc/IRsLidarDevice.h b/Device/RsLidarDevice/Inc/IRsLidarDevice.h index 269539b9..3eddf6f3 100644 --- a/Device/RsLidarDevice/Inc/IRsLidarDevice.h +++ b/Device/RsLidarDevice/Inc/IRsLidarDevice.h @@ -118,7 +118,7 @@ public: /// 获取驱动版本 virtual std::string GetVersion() = 0; - /// 获取丢帧计数(m_stuffedQueue 满时被丢弃的帧数) + /// 获取包序号缺口/半包异常计数(用于判断接收或组包是否丢数据) virtual uint64_t GetDroppedFrameCount() const = 0; /// 获取当前就绪队列深度(待消费的帧数) @@ -127,8 +127,8 @@ public: /// 获取指定 SDK ErrCode 的累计触发次数(接受原始 SDK error_code,如 0x40/0x48/0x49/0x42) virtual uint64_t GetExceptionCount(int errCode) const = 0; - /// 获取并清零最近一个 5s 滚动窗口内用户回调耗时统计 - /// 注意:内部统计字段每 5s 由 processCloudThread 重置,此接口仅返回当前累计快照 + /// 获取 Start 以来用户回调耗时累计统计 + /// 注意:设备内部 5s 诊断日志会输出窗口增量,此接口保留累计快照以兼容旧调用 virtual RsCallbackStats GetCallbackLatencyStats() const = 0; /// 获取就绪队列历史峰值深度(Open 以来) diff --git a/Device/RsLidarDevice/Src/RsLidarDevice.cpp b/Device/RsLidarDevice/Src/RsLidarDevice.cpp index 8b579f47..b17d2d03 100644 --- a/Device/RsLidarDevice/Src/RsLidarDevice.cpp +++ b/Device/RsLidarDevice/Src/RsLidarDevice.cpp @@ -2,6 +2,216 @@ #include #include #include +#include +#include +#include +#include +#ifdef _WIN32 +#include +#endif +#include + +namespace +{ +constexpr uint32_t kRsem4DefaultRings = 520; +constexpr uint32_t kRsem4ReserveColumns = 1200; +constexpr uint32_t kRsem4HalfRings = kRsem4DefaultRings / 2; +constexpr size_t kRsem4VecselsPerColumn = 26; +constexpr size_t kRsem4PixelsPerVcsel = 20; +constexpr size_t kRsem4SurfaceCount = 4; +constexpr size_t kRsem4BlocksPerPacket = 260; +constexpr size_t kRsem4MsopTailLen = 12; +constexpr size_t kDeliveryFramePoolSize = 6; +constexpr float kRsem4DistanceResolution = 0.005f; +constexpr float kRsem4MinDistance = 0.5f; +constexpr float kRsem4MaxDistance = 350.0f; +constexpr double kDeg01ToRad = 3.14159265358979323846 / 18000.0; + +#pragma pack(push, 1) +struct Rsem4DifopPkt +{ + uint8_t id[8]; + uint8_t reserved0[106]; + int8_t yawOffset[26]; + int16_t pitchAngle[520]; + int16_t surfacePitchOffset[4]; + uint8_t reserved1[110]; + uint16_t dataLength; + uint16_t counter; + uint32_t dataId; + uint32_t crc32; +}; + +struct Rsem4Difop2Pkt +{ + uint8_t id[4]; + uint8_t reserved0[63]; + uint8_t surfaceId; + uint8_t pixelCnt; + uint8_t vcselCnt; + int8_t yawOffset[26]; + int16_t pitchAngle[520]; + int16_t surfacePitchOffset[4]; + int16_t rollOffset; + uint8_t reserved1[4]; + uint16_t dataLength; + uint16_t counter; + uint32_t dataId; + uint32_t crc32; +}; + +struct Rsem4Difop0624Pkt +{ + uint8_t id[4]; + uint8_t reserved0[306]; + int8_t yawOffset[26]; + int16_t pitchAngle[520]; + int16_t surfacePitchOffset[4]; + uint8_t reserved1[6]; + uint16_t dataLength; + uint16_t counter; + uint32_t dataId; + uint32_t crc32; +}; + +struct Rsem4Channel +{ + uint16_t distance; + uint8_t intensity; + uint8_t pointAttribute; +}; + +struct Rsem4Block +{ + Rsem4Channel channel[1]; +}; + +struct Rsem4MsopHeader +{ + uint8_t id[4]; + uint16_t pktSeq; + uint16_t protocolVersion; + uint8_t returnMode; + uint8_t timeMode; + RSTimestampUTC timestamp; + uint8_t frameSync; + uint8_t frameRate; + uint16_t columnNum; + int16_t yawAngle; + uint8_t packMode; + uint8_t surfaceId; + uint16_t reserved; + uint8_t lidarType; + uint8_t temperature; +}; + +struct Rsem4MsopHeader2 +{ + uint8_t id[4]; + uint16_t pktSeq; + uint8_t reserved[2]; +}; + +struct Rsem4MsopPkt +{ + Rsem4MsopHeader header; + Rsem4Block blocks[260]; + uint16_t dataLength; + uint16_t counter; + uint32_t dataId; + uint32_t crc32; +}; +#pragma pack(pop) + +inline void SetZeroPoint(SVzNLPointXYZI& dst) +{ + dst.fData[0] = 0.0f; + dst.fData[1] = 0.0f; + dst.fData[2] = 0.0f; + dst.fData_c[0] = 0.0f; +} + +inline int16_t SwapI16(int16_t value) +{ + uint16_t v = static_cast(value); + v = static_cast((v << 8) | (v >> 8)); + return static_cast(v); +} + +inline uint16_t ReadU16BE(uint16_t value) +{ + return static_cast((value << 8) | (value >> 8)); +} + +inline int32_t NormalizeAngle01(int32_t angle) +{ + angle %= 36000; + if (angle < 0) + angle += 36000; + return angle; +} + +struct Deg01TrigTable +{ + std::array sinValues; + std::array cosValues; + + Deg01TrigTable() + { + for (size_t i = 0; i < sinValues.size(); ++i) + { + const double rad = static_cast(i) * kDeg01ToRad; + sinValues[i] = static_cast(std::sin(rad)); + cosValues[i] = static_cast(std::cos(rad)); + } + } +}; + +inline const Deg01TrigTable& TrigTable() +{ + static const Deg01TrigTable table; + return table; +} + +inline float SinDeg01(int32_t angle) +{ + return TrigTable().sinValues[NormalizeAngle01(angle)]; +} + +inline float CosDeg01(int32_t angle) +{ + return TrigTable().cosValues[NormalizeAngle01(angle)]; +} + +inline bool IsCompleteRsem4Packet(const uint8_t* data, size_t size) +{ + static const uint8_t kHeader[] = {0x55, 0xAA, 0x5A, 0xA5}; + return size >= sizeof(kHeader) && std::memcmp(data, kHeader, sizeof(kHeader)) == 0; +} + +inline void UpdateMax(std::atomic& target, uint64_t value) +{ + uint64_t prev = target.load(std::memory_order_relaxed); + while (value > prev && + !target.compare_exchange_weak(prev, value, std::memory_order_relaxed)) + {} +} + +inline void UpdateMaxSize(std::atomic& target, size_t value) +{ + size_t prev = target.load(std::memory_order_relaxed); + while (value > prev && + !target.compare_exchange_weak(prev, value, std::memory_order_relaxed)) + {} +} + +inline uint64_t ElapsedUs(std::chrono::steady_clock::time_point begin, + std::chrono::steady_clock::time_point end) +{ + return static_cast( + std::chrono::duration_cast(end - begin).count()); +} +} // WSAStartup 引用计数 static std::atomic g_sockRefCount{0}; @@ -40,6 +250,7 @@ int IRsLidarDevice::CreateObject(IRsLidarDevice** ppDevice) CRsLidarDevice::CRsLidarDevice() : m_pDriver(std::make_unique>()) { + initializeRsem4DefaultAngles(); } CRsLidarDevice::~CRsLidarDevice() @@ -48,6 +259,35 @@ CRsLidarDevice::~CRsLidarDevice() CloseDevice(); } +void* CRsLidarDevice::operator new(std::size_t size) +{ +#ifdef _WIN32 + void* ptr = _aligned_malloc(size, 64); + if (!ptr) + throw std::bad_alloc(); + return ptr; +#else + void* ptr = nullptr; + if (posix_memalign(&ptr, 64, size) != 0) + throw std::bad_alloc(); + return ptr; +#endif +} + +void CRsLidarDevice::operator delete(void* ptr) noexcept +{ +#ifdef _WIN32 + _aligned_free(ptr); +#else + std::free(ptr); +#endif +} + +void CRsLidarDevice::operator delete(void* ptr, std::size_t) noexcept +{ + CRsLidarDevice::operator delete(ptr); +} + // ============================================================ // InitDevice // ============================================================ @@ -110,6 +350,16 @@ int CRsLidarDevice::OpenDevice(const RsLidarConfig& config) CloseDevice(); m_param = toDriverParam(config); + if (config.minDistance > 0.0f || config.maxDistance > 0.0f) + { + m_rsem4MinDistance = (config.minDistance > 0.0f) ? config.minDistance : 0.0f; + m_rsem4MaxDistance = (config.maxDistance > 0.0f) ? config.maxDistance : kRsem4MaxDistance; + } + else + { + m_rsem4MinDistance = kRsem4MinDistance; + m_rsem4MaxDistance = kRsem4MaxDistance; + } m_pDriver->regPointCloudCallback( [this]() -> SdkCloudPtr { return this->getFreeCloud(); }, @@ -146,7 +396,6 @@ int CRsLidarDevice::CloseDevice() m_bOpened = false; m_freeQueue.clear(); - m_stuffedQueue.clear(); m_frameFreeQueue.clear(); m_deliveryQueue.clear(); @@ -184,41 +433,60 @@ int CRsLidarDevice::Start() m_droppedFrameCount.store(0, std::memory_order_relaxed); m_totalPushCount.store(0, std::memory_order_relaxed); for (auto& c : m_exceptionCounts) c.store(0, std::memory_order_relaxed); + m_parseAccumUs.store(0, std::memory_order_relaxed); + m_parseMaxUs.store(0, std::memory_order_relaxed); + m_parseCount.store(0, std::memory_order_relaxed); + m_frameWaitAccumUs.store(0, std::memory_order_relaxed); + m_frameWaitMaxUs.store(0, std::memory_order_relaxed); + m_frameWaitCount.store(0, std::memory_order_relaxed); + m_frameWaitTimeoutCount.store(0, std::memory_order_relaxed); + m_deliveryDropCount.store(0, std::memory_order_relaxed); + m_deliveredFrameCount.store(0, std::memory_order_relaxed); + m_deliveryQueuePeak.store(0, std::memory_order_relaxed); m_cbMaxUs.store(0, std::memory_order_relaxed); m_cbAccumUs.store(0, std::memory_order_relaxed); m_cbCount.store(0, std::memory_order_relaxed); - m_qPeak.store(0, std::memory_order_relaxed); + m_diagLastPushCount = 0; + m_diagLastQueueDropCount = 0; + m_diagLastParseCount = 0; + m_diagLastParseAccumUs = 0; + m_diagLastFrameWaitCount = 0; + m_diagLastFrameWaitAccumUs = 0; + m_diagLastFrameWaitTimeoutCount = 0; + m_diagLastDeliveryDropCount = 0; + m_diagLastDeliveredFrameCount = 0; + m_diagLastCbCount = 0; + m_diagLastCbAccumUs = 0; + m_diagLastExceptionCounts.fill(0); + m_diagLastLogTime = std::chrono::steady_clock::now(); + resetPacketFrameBuilder(); // 清空可能残留的预热缓冲(防止 Stop→Start 循环累积膨胀) m_freeQueue.clear(); - m_stuffedQueue.clear(); - for (int i = 0; i < 32; ++i) + for (int i = 0; i < 4; ++i) { auto msg = std::make_shared(); - msg->points.reserve(500000); // 预分配 SDK 内部缓冲,避免 SDK push_back 反复 realloc m_freeQueue.push(std::move(msg)); } m_bStopProcess = false; - // 预分配交付帧:3 帧 × 600 线 × 1500 点 × 32B ≈ 86MB - // 帧 0=解析线程填充中 帧 1=交付队列中 帧 2=空闲备用 + // 预分配交付帧:6 帧 × 1200 线 × 520 点 × 32B ≈ 120MB + // 帧 0=SDK packet callback 填充中,其余用于交付队列和短时回调抖动缓冲 m_deliveryQueue.clear(); m_frameFreeQueue.clear(); - for (int i = 0; i < 3; ++i) + for (size_t i = 0; i < kDeliveryFramePoolSize; ++i) { auto frame = std::make_shared(); - frame->reserveLines(600, 1500); + frame->reserveLines(kRsem4ReserveColumns, kRsem4DefaultRings); m_frameFreeQueue.push(frame); } - m_pDriver->start(); - m_bRunning = true; m_bStopDelivery = false; m_deliveryThread = std::thread(&CRsLidarDevice::deliveryThread, this); - m_processThread = std::thread(&CRsLidarDevice::processCloudThread, this); + m_pDriver->start(); return 0; } @@ -230,18 +498,14 @@ int CRsLidarDevice::Stop() return 0; } - // ① 停止解析线程 + // ① 停止包级解析 m_bStopProcess = true; if (m_pDriver) { m_pDriver->stop(); } - - if (m_processThread.joinable()) - { - m_processThread.join(); - } + resetPacketFrameBuilder(); // ② 停止交付线程(推入空帧唤醒 popWait,避免等超时) m_bStopDelivery = true; @@ -276,8 +540,13 @@ int CRsLidarDevice::SetPointCloudCallback(PointCloudCallback callback) int CRsLidarDevice::SetPacketCallback(PacketCallback callback) { - std::lock_guard lock(m_callbackMutex); - m_packetCallback = std::move(callback); + bool hasCallback = false; + { + std::lock_guard lock(m_callbackMutex); + m_packetCallback = std::move(callback); + hasCallback = static_cast(m_packetCallback); + } + m_hasPacketCallback.store(hasCallback, std::memory_order_release); return 0; } @@ -295,7 +564,7 @@ std::string CRsLidarDevice::GetVersion() // ============================================================ // 内部:5s 窗口诊断日志输出 + 字段重置(仅当距上次输出 ≥5s 时实际输出) -// 在 processCloudThread 内部调用,因此对窗口字段的 exchange 是单写者操作 +// 由 SDK 包处理线程触发,计数器只做 relaxed 采样,避免诊断影响实时路径。 // ============================================================ void CRsLidarDevice::emitDiagnosticLogIfDue(std::chrono::steady_clock::time_point& lastLogTime) { @@ -305,129 +574,60 @@ void CRsLidarDevice::emitDiagnosticLogIfDue(std::chrono::steady_clock::time_poin return; } - uint64_t cbCnt = m_cbCount.exchange(0); - uint64_t cbMax = m_cbMaxUs.exchange(0); - uint64_t cbAcc = m_cbAccumUs.exchange(0); - uint64_t cbAvg = (cbCnt > 0) ? (cbAcc / cbCnt) : 0; - uint64_t e0 = m_exceptionCounts[0].exchange(0); - uint64_t e1 = m_exceptionCounts[1].exchange(0); - uint64_t e2 = m_exceptionCounts[2].exchange(0); - uint64_t e3 = m_exceptionCounts[3].exchange(0); - uint64_t e4 = m_exceptionCounts[4].exchange(0); - size_t qPeakSnap = m_qPeak.load(); -#if 0 + const double sec = std::chrono::duration_cast>(now - lastLogTime).count(); + + const uint64_t pushCount = m_totalPushCount.load(std::memory_order_relaxed); + const uint64_t queueDropCount = m_droppedFrameCount.load(std::memory_order_relaxed); + const uint64_t frameWaitTimeoutCount = m_frameWaitTimeoutCount.load(std::memory_order_relaxed); + const uint64_t cbCount = m_cbCount.load(std::memory_order_relaxed); + const uint64_t cbAccumUs = m_cbAccumUs.load(std::memory_order_relaxed); + + const uint64_t pushDelta = pushCount - m_diagLastPushCount; + const uint64_t queueDropDelta = queueDropCount - m_diagLastQueueDropCount; + const uint64_t frameWaitTimeoutDelta = frameWaitTimeoutCount - m_diagLastFrameWaitTimeoutCount; + const uint64_t cbDelta = cbCount - m_diagLastCbCount; + const uint64_t cbAccumDelta = cbAccumUs - m_diagLastCbAccumUs; + + uint64_t excDelta[EXC_BUCKET_COUNT] = {}; + for (size_t i = 0; i < EXC_BUCKET_COUNT; ++i) + { + const uint64_t current = m_exceptionCounts[i].load(std::memory_order_relaxed); + excDelta[i] = current - m_diagLastExceptionCounts[i]; + m_diagLastExceptionCounts[i] = current; + } + fprintf(stderr, - "[RsLidarDevice] qd=%zu qpeak=%zu drop=%llu push=%llu | " - "cb_us max=%llu avg=%llu n=%llu | " - "exc msopto=%llu pktof=%llu cldof=%llu wlen=%llu other=%llu\n", - static_cast(m_stuffedQueue.size()), - qPeakSnap, - static_cast(m_droppedFrameCount.load()), - static_cast(m_totalPushCount.load()), - static_cast(cbMax), - static_cast(cbAvg), - static_cast(cbCnt), - static_cast(e0), - static_cast(e1), - static_cast(e2), - static_cast(e3), - static_cast(e4)); -#endif + "[RsLidarDevice][diag %.1fs] fps=%.1f frames=%llu(+%llu) " + "drop=%llu(+%llu) q=%zu/%zu waitTO=%llu(+%llu) " + "cb_us=%llu/%llu n=%llu exc48=%llu exc49=%llu exc42=%llu\n", + sec, + (sec > 0.0) ? (static_cast(pushDelta) / sec) : 0.0, + static_cast(pushCount), + static_cast(pushDelta), + static_cast(queueDropCount), + static_cast(queueDropDelta), + static_cast(m_deliveryQueue.size()), + static_cast(m_deliveryQueuePeak.load(std::memory_order_relaxed)), + static_cast(frameWaitTimeoutCount), + static_cast(frameWaitTimeoutDelta), + static_cast((cbDelta > 0) ? (cbAccumDelta / cbDelta) : 0), + static_cast(m_cbMaxUs.load(std::memory_order_relaxed)), + static_cast(cbDelta), + static_cast(excDelta[2]), + static_cast(excDelta[3]), + static_cast(excDelta[4])); + + m_diagLastPushCount = pushCount; + m_diagLastQueueDropCount = queueDropCount; + m_diagLastFrameWaitTimeoutCount = frameWaitTimeoutCount; + m_diagLastCbCount = cbCount; + m_diagLastCbAccumUs = cbAccumUs; lastLogTime = now; } -// ============================================================ -// 内部:点云解析线程(只做数据处理,不调回调) -// 处理完一帧 → 推入 m_deliveryQueue → 立刻取下一帧解析 -// 回调(PushFrame memcpy)在 deliveryThread 中并行执行 -// ============================================================ -void CRsLidarDevice::processCloudThread() -{ - auto lastLogTime = std::chrono::steady_clock::now(); - while (!m_bStopProcess) - { - SdkCloudPtr sdkCloud = m_stuffedQueue.popWait(500000); - if (!sdkCloud || sdkCloud->points.empty()) - { - emitDiagnosticLogIfDue(lastLogTime); - continue; - } - - emitDiagnosticLogIfDue(lastLogTime); - - // 获取空闲交付帧(超时 100ms 允许检查 m_bStopProcess) - auto frame = m_frameFreeQueue.popWait(100000); - if (!frame) - { - if (m_bStopProcess) break; - continue; - } - - // ① NaN→0 + ② 坐标系修正(X=-SDK_Y,Y=-SDK_Z,Z=SDK_X) + ③ 米→毫米(×1000) + ④ 拆线 - const auto& src = sdkCloud->points; - const uint32_t N = static_cast(src.size()); - const uint32_t H = sdkCloud->height > 0 ? sdkCloud->height : 1; - const uint32_t W = (H > 1 && sdkCloud->width > 0) ? sdkCloud->width : N; - - if (frame->linePool.size() < H) - frame->linePool.resize(H); - - auto& cloudData = frame->cloudData; - cloudData.clear(); - cloudData.reserve(H); - bool allValid = sdkCloud->is_dense; - - for (uint32_t line = 0; line < H; ++line) - { - uint32_t start = line * W; - uint32_t end = (line + 1 == H) ? N : (start + W); - if (end <= start) continue; - - uint32_t cnt = end - start; - auto& linePts = frame->linePool[line]; - linePts.resize(cnt); - - for (uint32_t j = 0; j < cnt; ++j) - { - uint32_t i = start + j; - float sx = std::isnan(src[i].x) ? 0.0f : src[i].x; - float sy = std::isnan(src[i].y) ? 0.0f : src[i].y; - float sz = std::isnan(src[i].z) ? 0.0f : src[i].z; - linePts[j].fData[0] = -sy * 1000.0f; - linePts[j].fData[1] = -sz * 1000.0f; - linePts[j].fData[2] = sx * 1000.0f; - linePts[j].fData_c[0] = static_cast(src[i].intensity); - if (std::isnan(src[i].x) || std::isnan(src[i].y) || std::isnan(src[i].z)) - allValid = false; - } - - SVzLaserLineData ld{}; - ld.p3DPoint = linePts.data(); - ld.nPointCount = static_cast(cnt); - ld.llFrameIdx = sdkCloud->seq; - ld.llTimeStamp = static_cast(sdkCloud->timestamp * 1e6); - ld.nEncodeNo = static_cast(line); - ld.bEndOnceScan = (line + 1 == H) ? VzTrue : VzFalse; - - cloudData.emplace_back(keResultDataType_PointXYZI, ld); - } - - frame->info.height = H; - frame->info.width = W; - frame->info.isDense = allValid; - - // 送入交付队列(可能短暂阻塞等 deliveryThread 消费,提供反压) - m_deliveryQueue.push(frame); - - // 归还 SDK 缓冲到 freeQueue(复用其预留 capacity) - m_freeQueue.push(sdkCloud); - } -} - -// ============================================================ // 内部:交付线程(只调回调,不做数据处理) // 从 m_deliveryQueue 取帧 → 调 PointCloudCallback(PushFrame memcpy 在此发生) -// 与 processCloudThread 并行:交付帧 N 的同时,解析帧 N+1 +// 与 SDK packet callback 并行:交付帧 N 的同时,解析帧 N+1 // ============================================================ void CRsLidarDevice::deliveryThread() { @@ -443,6 +643,12 @@ void CRsLidarDevice::deliveryThread() cb = m_pointCloudCallback; } + if (frame->cloudData.empty()) + { + m_frameFreeQueue.push(frame); + continue; + } + if (cb) { auto t0 = std::chrono::steady_clock::now(); @@ -461,7 +667,8 @@ void CRsLidarDevice::deliveryThread() {} } - // 归还交付帧到空闲池(清空 cloudData 元数据,保留 linePool capacity) + // 归还交付帧到空闲池(清空 cloudData 元数据,保留 pointPool capacity) + m_deliveredFrameCount.fetch_add(1, std::memory_order_relaxed); frame->cloudData.clear(); m_frameFreeQueue.push(frame); } @@ -479,32 +686,527 @@ CRsLidarDevice::SdkCloudPtr CRsLidarDevice::getFreeCloud() void CRsLidarDevice::putStuffedCloud(SdkCloudPtr msg) { - m_totalPushCount++; - size_t depth = m_stuffedQueue.push(msg); - if (depth == 0) + if (msg) { - m_droppedFrameCount++; - // 丢帧时把 SdkCloudMsg 回收到 freeQueue,复用其 points vector 容量 - // (单帧最多 reserve MAX_POINT_CLOUD_SIZE × 13B ≈ 22MB,避免反复分配) + msg->points.clear(); m_freeQueue.push(msg); } - else - { - // CAS 采样队列峰值(Open 以来) - size_t prevPeak = m_qPeak.load(std::memory_order_relaxed); - while (depth > prevPeak && - !m_qPeak.compare_exchange_weak(prevPeak, depth, std::memory_order_relaxed)) - { - // prevPeak 已被自动更新,循环重试 - } - } } // ============================================================ // 内部:Packet → RsPacketInfo // ============================================================ +void CRsLidarDevice::resetPacketFrameBuilder() +{ + if (m_activeFrame) + { + m_activeFrame->cloudData.clear(); + m_frameFreeQueue.push(m_activeFrame); + } + m_activeFrame.reset(); + m_activeFrameHasData = false; + m_activeFrameAllValid = true; + m_activeFrameStartedAtBoundary = false; + resetActiveFrameTracking(); + m_rsem4FrameSynced = false; + m_hasRsem4PrevSeq = false; + m_prevRsem4Seq = 0; + m_hasRsem4PrevColumn = false; + m_prevRsem4Column = 0; + m_rsem4HasPartialPacket = false; + m_rsem4PartialSeq = 0; + m_rsem4PartialLen = 0; + m_diagPacketTick = 0; +} + +void CRsLidarDevice::initializeRsem4DefaultAngles() +{ + m_rsem4YawOffset.fill(0); + m_rsem4SurfacePitchOffset.fill(0); + for (size_t i = 0; i < m_rsem4PitchAngle.size(); ++i) + m_rsem4PitchAngle[i] = static_cast(-1300 + static_cast(i) * 5); + m_rsem4AnglesReady = false; + updateRsem4PitchTrig(); +} + +void CRsLidarDevice::updateRsem4PitchTrig() +{ + for (size_t surface = 0; surface < kRsem4SurfaceCount; ++surface) + for (size_t i = 0; i < m_rsem4PitchAngle.size(); ++i) + { + const int pitch = static_cast(m_rsem4PitchAngle[i]) + + static_cast(m_rsem4SurfacePitchOffset[surface]); + m_rsem4PitchSin[surface][i] = SinDeg01(pitch); + m_rsem4PitchCos[surface][i] = CosDeg01(pitch); + } +} + +DeliveryFramePtr CRsLidarDevice::acquireFrameForBuild() +{ + while (!m_bStopProcess.load(std::memory_order_relaxed)) + { + const auto t0 = std::chrono::steady_clock::now(); + auto frame = m_frameFreeQueue.popWait(100000); + const auto t1 = std::chrono::steady_clock::now(); + const uint64_t waitUs = ElapsedUs(t0, t1); + m_frameWaitAccumUs.fetch_add(waitUs, std::memory_order_relaxed); + m_frameWaitCount.fetch_add(1, std::memory_order_relaxed); + UpdateMax(m_frameWaitMaxUs, waitUs); + if (frame) + { + resetDeliveryFrame(frame); + return frame; + } + m_frameWaitTimeoutCount.fetch_add(1, std::memory_order_relaxed); + } + return DeliveryFramePtr(); +} + +void CRsLidarDevice::resetDeliveryFrame(const DeliveryFramePtr& frame) +{ + if (!frame) + return; + + const size_t pointCount = + static_cast(kRsem4ReserveColumns) * static_cast(kRsem4DefaultRings); + if (frame->pointPool.size() != pointCount) + frame->pointPool.resize(pointCount); + + frame->cloudData.clear(); + frame->cloudData.reserve(kRsem4ReserveColumns); + std::memset(frame->pointPool.data(), 0, frame->pointPool.size() * sizeof(SVzNLPointXYZI)); + + for (uint32_t col = 0; col < kRsem4ReserveColumns; ++col) + { + SVzNLPointXYZI* linePts = frame->pointPool.data() + + static_cast(col) * static_cast(kRsem4DefaultRings); + + SVzLaserLineData ld{}; + ld.p3DPoint = linePts; + ld.nPointCount = static_cast(kRsem4DefaultRings); + ld.llFrameIdx = col; + ld.llTimeStamp = col; + ld.nEncodeNo = static_cast(col); + ld.bEndOnceScan = (col + 1 == kRsem4ReserveColumns) ? VzTrue : VzFalse; + frame->cloudData.emplace_back(keResultDataType_PointXYZI, ld); + } + + frame->info.height = kRsem4ReserveColumns; + frame->info.width = kRsem4DefaultRings; + frame->info.isDense = false; +} + +void CRsLidarDevice::submitActiveFrame() +{ + if (!m_activeFrame) + return; + + auto frame = m_activeFrame; + m_activeFrame.reset(); + + const bool completeFrame = + m_activeFrameHasData && + m_activeFrameStartedAtBoundary && + !m_activeFramePacketLoss && + m_activeCompleteColumns == kRsem4ReserveColumns; + + if (!completeFrame) + { + frame->cloudData.clear(); + m_frameFreeQueue.push(frame); + if (m_activeFrameStartedAtBoundary && m_activeFrameHasData) + m_droppedFrameCount.fetch_add(1, std::memory_order_relaxed); + m_activeFrameStartedAtBoundary = false; + resetActiveFrameTracking(); + return; + } + + frame->info.height = kRsem4ReserveColumns; + frame->info.width = kRsem4DefaultRings; + frame->info.isDense = m_activeFrameAllValid; + + const uint64_t parseUs = ElapsedUs(m_activeFrameStartTime, std::chrono::steady_clock::now()); + m_parseAccumUs.fetch_add(parseUs, std::memory_order_relaxed); + m_parseCount.fetch_add(1, std::memory_order_relaxed); + UpdateMax(m_parseMaxUs, parseUs); + + while (!m_bStopDelivery.load(std::memory_order_relaxed)) + { + const size_t depth = m_deliveryQueue.push(frame); + if (depth > 0) + { + UpdateMaxSize(m_deliveryQueuePeak, depth); + m_totalPushCount.fetch_add(1, std::memory_order_relaxed); + break; + } + std::this_thread::sleep_for(std::chrono::milliseconds(1)); + } + + if (m_bStopDelivery.load(std::memory_order_relaxed)) + m_frameFreeQueue.push(frame); + + m_activeFrameHasData = false; + m_activeFrameAllValid = true; + m_activeFrameStartedAtBoundary = false; + resetActiveFrameTracking(); +} + +void CRsLidarDevice::resetActiveFrameTracking() +{ + m_activeFramePacketLoss = false; + m_activeCompleteColumns = 0; + m_activeColumnCompleteMask = 0; + m_activeColumnMask.fill(0); +} + +bool CRsLidarDevice::beginActiveFrame(bool startedAtBoundary) +{ + if (m_activeFrame) + return true; + + m_activeFrame = acquireFrameForBuild(); + if (!m_activeFrame) + return false; + + m_activeFrameStartTime = std::chrono::steady_clock::now(); + m_activeFrameStartedAtBoundary = startedAtBoundary; + m_activeFrameHasData = false; + m_activeFrameAllValid = true; + resetActiveFrameTracking(); + return true; +} + +bool CRsLidarDevice::prepareRsem4FrameForColumn(uint32_t column) +{ + if (column >= kRsem4ReserveColumns) + return false; + + const bool columnWrap = m_hasRsem4PrevColumn && (column + 10 < m_prevRsem4Column); + const bool columnStart = (column == 0); + + if (columnWrap && m_activeFrameHasData) + submitActiveFrame(); + + m_hasRsem4PrevColumn = true; + m_prevRsem4Column = column; + + if (!m_activeFrame) + { + if (!columnStart) + return false; + return beginActiveFrame(true); + } + + return true; +} + +void CRsLidarDevice::markRsem4ColumnSegment(uint32_t column, uint8_t segmentMask, uint8_t completeMask) +{ + if (!m_activeFrame || column >= kRsem4ReserveColumns || segmentMask == 0) + return; + + if (m_activeColumnCompleteMask == 0) + { + m_activeColumnCompleteMask = completeMask; + } + else if (m_activeColumnCompleteMask != completeMask) + { + m_activeFramePacketLoss = true; + return; + } + + uint8_t& mask = m_activeColumnMask[column]; + const bool wasComplete = ((mask & completeMask) == completeMask); + mask = static_cast(mask | segmentMask); + const bool isComplete = ((mask & completeMask) == completeMask); + if (!wasComplete && isComplete) + ++m_activeCompleteColumns; +} + +void CRsLidarDevice::fillRsem4Point(uint32_t column, uint32_t ring, float distance, + uint8_t intensity, uint8_t surfaceIndex, + float cosYaw, float sinYaw) +{ + if (!m_activeFrame || column >= kRsem4ReserveColumns || ring >= kRsem4DefaultRings) + return; + + const size_t pointIndex = + static_cast(column) * static_cast(kRsem4DefaultRings) + + static_cast(kRsem4DefaultRings - 1 - ring); + if (pointIndex >= m_activeFrame->pointPool.size()) + return; + + auto& dst = m_activeFrame->pointPool[pointIndex]; + + if (surfaceIndex >= kRsem4SurfaceCount || distance < m_rsem4MinDistance || distance > m_rsem4MaxDistance) + { + SetZeroPoint(dst); + m_activeFrameAllValid = false; + return; + } + + const float sinPitch = m_rsem4PitchSin[surfaceIndex][ring]; + const float cosPitch = m_rsem4PitchCos[surfaceIndex][ring]; + + const float x = distance * cosPitch * cosYaw; + const float y = distance * cosPitch * sinYaw; + const float z = distance * sinPitch; + + dst.fData[0] = -y * 1000.0f; + dst.fData[1] = -z * 1000.0f; + dst.fData[2] = x * 1000.0f; + dst.fData_c[0] = static_cast(intensity); +} + +void CRsLidarDevice::processRsem4Difop(const uint8_t* data, size_t size) +{ + if (!data) + return; + + auto applyAngles = [this](const int8_t* yaw, const int16_t* pitch, const int16_t* surface) { + for (size_t i = 0; i < kRsem4VecselsPerColumn; ++i) + m_rsem4YawOffset[i] = yaw[i]; + for (size_t i = 0; i < kRsem4DefaultRings; ++i) + m_rsem4PitchAngle[i] = SwapI16(pitch[i]); + for (size_t i = 0; i < kRsem4SurfaceCount; ++i) + m_rsem4SurfacePitchOffset[i] = SwapI16(surface[i]); + m_rsem4AnglesReady = true; + updateRsem4PitchTrig(); + }; + + if (size == sizeof(Rsem4DifopPkt)) + { + const auto* pkt = reinterpret_cast(data); + applyAngles(pkt->yawOffset, pkt->pitchAngle, pkt->surfacePitchOffset); + } + else if (size == sizeof(Rsem4Difop2Pkt)) + { + const auto* pkt = reinterpret_cast(data); + applyAngles(pkt->yawOffset, pkt->pitchAngle, pkt->surfacePitchOffset); + } + else if (size == sizeof(Rsem4Difop0624Pkt)) + { + const auto* pkt = reinterpret_cast(data); + applyAngles(pkt->yawOffset, pkt->pitchAngle, pkt->surfacePitchOffset); + } +} + +bool CRsLidarDevice::processRsem4Packet(const Packet& pkt) +{ + const uint8_t* data = pkt.buf_.empty() ? nullptr : pkt.buf_.data(); + const size_t size = pkt.buf_.size(); + if (!data || size < 4) + return false; + + if (pkt.is_difop) + { + processRsem4Difop(data, size); + return false; + } + + return processRsem4Msop(data, size); +} + +bool CRsLidarDevice::processRsem4Msop(const uint8_t* data, size_t size) +{ + if (IsCompleteRsem4Packet(data, size)) + return processRsem4CompleteMsop(data, size); + + if (size < sizeof(Rsem4MsopHeader2)) + return false; + + const auto& header2 = *reinterpret_cast(data); + const uint16_t seq = ReadU16BE(header2.pktSeq); + if (!m_rsem4HasPartialPacket || seq != m_rsem4PartialSeq) + { + m_rsem4HasPartialPacket = false; + m_rsem4PartialLen = 0; + if (m_activeFrame) + m_activeFramePacketLoss = true; + return false; + } + + const size_t payloadOffset = sizeof(Rsem4MsopHeader2); + const size_t payloadLen = size - payloadOffset; + if (m_rsem4PartialLen + payloadLen > m_rsem4PartialPacket.size()) + { + m_rsem4HasPartialPacket = false; + m_rsem4PartialLen = 0; + if (m_activeFrame) + m_activeFramePacketLoss = true; + return false; + } + + std::memcpy(m_rsem4PartialPacket.data() + m_rsem4PartialLen, data + payloadOffset, payloadLen); + m_rsem4PartialLen += payloadLen; + m_rsem4HasPartialPacket = false; + return processRsem4CompressedMsop(m_rsem4PartialPacket.data(), m_rsem4PartialLen); +} + +bool CRsLidarDevice::processRsem4CompleteMsop(const uint8_t* data, size_t size) +{ + if (size < sizeof(Rsem4MsopHeader)) + return false; + + const auto& header = *reinterpret_cast(data); + const uint8_t packMode = header.packMode & 0x03; + if (packMode == 0x03) + { + const uint16_t compressedSeq = ReadU16BE(header.pktSeq); + + const uint8_t splitPackNum = header.packMode >> 4; + if (splitPackNum == 0) + return processRsem4CompressedMsop(data, size); + + if (size > m_rsem4PartialPacket.size()) + { + if (m_activeFrame) + m_activeFramePacketLoss = true; + return false; + } + std::memcpy(m_rsem4PartialPacket.data(), data, size); + m_rsem4PartialLen = size; + m_rsem4PartialSeq = compressedSeq; + m_rsem4HasPartialPacket = true; + return false; + } + + if (size < sizeof(Rsem4MsopPkt)) + return false; + + const auto& pkt = *reinterpret_cast(data); + const uint16_t pktSeqRaw = ReadU16BE(pkt.header.pktSeq); + const uint16_t pktSeq = (pktSeqRaw > 0) ? static_cast(pktSeqRaw - 1) : 0; + if (!m_rsem4FrameSynced) + { + m_rsem4FrameSynced = true; + m_hasRsem4PrevSeq = false; + m_hasRsem4PrevColumn = false; + } + + m_prevRsem4Seq = pktSeq; + m_hasRsem4PrevSeq = true; + + const uint16_t columnRaw = ReadU16BE(pkt.header.columnNum); + const uint32_t column = (columnRaw < kRsem4ReserveColumns) + ? static_cast(columnRaw) + : static_cast((pktSeq / 2) % kRsem4ReserveColumns); + if (!prepareRsem4FrameForColumn(column)) + return false; + m_activeFrameHasData = true; + + const uint8_t surfaceIndex = (pkt.header.surfaceId > 0) ? static_cast(pkt.header.surfaceId - 1) : 0; + const int16_t yawBase = SwapI16(pkt.header.yawAngle); + + float yawCos[kRsem4VecselsPerColumn]; + float yawSin[kRsem4VecselsPerColumn]; + for (size_t i = 0; i < kRsem4VecselsPerColumn; ++i) + { + const int yaw = static_cast(yawBase) + static_cast(m_rsem4YawOffset[i]); + yawCos[i] = CosDeg01(yaw); + yawSin[i] = SinDeg01(yaw); + } + + const bool dualReturn = (pkt.header.returnMode == 0x00); + const uint8_t segmentModulo = dualReturn ? 4 : 2; + const uint8_t segmentMask = static_cast(1U << (pktSeq % segmentModulo)); + const uint8_t completeMask = dualReturn ? 0x0F : 0x03; + for (uint32_t blk = 0; blk < kRsem4BlocksPerPacket; ++blk) + { + uint32_t ring = 0; + if (dualReturn) + { + if ((blk & 1U) != 0) + continue; + ring = (blk / 2) + (pktSeq % 4) * (kRsem4HalfRings / 2); + } + else + { + ring = blk + (pktSeq % 2) * kRsem4HalfRings; + } + if (ring >= kRsem4DefaultRings) + continue; + + const auto& channel = pkt.blocks[blk].channel[0]; + const float distance = static_cast(ReadU16BE(channel.distance)) * kRsem4DistanceResolution; + const size_t vecsel = ring / kRsem4PixelsPerVcsel; + fillRsem4Point(column, ring, distance, channel.intensity, surfaceIndex, yawCos[vecsel], yawSin[vecsel]); + } + markRsem4ColumnSegment(column, segmentMask, completeMask); + + return true; +} + +bool CRsLidarDevice::processRsem4CompressedMsop(const uint8_t* data, size_t size) +{ + if (size < sizeof(Rsem4MsopHeader) + kRsem4MsopTailLen) + return false; + + const auto& header = *reinterpret_cast(data); + const uint16_t pktSeq = ReadU16BE(header.pktSeq); + if (!m_rsem4FrameSynced) + { + m_rsem4FrameSynced = true; + m_hasRsem4PrevSeq = false; + m_hasRsem4PrevColumn = false; + } + + m_prevRsem4Seq = pktSeq; + m_hasRsem4PrevSeq = true; + + uint16_t decoded[kRsem4DefaultRings * 2] = {}; + const int dataLen = static_cast((size - sizeof(Rsem4MsopHeader) - kRsem4MsopTailLen) / 2); + CompressAlgo::RLenc_unpack_optimize( + reinterpret_cast(data + sizeof(Rsem4MsopHeader)), + decoded, + dataLen, + 16); + + const uint16_t* radius = decoded; + const uint16_t* identity = decoded + kRsem4DefaultRings; + + const uint16_t columnRaw = ReadU16BE(header.columnNum); + const uint32_t column = (columnRaw < kRsem4ReserveColumns) + ? static_cast(columnRaw) + : static_cast(pktSeq % kRsem4ReserveColumns); + if (!prepareRsem4FrameForColumn(column)) + return false; + m_activeFrameHasData = true; + + const uint8_t surfaceIndex = (header.surfaceId > 0) ? static_cast(header.surfaceId - 1) : 0; + const int16_t yawBase = SwapI16(header.yawAngle); + + float yawCos[kRsem4VecselsPerColumn]; + float yawSin[kRsem4VecselsPerColumn]; + for (size_t i = 0; i < kRsem4VecselsPerColumn; ++i) + { + const int yaw = static_cast(yawBase) + static_cast(m_rsem4YawOffset[i]); + yawCos[i] = CosDeg01(yaw); + yawSin[i] = SinDeg01(yaw); + } + + for (uint32_t ring = 0; ring < kRsem4DefaultRings; ++ring) + { + const float distance = static_cast(radius[ring]) * kRsem4DistanceResolution; + const uint8_t intensity = static_cast(identity[ring] & 0xFF); + const size_t vecsel = ring / kRsem4PixelsPerVcsel; + fillRsem4Point(column, ring, distance, intensity, surfaceIndex, yawCos[vecsel], yawSin[vecsel]); + } + markRsem4ColumnSegment(column, 0x01, 0x01); + + return true; +} + void CRsLidarDevice::onPacket(const Packet& pkt) { + if ((++m_diagPacketTick & 0x3FFU) == 0) + emitDiagnosticLogIfDue(m_diagLastLogTime); + if (m_param.lidar_type == LidarType::RSEM4) + processRsem4Packet(pkt); + + if (!m_hasPacketCallback.load(std::memory_order_acquire)) + return; + PacketCallback cb; { std::lock_guard lock(m_callbackMutex); @@ -534,7 +1236,7 @@ uint64_t CRsLidarDevice::GetDroppedFrameCount() const size_t CRsLidarDevice::GetStuffedQueueDepth() const { - return m_stuffedQueue.size(); + return m_deliveryQueue.size(); } uint64_t CRsLidarDevice::GetExceptionCount(int errCode) const @@ -555,7 +1257,7 @@ RsCallbackStats CRsLidarDevice::GetCallbackLatencyStats() const size_t CRsLidarDevice::GetStuffedQueuePeak() const { - return m_qPeak.load(std::memory_order_relaxed); + return m_deliveryQueuePeak.load(std::memory_order_relaxed); } // ============================================================ diff --git a/Device/RsLidarDevice/_Inc/RsLidarDevice.h b/Device/RsLidarDevice/_Inc/RsLidarDevice.h index eca4022f..4672b0ee 100644 --- a/Device/RsLidarDevice/_Inc/RsLidarDevice.h +++ b/Device/RsLidarDevice/_Inc/RsLidarDevice.h @@ -33,22 +33,26 @@ inline int recvfrom(SOCKET s, unsigned char* buf, int len, int flags, sockaddr* #include #include #include +#include using namespace robosense::lidar; -// 交付帧:封装一次完整扫描的数据 + 行缓冲池(p3DPoint 指向此处) -// processCloudThread 填充 → deliveryThread 消费(调用回调)→ 归还 m_frameFreeQueue +struct RsSdkDiscardPoint +{ +}; + +// 交付帧:封装一次完整扫描的数据 + 连续点池(p3DPoint 指向此处) +// SDK packet callback 填充 → deliveryThread 消费(调用回调)→ 归还 m_frameFreeQueue struct DeliveryFrame { - std::vector> linePool; + std::vector pointPool; RsCloudData cloudData; RsFrameInfo info; void reserveLines(size_t lines, size_t pointsPerLine) { - linePool.resize(lines); - for (auto& v : linePool) - v.reserve(pointsPerLine); + pointPool.resize(lines * pointsPerLine); + cloudData.reserve(lines); } }; using DeliveryFramePtr = std::shared_ptr; @@ -59,6 +63,10 @@ public: CRsLidarDevice(); ~CRsLidarDevice(); + static void* operator new(std::size_t size); + static void operator delete(void* ptr) noexcept; + static void operator delete(void* ptr, std::size_t) noexcept; + int InitDevice() override; int OpenDevice(const RsLidarConfig& config) override; int CloseDevice() override; @@ -82,12 +90,9 @@ public: private: // SDK 内部类型别名 - using SdkCloudMsg = PointCloudT; + using SdkCloudMsg = PointCloudT; using SdkCloudPtr = std::shared_ptr; - /// 解析线程:pop SDK帧 → NaN→0+变换+拆线 → 推入交付队列 → 归还SDK缓冲 - void processCloudThread(); - /// 交付线程:从交付队列取帧 → 调用用户回调(PushFrame memcpy在此发生)→ 归还交付帧 void deliveryThread(); @@ -100,6 +105,25 @@ private: /// 将 SDK Packet 转为 RsPacketInfo 后转发给用户 void onPacket(const Packet& pkt); + void resetPacketFrameBuilder(); + void initializeRsem4DefaultAngles(); + void updateRsem4PitchTrig(); + bool processRsem4Packet(const Packet& pkt); + void processRsem4Difop(const uint8_t* data, size_t size); + bool processRsem4Msop(const uint8_t* data, size_t size); + bool processRsem4CompleteMsop(const uint8_t* data, size_t size); + bool processRsem4CompressedMsop(const uint8_t* data, size_t size); + DeliveryFramePtr acquireFrameForBuild(); + void resetDeliveryFrame(const DeliveryFramePtr& frame); + void submitActiveFrame(); + void resetActiveFrameTracking(); + bool beginActiveFrame(bool startedAtBoundary); + bool prepareRsem4FrameForColumn(uint32_t column); + void markRsem4ColumnSegment(uint32_t column, uint8_t segmentMask, uint8_t completeMask); + void fillRsem4Point(uint32_t column, uint32_t ring, float distance, + uint8_t intensity, uint8_t surfaceIndex, + float cosYaw, float sinYaw); + /// 将 SDK Error 转为 RsExceptionInfo 后转发给用户 void onException(const Error& code); @@ -114,24 +138,63 @@ private: PointCloudCallback m_pointCloudCallback; PacketCallback m_packetCallback; ExceptionCallback m_exceptionCallback; + std::atomic m_hasPacketCallback{false}; std::mutex m_callbackMutex; std::unique_ptr> m_pDriver; - std::thread m_processThread; std::thread m_deliveryThread; SyncQueue m_freeQueue; - SyncQueue m_stuffedQueue; - // 交付帧流水线(深度2=解耦一帧):processCloud 推入 → deliveryThread 消费 - SyncQueue m_deliveryQueue; - SyncQueue m_frameFreeQueue; // 空闲帧池(3帧预分配 + 1余量) + // 交付帧流水线:SDK packet callback 填充 → deliveryThread 消费 + SyncQueue m_deliveryQueue; + SyncQueue m_frameFreeQueue; RSDriverParam m_param; + DeliveryFramePtr m_activeFrame; + bool m_activeFrameHasData = false; + bool m_activeFrameAllValid = true; + bool m_activeFrameStartedAtBoundary = false; + bool m_activeFramePacketLoss = false; + uint32_t m_activeCompleteColumns = 0; + uint8_t m_activeColumnCompleteMask = 0; + std::array m_activeColumnMask{}; + bool m_hasRsem4PrevColumn = false; + uint32_t m_prevRsem4Column = 0; + bool m_rsem4FrameSynced = false; + bool m_hasRsem4PrevSeq = false; + uint16_t m_prevRsem4Seq = 0; + std::chrono::steady_clock::time_point m_activeFrameStartTime{}; + std::chrono::steady_clock::time_point m_diagLastLogTime{}; + + std::array m_rsem4YawOffset{}; + std::array m_rsem4PitchAngle{}; + std::array, 4> m_rsem4PitchSin{}; + std::array, 4> m_rsem4PitchCos{}; + std::array m_rsem4SurfacePitchOffset{}; + bool m_rsem4AnglesReady = false; + bool m_rsem4HasPartialPacket = false; + uint16_t m_rsem4PartialSeq = 0; + size_t m_rsem4PartialLen = 0; + std::array m_rsem4PartialPacket{}; + uint32_t m_diagPacketTick = 0; + float m_rsem4MinDistance = 0.5f; + float m_rsem4MaxDistance = 350.0f; + // 丢帧监控 std::atomic m_droppedFrameCount{0}; std::atomic m_totalPushCount{0}; + std::atomic m_parseAccumUs{0}; + std::atomic m_parseMaxUs{0}; + std::atomic m_parseCount{0}; + std::atomic m_frameWaitAccumUs{0}; + std::atomic m_frameWaitMaxUs{0}; + std::atomic m_frameWaitCount{0}; + std::atomic m_frameWaitTimeoutCount{0}; + std::atomic m_deliveryDropCount{0}; + std::atomic m_deliveredFrameCount{0}; + std::atomic m_deliveryQueuePeak{0}; // 诊断统计(每 5s 滚动窗口) // bucket: 0=MSOPTIMEOUT(0x40) 1=PKTBUFOVERFLOW(0x48) 2=CLOUDOVERFLOW(0x49) 3=WRONGMSOPLEN(0x42) 4=other @@ -140,7 +203,19 @@ private: std::atomic m_cbMaxUs{0}; std::atomic m_cbAccumUs{0}; std::atomic m_cbCount{0}; - std::atomic m_qPeak{0}; + + uint64_t m_diagLastPushCount = 0; + uint64_t m_diagLastQueueDropCount = 0; + uint64_t m_diagLastParseCount = 0; + uint64_t m_diagLastParseAccumUs = 0; + uint64_t m_diagLastFrameWaitCount = 0; + uint64_t m_diagLastFrameWaitAccumUs = 0; + uint64_t m_diagLastFrameWaitTimeoutCount = 0; + uint64_t m_diagLastDeliveryDropCount = 0; + uint64_t m_diagLastDeliveredFrameCount = 0; + uint64_t m_diagLastCbCount = 0; + uint64_t m_diagLastCbAccumUs = 0; + std::array m_diagLastExceptionCounts{}; /// 将 SDK ErrCode 映射到统计桶下标,未知返回 4 (other) static size_t errCodeToBucket(int errCode); diff --git a/Module/FFMediaStream/Src/CVrFFMediaPuller.cpp b/Module/FFMediaStream/Src/CVrFFMediaPuller.cpp index a87660a8..5d00f89d 100644 --- a/Module/FFMediaStream/Src/CVrFFMediaPuller.cpp +++ b/Module/FFMediaStream/Src/CVrFFMediaPuller.cpp @@ -2,6 +2,7 @@ #include "VrError.h" #include "VrLog.h" +#include #include #if !defined(FFMEDIA_PLATFORM_DUMMY) @@ -73,6 +74,23 @@ int CVrFFMediaPuller::UnInit() // ---- 辅助函数 ---- +namespace +{ +bool startsWithNoCase(const std::string& s, const char* prefix) +{ + const size_t n = std::strlen(prefix); + if (s.size() < n) return false; + for (size_t i = 0; i < n; ++i) + { + const unsigned char a = static_cast(s[i]); + const unsigned char b = static_cast(prefix[i]); + if (std::tolower(a) != std::tolower(b)) + return false; + } + return true; +} +} + MppCodingType CVrFFMediaPuller::AvCodecIdToMppCoding(AVCodecID id) { switch (id) @@ -108,11 +126,67 @@ unsigned int CVrFFMediaPuller::PixelBpp(EFFPullPixelFormat fmt) // ---- MPP 解码器初始化 ---- +int CVrFFMediaPuller::initBitstreamFilter() +{ + if (!m_fmtCtx || m_videoStreamIndex < 0) return SUCCESS; + + AVStream* stream = m_fmtCtx->streams[m_videoStreamIndex]; + if (!stream || !stream->codecpar) return SUCCESS; + + const char* filterName = nullptr; + switch (stream->codecpar->codec_id) + { + case AV_CODEC_ID_H264: + filterName = "h264_mp4toannexb"; + break; + case AV_CODEC_ID_HEVC: + filterName = "hevc_mp4toannexb"; + break; + default: + return SUCCESS; + } + + const AVBitStreamFilter* filter = av_bsf_get_by_name(filterName); + if (!filter) + { + LOG_ERROR("FFMediaPuller: bitstream filter not found %s\n", filterName); + return ERR_CODE(DEV_OPEN_ERR); + } + + int ret = av_bsf_alloc(filter, &m_bsfCtx); + if (ret < 0 || !m_bsfCtx) + { + LOG_ERROR("FFMediaPuller: av_bsf_alloc fail %d (%s)\n", ret, filterName); + return ERR_CODE(DEV_OPEN_ERR); + } + + ret = avcodec_parameters_copy(m_bsfCtx->par_in, stream->codecpar); + if (ret < 0) + { + LOG_ERROR("FFMediaPuller: avcodec_parameters_copy fail %d\n", ret); + av_bsf_free(&m_bsfCtx); + return ERR_CODE(DEV_OPEN_ERR); + } + m_bsfCtx->time_base_in = stream->time_base; + + ret = av_bsf_init(m_bsfCtx); + if (ret < 0) + { + LOG_ERROR("FFMediaPuller: av_bsf_init fail %d (%s)\n", ret, filterName); + av_bsf_free(&m_bsfCtx); + return ERR_CODE(DEV_OPEN_ERR); + } + + LOG_INFO("FFMediaPuller: bitstream filter enabled %s\n", filterName); + return SUCCESS; +} + int CVrFFMediaPuller::initMppDecoder() { if (!m_fmtCtx) return ERR_CODE(DEV_OPEN_ERR); - AVCodecParameters* codecpar = m_fmtCtx->streams[m_videoStreamIndex]->codecpar; + AVCodecParameters* codecpar = m_bsfCtx ? m_bsfCtx->par_out + : m_fmtCtx->streams[m_videoStreamIndex]->codecpar; MppCodingType coding = AvCodecIdToMppCoding(codecpar->codec_id); // 1) 创建 MPP 上下文 @@ -253,6 +327,23 @@ void CVrFFMediaPuller::decodeLoop() continue; } + if (m_bsfCtx) + { + ret = av_bsf_send_packet(m_bsfCtx, avpkt); + av_packet_unref(avpkt); + if (ret < 0) + continue; + + ret = av_bsf_receive_packet(m_bsfCtx, avpkt); + if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) + continue; + if (ret < 0) + { + av_packet_unref(avpkt); + continue; + } + } + // 1) 构造 MppPacket MppPacket mppPkt = nullptr; mpp_packet_init(&mppPkt, avpkt->data, static_cast(avpkt->size)); @@ -378,22 +469,25 @@ int CVrFFMediaPuller::Init(const VrFFPullConfig& config) avformat_network_init(); av_log_set_level(AV_LOG_ERROR); - // 2) 打开 RTSP 流 + // 2) 打开输入流 AVDictionary* opts = nullptr; - if (config.useTcp) + const bool isRtsp = startsWithNoCase(config.rtspUrl, "rtsp://"); + const bool isRtmp = startsWithNoCase(config.rtspUrl, "rtmp://"); + if (isRtsp) { - av_dict_set(&opts, "rtsp_transport", "tcp", 0); + av_dict_set(&opts, "rtsp_transport", config.useTcp ? "tcp" : "udp", 0); + av_dict_set(&opts, "timeout", "3000000", 0); + av_dict_set(&opts, "stimeout", "3000000", 0); } - else + if (isRtmp) { - av_dict_set(&opts, "rtsp_transport", "udp", 0); + av_dict_set(&opts, "rtmp_live", "live", 0); } - // socket 读超时(微秒)。FFmpeg 6.x RTSP demuxer 用 "timeout"; - // 同时设置旧名 "stimeout" 以兼容(未知项被忽略,无害)。 - av_dict_set(&opts, "timeout", "5000000", 0); - av_dict_set(&opts, "stimeout", "5000000", 0); - // 降低拉流端到端延迟 - av_dict_set(&opts, "max_delay", "500000", 0); + av_dict_set(&opts, "rw_timeout", "3000000", 0); + av_dict_set(&opts, "probesize", "32768", 0); + av_dict_set(&opts, "analyzeduration", "100000", 0); + av_dict_set(&opts, "fflags", "nobuffer", 0); + av_dict_set(&opts, "max_delay", "100000", 0); int ret = avformat_open_input(&m_fmtCtx, config.rtspUrl.c_str(), nullptr, &opts); av_dict_free(&opts); @@ -441,11 +535,20 @@ int CVrFFMediaPuller::Init(const VrFFPullConfig& config) m_decHorStride = codecpar->width; // 先猜测,info_change 后会更新 m_decVerStride = codecpar->height; + ret = initBitstreamFilter(); + if (ret != SUCCESS) + { + LOG_ERROR("FFMediaPuller: initBitstreamFilter fail\n"); + avformat_close_input(&m_fmtCtx); + return ret; + } + // 6) 初始化 MPP 解码器 ret = initMppDecoder(); if (ret != SUCCESS) { LOG_ERROR("FFMediaPuller: initMppDecoder fail\n"); + av_bsf_free(&m_bsfCtx); avformat_close_input(&m_fmtCtx); return ret; } @@ -455,6 +558,7 @@ int CVrFFMediaPuller::Init(const VrFFPullConfig& config) if (ret != SUCCESS) { LOG_ERROR("FFMediaPuller: initSwscale fail\n"); + av_bsf_free(&m_bsfCtx); avformat_close_input(&m_fmtCtx); return ret; } @@ -515,6 +619,11 @@ int CVrFFMediaPuller::UnInit() m_fmtCtx = nullptr; } + if (m_bsfCtx) + { + av_bsf_free(&m_bsfCtx); + } + // 销毁 MPP 解码器上下文 if (m_mppCtx) { diff --git a/Module/FFMediaStream/Src/CVrFFMediaPusher.cpp b/Module/FFMediaStream/Src/CVrFFMediaPusher.cpp index 97efb704..3935e738 100644 --- a/Module/FFMediaStream/Src/CVrFFMediaPusher.cpp +++ b/Module/FFMediaStream/Src/CVrFFMediaPusher.cpp @@ -5,6 +5,7 @@ #include #include #include +#include #if !defined(FFMEDIA_PLATFORM_DUMMY) #include @@ -130,8 +131,13 @@ int CVrFFMediaPusher::initMppEncoder() MppCodingType coding = ToMppCoding(m_config.encodeType); - ret = m_mppApi->control(m_mppCtx, MPP_SET_INPUT_TIMEOUT, nullptr); - ret = m_mppApi->control(m_mppCtx, MPP_SET_OUTPUT_TIMEOUT, nullptr); + RK_S64 timeout = 1000; + ret = m_mppApi->control(m_mppCtx, MPP_SET_INPUT_TIMEOUT, &timeout); + if (ret != MPP_SUCCESS) + LOG_WARN("FFMediaPusher: MPP_SET_INPUT_TIMEOUT fail %d\n", ret); + ret = m_mppApi->control(m_mppCtx, MPP_SET_OUTPUT_TIMEOUT, &timeout); + if (ret != MPP_SUCCESS) + LOG_WARN("FFMediaPusher: MPP_SET_OUTPUT_TIMEOUT fail %d\n", ret); ret = mpp_init(m_mppCtx, MPP_CTX_ENC, coding); if (ret != MPP_SUCCESS) @@ -229,19 +235,19 @@ int CVrFFMediaPusher::initMppEncoder() return SUCCESS; } -// ---- RTSP 输出初始化 ---- +// ---- RTMP 输出初始化 ---- int CVrFFMediaPusher::initRtspOutput() { - // 1) 构建 RTSP URL: 主动推流到 mediamtx (rtsp://{ip}:{port}{path}) - // 不再使用 0.0.0.0:listen 模式,而是推到 mediamtx 的接收端口 + // 1) 构建 RTMP URL: 主动推流到 mediamtx (rtmp://{ip}:{port}{path}) + // RTMP 比 RTSP 更稳定,避免 interleaved frame 等兼容性问题 char url[256]; - snprintf(url, sizeof(url), "rtsp://%s:%u%s", + snprintf(url, sizeof(url), "rtmp://%s:%u%s", m_config.rtspHost.empty() ? "127.0.0.1" : m_config.rtspHost.c_str(), m_config.rtspPort, m_config.rtspPath.c_str()); - // 2) 创建 RTSP 输出上下文 - int ret = avformat_alloc_output_context2(&m_fmtCtx, nullptr, "rtsp", url); + // 2) 创建 RTMP 输出上下文(格式改为 flv,RTMP 基于 FLV 容器) + int ret = avformat_alloc_output_context2(&m_fmtCtx, nullptr, "flv", url); if (ret < 0 || !m_fmtCtx) { LOG_ERROR("FFMediaPusher: avformat_alloc_output_context2 fail %d\n", ret); @@ -271,6 +277,8 @@ int CVrFFMediaPusher::initRtspOutput() codecpar->width = m_encWidth; codecpar->height = m_encHeight; + codecpar->format = AV_PIX_FMT_YUV420P; // NV12 对应的 FFmpeg 格式 + codecpar->bit_rate = static_cast(m_config.bitrateKbps) * 1000; // RTMP muxer 需要知道码率 // 设置流时基为微秒(PushFrame 以微秒传入 PTS,写帧时再 rescale 到 muxer 实际时基) stream->time_base = AVRational{1, 1000000}; @@ -397,15 +405,25 @@ int CVrFFMediaPusher::Start() if (!m_bInited.load()) return ERR_CODE(DEV_NO_OPEN); if (m_bStarted.load()) return SUCCESS; - // 主动推流到 mediamtx:直接调用 avformat_write_header(不再开线程等 listen) - AVDictionary* opts = nullptr; - av_dict_set(&opts, "rtsp_transport", "tcp", 0); - int ret = avformat_write_header(m_fmtCtx, &opts); - av_dict_free(&opts); + // RTMP 需要先打开 IO(RTSP 不需要,但 RTMP/FLV 必须) + if (!(m_fmtCtx->oformat->flags & AVFMT_NOFILE)) + { + int ret = avio_open(&m_fmtCtx->pb, m_fmtCtx->url, AVIO_FLAG_WRITE); + if (ret < 0) + { + LOG_ERROR("FFMediaPusher: avio_open fail %d url=%s\n", ret, m_fmtCtx->url); + return ERR_CODE(DEV_OPEN_ERR); + } + } + + // 写 RTMP header + int ret = avformat_write_header(m_fmtCtx, nullptr); if (ret < 0) { LOG_ERROR("FFMediaPusher: avformat_write_header fail %d (mediamtx 未运行或端口不对?)\n", ret); + if (m_fmtCtx->pb) + avio_closep(&m_fmtCtx->pb); return ERR_CODE(DEV_OPEN_ERR); } @@ -423,12 +441,7 @@ int CVrFFMediaPusher::PushFrame(const void* data, size_t size, int64_t ptsUs) if (!data || size == 0) return ERR_CODE(DEV_ARG_INVAILD); - // 生成 PTS - if (ptsUs == 0) - { - auto now = std::chrono::steady_clock::now().time_since_epoch(); - ptsUs = std::chrono::duration_cast(now).count(); - } + // 使用调用方传入的 PTS(由 DroneScrewServerPresenter 按帧计数递增生成) m_lastPtsUs = ptsUs; // 1) 分配 MPP 输入缓冲区(hor_stride × ver_stride 对齐布局),直接把数据写进去 @@ -446,6 +459,7 @@ int CVrFFMediaPusher::PushFrame(const void* data, size_t size, int64_t ptsUs) mpp_buffer_put(mppBuf); return ERR_CODE(DEV_CTRL_ERR); } + mpp_buffer_sync_begin(mppBuf); uint8_t* dstY = dst; // Y 平面 uint8_t* dstUV = dst + m_encHorStride * m_encVerStride; // UV 平面(按 ver_stride 偏移) @@ -493,6 +507,8 @@ int CVrFFMediaPusher::PushFrame(const void* data, size_t size, int64_t ptsUs) srcUV + static_cast(y) * srcLn, copyW); } + mpp_buffer_sync_end(mppBuf); + // 2) 构建 MppFrame MppFrame frame = nullptr; mpp_frame_init(&frame); @@ -504,39 +520,126 @@ int CVrFFMediaPusher::PushFrame(const void* data, size_t size, int64_t ptsUs) mpp_frame_set_buffer(frame, mppBuf); mpp_frame_set_pts(frame, ptsUs); - // 3) 同步编码 + // 3) 编码。H.264/H.265 在当前 MPP 版本上需要显式提供输出 packet buffer, + // 否则 encode() 可能持续返回 success + null packet,RTMP 只有 header 没有媒体数据。 MppPacket packet = nullptr; - mret = m_mppApi->encode(m_mppCtx, frame, &packet); - mpp_frame_deinit(&frame); - mpp_buffer_put(mppBuf); + MppPacket outPacket = nullptr; + MppBuffer pktBuf = nullptr; + const size_t pktBufSize = static_cast(frameSize); - if (mret != MPP_SUCCESS || !packet) + mret = mpp_buffer_get(m_mppBufGroup, &pktBuf, pktBufSize); + if (mret != MPP_SUCCESS || !pktBuf) { - LOG_DEBUG("FFMediaPusher: encode fail %d\n", mret); + mpp_frame_deinit(&frame); + mpp_buffer_put(mppBuf); + LOG_ERROR("FFMediaPusher: packet buffer get fail %d\n", mret); return ERR_CODE(DEV_CTRL_ERR); } - void* pktData = mpp_packet_get_data(packet); - size_t pktSize = mpp_packet_get_length(packet); + mret = mpp_packet_init_with_buffer(&packet, pktBuf); + if (mret != MPP_SUCCESS || !packet) + { + mpp_frame_deinit(&frame); + mpp_buffer_put(mppBuf); + mpp_buffer_put(pktBuf); + LOG_ERROR("FFMediaPusher: packet init fail %d\n", mret); + return ERR_CODE(DEV_CTRL_ERR); + } + mpp_packet_set_length(packet, 0); + mpp_packet_set_pos(packet, mpp_packet_get_data(packet)); - if (!pktData || pktSize == 0) + MppMeta meta = mpp_frame_get_meta(frame); + if (meta) + mpp_meta_set_packet(meta, KEY_OUTPUT_PACKET, packet); + + mret = m_mppApi->encode_put_frame(m_mppCtx, frame); + mpp_frame_deinit(&frame); + mpp_buffer_put(mppBuf); + + if (mret != MPP_SUCCESS) { mpp_packet_deinit(&packet); - return SUCCESS; // 编码器可能吞帧(如缓冲未就绪),正常情况 + mpp_buffer_put(pktBuf); + LOG_DEBUG("FFMediaPusher: encode_put_frame fail %d\n", mret); + return ERR_CODE(DEV_CTRL_ERR); + } + + mret = m_mppApi->encode_get_packet(m_mppCtx, &outPacket); + if (mret != MPP_SUCCESS) + { + mpp_packet_deinit(&packet); + mpp_buffer_put(pktBuf); + LOG_DEBUG("FFMediaPusher: encode_get_packet fail %d\n", mret); + return ERR_CODE(DEV_CTRL_ERR); + } + + MppPacket encodedPacket = outPacket ? outPacket : packet; + + // 编码器预热期可能无输出,跳过本帧(返回 SUCCESS,不算失败) + if (!encodedPacket || mpp_packet_get_length(encodedPacket) == 0) + { + mpp_packet_deinit(&packet); + mpp_buffer_put(pktBuf); + LOG_DEBUG("FFMediaPusher: encode produced no packet (warmup)\n"); + return SUCCESS; // 跳过,不算失败 + } + + MppBuffer encodedBuf = mpp_packet_get_buffer(encodedPacket); + if (encodedBuf) + mpp_buffer_sync_ro_begin(encodedBuf); + auto releaseEncodedPacket = [&]() { + if (encodedBuf) + { + mpp_buffer_sync_ro_end(encodedBuf); + encodedBuf = nullptr; + } + if (outPacket && outPacket != packet) + mpp_packet_deinit(&outPacket); + if (packet) + mpp_packet_deinit(&packet); + if (pktBuf) + { + mpp_buffer_put(pktBuf); + pktBuf = nullptr; + } + }; + + void* pktData = mpp_packet_get_pos(encodedPacket); + if (!pktData) + pktData = mpp_packet_get_data(encodedPacket); + size_t pktSize = mpp_packet_get_length(encodedPacket); + + // 双重检查:确保有效数据(理论上前面已检查过,这里是保险) + if (!pktData || pktSize == 0) + { + releaseEncodedPacket(); + LOG_DEBUG("FFMediaPusher: encode produced empty packet data\n"); + return SUCCESS; // 跳过,不算失败 } // 4) 写入 RTSP(独立 AVPacket,pts rescale 到 muxer 时基) AVPacket* avpkt = av_packet_alloc(); if (!avpkt) { - mpp_packet_deinit(&packet); + releaseEncodedPacket(); return ERR_CODE(DEV_CTRL_ERR); } - avpkt->data = static_cast(pktData); - avpkt->size = static_cast(pktSize); + + if (pktSize > static_cast(std::numeric_limits::max()) || + av_new_packet(avpkt, static_cast(pktSize)) < 0) + { + av_packet_free(&avpkt); + releaseEncodedPacket(); + return ERR_CODE(DEV_CTRL_ERR); + } + + memcpy(avpkt->data, pktData, pktSize); avpkt->stream_index = 0; avpkt->pts = ptsUs; avpkt->dts = ptsUs; + avpkt->duration = m_config.fps > 0 + ? static_cast(1000000 / m_config.fps) + : 0; // 判断关键帧(I/IDR 帧) if (pktSize >= 5) @@ -561,6 +664,8 @@ int CVrFFMediaPusher::PushFrame(const void* data, size_t size, int64_t ptsUs) } } + releaseEncodedPacket(); + int aret; { std::lock_guard wlk(m_writeMutex); @@ -569,7 +674,13 @@ int CVrFFMediaPusher::PushFrame(const void* data, size_t size, int64_t ptsUs) // 微秒 → muxer 实际时基 av_packet_rescale_ts(avpkt, AVRational{1, 1000000}, m_fmtCtx->streams[0]->time_base); - aret = av_interleaved_write_frame(m_fmtCtx, avpkt); + aret = av_interleaved_write_frame(m_fmtCtx, avpkt); // 使用 interleaved,处理时间戳排序 + + // RTMP 推流需要强制刷新缓冲区,否则数据会积压不发送 + if (aret >= 0 && m_fmtCtx->pb) + { + avio_flush(m_fmtCtx->pb); + } } else { @@ -578,7 +689,6 @@ int CVrFFMediaPusher::PushFrame(const void* data, size_t size, int64_t ptsUs) } av_packet_free(&avpkt); - mpp_packet_deinit(&packet); if (aret < 0) { @@ -596,12 +706,18 @@ int CVrFFMediaPusher::Stop() // 先置 started=false,使并发的 PushFrame 尽快早退 m_bStarted = false; - // 主动推流模式无 accept 线程,直接发送 trailer(与 PushFrame 写帧互斥) + // 发送 trailer 并关闭 IO(与 PushFrame 写帧互斥) { std::lock_guard wlk(m_writeMutex); if (m_headerWritten.load() && m_fmtCtx) { - av_write_trailer(m_fmtCtx); // RTSP muxer 为 AVFMT_NOFILE,内部自动关闭连接 + av_write_trailer(m_fmtCtx); + + // RTMP 需要手动关闭 IO(RTSP 不需要,AVFMT_NOFILE) + if (m_fmtCtx->pb) + { + avio_closep(&m_fmtCtx->pb); + } } m_headerWritten = false; } @@ -661,7 +777,7 @@ int CVrFFMediaPusher::UnInit() std::string CVrFFMediaPusher::GetRtspUrl(const std::string& localIp) const { char buf[256]; - snprintf(buf, sizeof(buf), "rtsp://%s:%u%s", + snprintf(buf, sizeof(buf), "rtmp://%s:%u%s", localIp.c_str(), m_config.rtspPort, m_config.rtspPath.c_str()); return std::string(buf); } diff --git a/Module/FFMediaStream/_Inc/CVrFFMediaPuller.h b/Module/FFMediaStream/_Inc/CVrFFMediaPuller.h index e5932869..c9efc83d 100644 --- a/Module/FFMediaStream/_Inc/CVrFFMediaPuller.h +++ b/Module/FFMediaStream/_Inc/CVrFFMediaPuller.h @@ -16,6 +16,8 @@ #include "mpp_buffer.h" extern "C" { +#include +#include #include #include #include @@ -50,6 +52,7 @@ private: // FFmpeg RTSP 输入 AVFormatContext* m_fmtCtx{nullptr}; + AVBSFContext* m_bsfCtx{nullptr}; int m_videoStreamIndex{-1}; // 解码器输出尺寸(从 info_change 获取) @@ -76,6 +79,7 @@ private: static MppCodingType AvCodecIdToMppCoding(AVCodecID id); static int ToAvPixelFormat(EFFPullPixelFormat fmt); static unsigned int PixelBpp(EFFPullPixelFormat fmt); + int initBitstreamFilter(); int initMppDecoder(); int initSwscale(); void decodeLoop(); diff --git a/SDK/Lidar/rslidar/rs_driver/src/rs_driver/driver/decoder/decoder_RSEM4.hpp b/SDK/Lidar/rslidar/rs_driver/src/rs_driver/driver/decoder/decoder_RSEM4.hpp index a24df4b9..c344cb16 100644 --- a/SDK/Lidar/rslidar/rs_driver/src/rs_driver/driver/decoder/decoder_RSEM4.hpp +++ b/SDK/Lidar/rslidar/rs_driver/src/rs_driver/driver/decoder/decoder_RSEM4.hpp @@ -396,6 +396,14 @@ inline bool DecoderRSEM4::decodeCompPkt(const uint8_t* packet, siz this->first_point_ts_ = pkt_ts; ret = true; } + using PointT = typename T_PointCloud::PointT; + if (!RS_HAS_MEMBER(PointT, x)) + { + this->prev_point_ts_ = pkt_ts; + this->prev_pkt_ts_ = pkt_ts; + packet_info_.clear(); + return ret; + } rleDecodeMethod(packet, size, pkt_ts); packet_info_.clear(); return ret; @@ -546,6 +554,13 @@ inline bool DecoderRSEM4::decodeGeneralPkt(const uint8_t* packet, this->first_point_ts_ = pkt_ts; ret = true; } + using PointT = typename T_PointCloud::PointT; + if (!RS_HAS_MEMBER(PointT, x)) + { + this->prev_point_ts_ = pkt_ts; + this->prev_pkt_ts_ = pkt_ts; + return ret; + } const uint16_t blocks_per_pkt = this->const_param_.BLOCKS_PER_PKT; const uint16_t channels_per_block = this->const_param_.CHANNELS_PER_BLOCK; const float distance_res = this->const_param_.DISTANCE_RES; diff --git a/Test/RsLidarTest/main.cpp b/Test/RsLidarTest/main.cpp index 60500b3b..058cf2ee 100644 --- a/Test/RsLidarTest/main.cpp +++ b/Test/RsLidarTest/main.cpp @@ -156,7 +156,15 @@ static void StatsLoop() << " | pps=" << std::fixed << std::setprecision(0) << (static_cast(g_nTotalPoints) / elapsed); if (g_pDevice) - oss << " | drop=" << g_pDevice->GetDroppedFrameCount(); + { + const auto cb = g_pDevice->GetCallbackLatencyStats(); + oss << " | drop=" << g_pDevice->GetDroppedFrameCount() + << " | q=" << g_pDevice->GetStuffedQueueDepth() + << "/" << g_pDevice->GetStuffedQueuePeak() + << " | cb_us=" << cb.avgUs << "/" << cb.maxUs + << " | exc48=" << g_pDevice->GetExceptionCount(0x48) + << " | exc49=" << g_pDevice->GetExceptionCount(0x49); + } PrintLog(oss.str()); } } @@ -236,7 +244,8 @@ int main(int argc, char* argv[]) // 4. 注册回调(PointCloudCallback:Device 已完成 NaN→0 + 坐标变换 + 米→毫米) pDevice->SetPointCloudCallback(OnPointCloud); - pDevice->SetPacketCallback(OnPacket); + if (g_bVerbose) + pDevice->SetPacketCallback(OnPacket); pDevice->SetExceptionCallback(OnException); // 5. 启动 @@ -250,10 +259,11 @@ int main(int argc, char* argv[]) // 7. 显示窗口 if (g_bDisplay && g_pCloudShow) { - if (g_pCloudShow->Start("LiDAR PointCloud v1.0.2") == 0) + if (g_pCloudShow->Start("LiDAR PointCloud v1.1.1") == 0) PrintLog("PCL 窗口已打开 — START=连续保存 S=单帧 Q=退出"); - else - { PrintLog("PCL 窗口创建失败,继续无显示运行"); g_bDisplay = false; } + else{ + PrintLog("PCL 窗口创建失败,继续无显示运行"); g_bDisplay = false; + } } // 8. 主循环