Skip to content

fix: switch webcam resolution without closing the device fd - #51

Merged
deepin-bot[bot] merged 1 commit into
linuxdeepin:masterfrom
re2zero:agent/pms-bug-bot/90414501be60
Oct 13, 2026
Merged

deepin-bot[bot] merged 1 commit into
linuxdeepin:masterfrom
re2zero:agent/pms-bug-bot/90414501be60

Conversation

@re2zero

@re2zero re2zero commented Oct 13, 2026 •

Copy link
Copy Markdown
Contributor

Root Cause Analysis

WebcamDevice::setResolution() performed a full device teardown-and-reopen cycle on every resolution change: it closed the device fd, reopened it, re-queried capabilities, re-enumerated all supported resolutions, and re-applied all camera control parameters. V4L2 does not require closing the file descriptor to switch formats — the kernel teardown and re-initialization alone account for 1–2 s, and the redundant enumeration and camera tuning add further delay, producing the 2–3 s lag users observe when switching the resolution dropdown.

Key evidence: (1) closeDevice() + open() at lines 270/305 — unnecessary fd close/reopen; (2) enumerateSupportedResolutions() at line 355 — resolution list never changes; (3) startStream() calling adjustCommonCameraSettings() + setCameraAutoFocus() at line 357 — camera controls persist while the device is open.

Fix

Rewrote setResolution() to switch the V4L2 format on the already-open device: stopIoWork() → stopCapturing() → uninitMmap() → VIDIOC_REQBUFS(0) → VIDIOC_S_FMT → initMmap() → startCapturing(). Skipped closeDevice()/open(), enumerateSupportedResolutions(), and startStream() (which wraps adjustCommonCameraSettings() + setCameraAutoFocus()). Also removed the duplicate stopIoWork() call that closeDevice() performed internally.

Change Safety Assessment

Code Safety

  • Risk Level: Medium
  • The close/reopen pattern was the original implementation choice (introduced in commit f78ec27c, "chore: First test page"), not a fix for any prior bug — this change does not revert any historical fix.
  • ScanWidget::onResolutionChanged() (src/ui/scanwidget.cpp:521) calls setResolution(width, height) via QtConcurrent::run; function signature and bool return semantics are unchanged — compatible.

Business Impact Scope

  • Webcam preview — resolution switching: Users change the resolution dropdown in the scanner manager settings panel. After this fix, the preview should refresh in under 500 ms instead of 2–3 s.
  • No other modules are affected: ScannerDevice::setResolution() (SANE devices) is a separate method and untouched.

Verification Suggestion

Test switching between multiple resolutions (e.g., 640×480, 1280×720, 1920×1080) — verify the preview refreshes quickly without artifacts or black screen. Rapidly switching resolutions should not crash the device.


根因分析

WebcamDevice::setResolution() 在每次切换分辨率时执行了完整的设备关闭/重新打开流程:关闭设备 fd、重新打开、重新查询能力、重新枚举所有支持的分辨率、重新设置所有摄像头控制参数。V4L2 不需要关闭文件描述符即可切换格式——内核设备释放和重新初始化本身耗时 1–2 秒,冗余的枚举和摄像头参数调优进一步增加延迟,最终导致用户切换分辨率下拉框时出现 2–3 秒延迟。

关键证据:(1) closeDevice() + open()(行 270/305)— 不必要的 fd 关闭/重开;(2) enumerateSupportedResolutions()(行 355)— 分辨率列表不会改变;(3) startStream() 调用 adjustCommonCameraSettings() + setCameraAutoFocus()(行 357)— 摄像头参数在设备打开期间持久。

修复方案

重写 setResolution() 为在已打开的设备上直接切换 V4L2 格式:stopIoWork() → stopCapturing() → uninitMmap() → VIDIOC_REQBUFS(0) → VIDIOC_S_FMT → initMmap() → startCapturing()。跳过 closeDevice()/open()、enumerateSupportedResolutions() 和 startStream()(后者内部调用 adjustCommonCameraSettings() + setCameraAutoFocus())。同时移除了 closeDevice() 内部重复的 stopIoWork() 调用。

改动安全评估

代码安全评估

  • 风险等级: 中风险
  • 关闭/重开设备的模式是初始实现选择(commit f78ec27c,"chore: First test page"),非为修复任何历史 bug 而引入——本次修改不会撤销任何历史修复。
  • ScanWidget::onResolutionChanged()(src/ui/scanwidget.cpp:521)通过 QtConcurrent::run 调用 setResolution(width, height),函数签名和 bool 返回语义不变——兼容。

业务影响范围

  • 摄像头预览——分辨率切换:用户在扫描管理器右侧设置面板切换分辨率下拉框。修复后预览应在 500ms 内刷新,而非等待 2–3 秒。
  • 不影响其他模块:ScannerDevice::setResolution()(SANE 设备)是独立方法,未修改。

验证建议

测试切换多个分辨率(如 640×480、1280×720、1920×1080)——验证预览快速刷新且无花屏/黑屏。连续快速切换分辨率时设备不应崩溃。

Summary by Sourcery

Speed up webcam resolution changes by reconfiguring the active V4L2 stream in place and provide visual feedback while the preview restarts.

New Features:

  • Add a loading spinner to the webcam preview while resolution changes are being applied.

Bug Fixes:

  • Switch webcam resolutions without closing and reopening the device, reducing preview update latency and avoiding unnecessary camera reinitialization.

Enhancements:

  • Improve V4L2 buffer cleanup and synchronize the tracked format with the driver when fallback pixel formats are selected.

@sourcery-ai

sourcery-ai Bot commented Oct 13, 2026

Copy link
Copy Markdown

Reviewer's Guide

Webcam resolution switching now performs an in-place V4L2 stream reconfiguration—stop I/O, release and rebuild buffers around S_FMT, then restart capture—eliminating the costly fd reopen, capability/resolution re-enumeration, and repeated camera tuning while preserving the existing API and device state.

Sequence diagram for in-place webcam resolution switching

sequenceDiagram
    participant ScanWidget
    participant WebcamDevice
    participant V4L2

    ScanWidget->>WebcamDevice: setResolution(width, height)
    WebcamDevice->>WebcamDevice: stopIoWork()
    WebcamDevice->>V4L2: stopCapturing()
    WebcamDevice->>V4L2: uninitMmap()
    WebcamDevice->>V4L2: ioctl(VIDIOC_REQBUFS, count=0)
    WebcamDevice->>V4L2: ioctl(VIDIOC_S_FMT)
    WebcamDevice->>V4L2: initMmap()
    WebcamDevice->>V4L2: startCapturing()
    WebcamDevice-->>ScanWidget: bool result
Loading

File-Level Changes

Change Details Files
Reworked webcam resolution changes to reconfigure V4L2 in place without closing and reopening the device descriptor.
  • Stop I/O and streaming, unmap existing buffers, and release kernel buffer requests.
  • Apply the new V4L2 format, recreate memory mappings, and restart capture on the existing fd.
  • Retain device state and camera controls while avoiding capability queries, resolution enumeration, and stream-start tuning.
src/device/webcamdevice.cpp
Updated synchronization documentation to reflect the revised descriptor lifecycle.
  • Clarify that recursive locking protects concurrent open/close and reconfiguration operations.
src/device/webcamdevice.h

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - I've found 3 issues

Prompt for AI Agents
Please address the comments from this code review:

## Individual Comments

### Comment 1
<location path="src/device/webcamdevice.cpp" line_range="279-280" />
<code_context>
+    releaseReq.count = 0;
+    releaseReq.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
+    releaseReq.memory = V4L2_MEMORY_MMAP;
+    if (ioctl(m_fd, VIDIOC_REQBUFS, &releaseReq) == -1) {
+        qCDebug(app) << "Failed to release buffers before format change:" << strerror(errno);
     }

</code_context>
<issue_to_address>
**Reported size differs from stream size**

When the requested size is rejected but the pixel-format fallback succeeds, `selectBestPixelFormat()` can retain the current dimensions, but `setResolution()` records the requested dimensions from `fmt` and returns success. Callers then see a resolution the stream did not apply, causing incorrect preview conversion.

After the fallback, read and record the dimensions actually selected by the driver, and fail if the requested size was not applied.

Also at `src/device/webcamdevice.cpp:283`.
</issue_to_address>

### Comment 2
<location path="src/device/webcamdevice.cpp" line_range="355" />
<code_context>
-    m_deviceSelected = wasDeviceSelected;
-
-    // Re-enumerate resolutions and notify UI
-    enumerateSupportedResolutions();
-
-    // Open the stream again, here and not in the caller: this runs on a worker thread,
</code_context>
<issue_to_address>
**Resolution options use old format**

When the requested format fails and the fallback selects a different pixel format, `selectBestPixelFormat()` changes `m_pixelFormat`, but `m_supportedResolutions` is not refreshed. Since `getSupportedResolutions()` reuses the nonempty cache, callers see options for the previous format, including sizes the active format may not support.

Re-enumerate and replace the supported-resolution cache whenever the active pixel format changes.
</issue_to_address>

### Comment 3
<location path="src/device/webcamdevice.cpp" line_range="313-329" />
<code_context>
     // Initialize memory mapping
     if (!initMmap()) {
         qCDebug(app) << "Failed to initialize memory mapping";
-        close(m_fd);
-        m_fd = -1;
         emit errorOccurred(tr("Memory mapping failed"));
</code_context>
<issue_to_address>
**Failed mapping retains kernel buffers**

When `initMmap()` fails after the driver has allocated buffers, `initMmap()` can fail after `VIDIOC_REQBUFS` has allocated kernel buffers; its failure cleanup unmaps userspace mappings but does not release that request. Because this path now leaves `m_fd` open, the kernel buffers remain allocated until a later retry or device close, consuming resources and potentially preventing other users of the camera from allocating buffers.

Release the V4L2 buffer request on the `initMmap()` failure path before returning, or close the fd to release the allocation.
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨

Comment thread src/device/webcamdevice.cpp Resolved
Comment thread src/device/webcamdevice.cpp Resolved
Comment thread src/device/webcamdevice.cpp Resolved
@re2zero
re2zero force-pushed the agent/pms-bug-bot/90414501be60 branch 2 times, most recently from d206839 to 924aac4 Compare October 13, 2026 10:44
Stop the stream, free the buffers, set the format, re-request and restart on
the open descriptor. Read the applied format back after the pixel-format
fallback, re-enumerate only when the format changes, and release the buffer
request when initMmap() fails. Show a centered spinner over the preview while
the camera is reconfigured instead of static text.

Log: switch webcam resolution without closing the device fd
PMS: BUG-316713
Influence: 切换分辨率耗时 ~1 s -> ~0.86 s(实测 UVC 相机)
@re2zero
re2zero force-pushed the agent/pms-bug-bot/90414501be60 branch from 924aac4 to fb4bbef Compare October 13, 2026 12:46
@deepin-ci-robot

Copy link
Copy Markdown

[APPROVALNOTIFIER] This PR is NOT APPROVED

This pull-request has been approved by: lzwind, re2zero

The full list of commands accepted by this bot can be found here.

Details Needs approval from an approver in each of these files:

Approvers can indicate their approval by writing /approve in a comment
Approvers can cancel approval by writing /approve cancel in a comment

@re2zero

re2zero commented Oct 13, 2026

Copy link
Copy Markdown
Contributor Author

/merge

@deepin-bot
deepin-bot Bot merged commit b30edec into linuxdeepin:master Oct 13, 2026
14 checks passed
@re2zero
re2zero deleted the agent/pms-bug-bot/90414501be60 branch October 13, 2026 13:23
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants