From 099a8a78af6396e48092a56b85a942800c60cf58 Mon Sep 17 00:00:00 2001 From: julian chen Date: Tue, 30 Jun 2026 09:44:36 +0800 Subject: [PATCH 1/4] fix stop hang graceful pollerr --- modules/v4l2/v4l2_device.cc | 16 ++++++++++++++-- src/core/CameraDevice.cpp | 7 ++++++- src/core/CaptureUnit.cpp | 19 +++++++++++++++++-- src/core/RequestThread.cpp | 26 ++++++++++++++++++++++++-- src/iutils/Thread.cpp | 9 ++++++++- 5 files changed, 69 insertions(+), 8 deletions(-) diff --git a/modules/v4l2/v4l2_device.cc b/modules/v4l2/v4l2_device.cc index a15f436..cf44ae4 100644 --- a/modules/v4l2/v4l2_device.cc +++ b/modules/v4l2/v4l2_device.cc @@ -443,8 +443,20 @@ int V4L2DevicePoller::Poll(int timeout_ms, int events, std::vector* } int ret = ::poll(poll_fds_.data(), poll_fds_.size(), timeout_ms); if (ret <= 0) { - for (size_t i = 0; i < devices_.size(); i++) { - LOGE("%s: Device node fd %d poll timeout.", __func__, devices_[i]->fd_); + if (ret < 0) { + LOGE("%s: poll error: %s", __func__, strerror(errno)); + } else { + /* ret == 0: normal 1-second timeout — not an error, just no frames yet. + * Log at debug level to avoid flooding the error log every second. */ + for (size_t i = 0; i < devices_.size(); i++) { + LOG2("%s: Device node fd %d poll timeout.", __func__, devices_[i]->fd_); + } + /* Defensive: if flush_fd fired in the same tick as timeout (race), + * treat it as a flush so the caller exits promptly. */ + if (flush_fd_ != -1 && (poll_fds_.back().revents & (POLLIN | POLLPRI))) { + LOG1("%s: flush detected on timeout path, fd %d.", __func__, poll_fds_.back().fd); + return 1; + } } return ret; } diff --git a/src/core/CameraDevice.cpp b/src/core/CameraDevice.cpp index 96bac0e..2e5de09 100644 --- a/src/core/CameraDevice.cpp +++ b/src/core/CameraDevice.cpp @@ -850,7 +850,12 @@ int CameraDevice::stop() { LOG1("@%s, mState:%d", mCameraId, __func__, mState); AutoMutex m(mDeviceLock); - mRequestThread->clearRequests(); + /* Stop the RequestThread first (sets mState=EXIT + notifies mFrameAvailableSignal). + * This unblocks any icamerasrc streaming thread blocked inside camera_stream_dqbuf → + * waitFrame() immediately, instead of waiting for the 30-second dead-camera timeout. + * requestStop() includes clearRequests() so no separate call needed. + * The second requestStop() call in deinit() is safe — it is a no-op when already stopped. */ + mRequestThread->requestStop(); m3AControl->stop(); mLensCtrl->stop(); diff --git a/src/core/CaptureUnit.cpp b/src/core/CaptureUnit.cpp index 1640742..3eefe79 100644 --- a/src/core/CaptureUnit.cpp +++ b/src/core/CaptureUnit.cpp @@ -419,12 +419,27 @@ int CaptureUnit::poll() { // Exiting, no error return -1; } - CheckAndLogError(ret < 0, UNKNOWN_ERROR, "%s: Poll error, ret:%d", __func__, ret); + if (ret < 0) { + LOGE("%s: Unexpected poll error (POLLERR) while streaming — " + "camera may have been disrupted by another camera's STREAMOFF. " + "Poll thread will exit; other cameras should continue normally.", + mCameraId, __func__); + return ret; + } if (ret == 0) { - LOG1("%s, timeout happens, wait recovery", mCameraId, __func__); + LOG2("%s, timeout happens, wait recovery", mCameraId, __func__); return OK; } + /* ret > 0: one or more fds are ready. If the flush pipe triggered this + * wake-up (stop() wrote to mFlushFd[1]) there will be no camera device in + * readyDevices, so the dequeue loop below is a no-op. Check mExitPending + * first to avoid a spurious dequeue attempt after stop(). */ + if (mExitPending) { + LOG2("%s: mExitPending true after poll wakeup, exit", __func__); + return -1; + } + for (const auto& readyDevice : readyDevices) { for (auto device : mDevices) { if (device->getV4l2Device() == readyDevice) { diff --git a/src/core/RequestThread.cpp b/src/core/RequestThread.cpp index a376fd3..4db8f79 100644 --- a/src/core/RequestThread.cpp +++ b/src/core/RequestThread.cpp @@ -221,6 +221,8 @@ int RequestThread::waitFrame(int streamId, camera_buffer_t **ubuffer) { if (mState == EXIT) { return NO_INIT; } + static const int kMaxTimeouts = 2; // ~10s before declaring camera dead and setting EXIT + int timeoutCount = 0; while (frameQueue.mFrameQueue.empty()) { std::cv_status ret = frameQueue.mFrameAvailableSignal.wait_for( lock, @@ -229,8 +231,28 @@ int RequestThread::waitFrame(int streamId, camera_buffer_t **ubuffer) { return NO_INIT; } - CheckWarning(ret == std::cv_status::timeout, TIMED_OUT, - "@%s, time out happens, wait recovery", mCameraId, __func__); + if (ret == std::cv_status::timeout) { + ++timeoutCount; + if (timeoutCount < kMaxTimeouts) { + LOGW("@%s, no frame for ~%ds — waiting (attempt %d/%d)", + mCameraId, __func__, + (int)(kWaitFrameDuration * SLOWLY_MULTIPLIER / 1000000000LL) * timeoutCount, + timeoutCount, kMaxTimeouts); + } else { + LOGE("@%s, camera appears dead (no frame in ~%ds). " + "Poll thread may have exited due to POLLERR from another camera's STREAMOFF. " + "Marking EXIT so dqbuf() loop breaks and pipeline can shut down.", + mCameraId, __func__, + (int)(kWaitFrameDuration * SLOWLY_MULTIPLIER / 1000000000LL) * timeoutCount); + /* Set EXIT before releasing the lock so the next waitFrame() call (or a + * concurrent one on another stream) returns NO_INIT immediately instead of + * blocking for another kMaxTimeouts × kWaitFrameDuration seconds. + * CameraDevice::dqbuf() loops while ret==TIMED_OUT — returning NO_INIT here + * breaks that loop and lets icamerasrc post GST_FLOW_ERROR to start teardown. */ + mState = EXIT; + return NO_INIT; + } + } } shared_ptr camBuffer = frameQueue.mFrameQueue.front(); diff --git a/src/iutils/Thread.cpp b/src/iutils/Thread.cpp index 26e5aee..6713ff4 100644 --- a/src/iutils/Thread.cpp +++ b/src/iutils/Thread.cpp @@ -86,9 +86,16 @@ void Thread::wait() { return; } + const int64_t kWaitTimeout = 5000000000LL; // 5 seconds while (mState != EXITED) { mState = EXITING; - mExitedCondition.wait(lock); + int waitRet = mExitedCondition.waitRelative(lock, kWaitTimeout); + if (waitRet == TIMED_OUT) { + LOGE("Thread::wait() timed out after 5s waiting for thread '%s' to exit — " + "poll thread may be stuck in kernel; forcing exit", + mName.empty() ? "NO_NAME" : mName.c_str()); + break; + } } return; From 91d39d30dcfb350d7d0aae63d45383ad15b4fdc4 Mon Sep 17 00:00:00 2001 From: Julian Chen Date: Wed, 24 Jun 2026 12:49:17 +0800 Subject: [PATCH 2/4] Fix 8x camera concurrent stream on --- src/hal/CameraHal.cpp | 1 + src/platformdata/CameraSensorsParser.cpp | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/hal/CameraHal.cpp b/src/hal/CameraHal.cpp index 7d79fb3..4845922 100644 --- a/src/hal/CameraHal.cpp +++ b/src/hal/CameraHal.cpp @@ -280,6 +280,7 @@ int CameraHal::deviceStart(int cameraId) { cameraId, mConfigTimes); } } + lock.unlock(); // allow concurent STREAMON for all VC Cameras // VIRTUAL_CHANNEL_E return device->start(); diff --git a/src/platformdata/CameraSensorsParser.cpp b/src/platformdata/CameraSensorsParser.cpp index 4212ae4..8410283 100644 --- a/src/platformdata/CameraSensorsParser.cpp +++ b/src/platformdata/CameraSensorsParser.cpp @@ -777,8 +777,8 @@ void CameraSensorsParser::parseSensorSection(const Json::Value& node) { if (node.isMember("vcId")) { mCurCam->mVCId = node["vcId"].asInt(); } - if (node.isMember("vcGoupId")) { - mCurCam->mVCGroupId = node["vcGoupId"].asInt(); + if (node.isMember("vcGroupId")) { + mCurCam->mVCGroupId = node["vcGroupId"].asInt(); } // VIRTUAL_CHANNEL_E resolveCsiPortAndI2CBus(); From b9f78566d5ea595fa568c78e882efdca59375473 Mon Sep 17 00:00:00 2001 From: Julian Chen Date: Wed, 24 Jun 2026 14:32:00 +0800 Subject: [PATCH 3/4] Fix sensor name parse error --- src/v4l2/MediaControl.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/v4l2/MediaControl.cpp b/src/v4l2/MediaControl.cpp index 0b2cc33..ed940f7 100644 --- a/src/v4l2/MediaControl.cpp +++ b/src/v4l2/MediaControl.cpp @@ -1125,8 +1125,9 @@ int MediaControl::getI2CBusAddress(const string& sensorEntityName, const string& char* entityName = nullptr; size_t sensorEntityNameLen = sensorEntityName.length(); for (int i = 0; i < linksCount; i++) { - if (strcmp(links[i].sink->entity->info.name, sinkEntityName.c_str()) == 0) { - entityName = entity.info.name; + if (strcmp(links[i].sink->entity->info.name, sinkEntityName.c_str()) == 0 && + strncmp(entity.info.name, sensorEntityName.c_str(), sensorEntityNameLen) == 0) { + entityName = entity.info.name; break; } } From 56c174d76f5efac5449902ec2cdd88b410ac2e1a Mon Sep 17 00:00:00 2001 From: Julian Chen Date: Wed, 24 Jun 2026 14:37:16 +0800 Subject: [PATCH 4/4] Fix 2nd time open 8x gmsl camera with mmap streaming fail --- src/v4l2/MediaControl.cpp | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/v4l2/MediaControl.cpp b/src/v4l2/MediaControl.cpp index ed940f7..c9a3b61 100644 --- a/src/v4l2/MediaControl.cpp +++ b/src/v4l2/MediaControl.cpp @@ -1022,10 +1022,12 @@ void MediaControl::mediaCtlClear(int cameraId, MediaCtlConf* mc) { * clearing routes here would disrupt other processes or camera instances that share the * same subdev and are still streaming. Routes are left active on the hardware; the next * open() will skip SetRouting if the routes already match (see setRouting). - * Remove the receiver from the in-process tracking set so that a subsequent open() in - * this process re-evaluates the full setup (formats, links), even though SetRouting itself - * will be skipped when routes are still correctly configured. + * Reset mIsMediaCtlSetup so that a subsequent open() in this process re-evaluates the + * full setup (formats, links), even though SetRouting itself will be skipped when routes + * are still correctly configured. */ + AutoMutex lock(sLock); + mIsMediaCtlSetup = false; // VIRTUAL_CHANNEL_E }