Skip to content

feat(webcam): Implement threaded streaming for high FPS#1

Merged
cardil merged 2 commits into
masterfrom
feature/speedy-webcam
Nov 7, 2025
Merged

feat(webcam): Implement threaded streaming for high FPS#1
cardil merged 2 commits into
masterfrom
feature/speedy-webcam

Conversation

@cardil
Copy link
Copy Markdown
Owner

@cardil cardil commented Nov 7, 2025

This commit completely overhauls the webcam functionality to provide a much smoother, higher frame-rate live stream.

Previously, each request for cam.jpg would open the camera device, capture a single frame, and then close the device. This process was extremely slow, resulting in a frame rate of less than 1 FPS and causing unnecessary wear on the flash storage.

The new implementation introduces a background thread for continuous capture:

  • A dedicated pthread is spawned on the first request to the webcam page.
  • This thread keeps the camera device open and continuously captures frames into /tmp/cam.jpg (a RAM disk), significantly reducing latency and eliminating flash writes.
  • The thread automatically terminates and releases the camera if no new web requests are received within a 2-second timeout.
  • The frontend refresh interval has been reduced from 2000ms to 125ms, enabling a frame rate of ~8 FPS.

To support this new model, the webcam C API was refactored:

  • v_capture_image is replaced by v_open_camera, v_capture_frame_to_file, and v_close_camera for more granular control over the device state.

Additionally, a minor fix was made to the config parser to include the file path in the "cannot read" error message.

Summary by CodeRabbit

  • Bug Fixes

    • Improved error messages when configuration files cannot be opened.
  • New Features

    • Webcam switched to on-demand background capture with automatic idle shutdown.
    • Camera lifecycle handling added to improve reliability of frame capture.
    • Increased webcam refresh frequency for more responsive updates.
    • Webcam image styling made responsive to adapt to different screen sizes.

This commit completely overhauls the webcam functionality to provide a much smoother, higher frame-rate live stream.

Previously, each request for `cam.jpg` would open the camera device, capture a single frame, and then close the device. This process was extremely slow, resulting in a frame rate of less than 1 FPS and causing unnecessary wear on the flash storage.

The new implementation introduces a background thread for continuous capture:

-   A dedicated pthread is spawned on the first request to the webcam page.
-   This thread keeps the camera device open and continuously captures frames into `/tmp/cam.jpg` (a RAM disk), significantly reducing latency and eliminating flash writes.
-   The thread automatically terminates and releases the camera if no new web requests are received within a 2-second timeout.
-   The frontend refresh interval has been reduced from 2000ms to 125ms, enabling a frame rate of ~8 FPS.

To support this new model, the webcam C API was refactored:
-   `v_capture_image` is replaced by `v_open_camera`, `v_capture_frame_to_file`, and `v_close_camera` for more granular control over the device state.

Additionally, a minor fix was made to the config parser to include the file path in the "cannot read" error message.
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Nov 7, 2025

Walkthrough

Refactors webcam capture from a synchronous single-call to a lifecycle API (open/capture/close) and adds a background thread that periodically captures frames to /tmp/cam.jpg. Also improves config-file open error messages and changes the UI to refresh more frequently with responsive image styling.

Changes

Cohort / File(s) Summary
Config Error Reporting
src/config-parser.c
Enhanced error message to include the config file path when it cannot be opened.
Webcam API Refactor (headers & implementation)
src/httpd.h, src/webcam.c
Removed v_capture_image(const char*, int). Added int v_open_camera(void), int v_capture_frame_to_file(const char *v_filename), and void v_close_camera(void). Introduced v_is_open state and added debug instrumentation; adjusted select timeout from 2000ms to 500ms.
Background Capture Threading & Request Handling
src/request.c
Added pthread-based background webcam_capture_thread, thread state (thread id, running flag, last request timestamp) and mutex protection. Thread opens camera, repeatedly captures frames to /tmp/cam.tmp then renames to /tmp/cam.jpg, exits after 2s inactivity. HTTP handler starts thread on-demand and serves /tmp/cam.jpg.
Web UI: Webcam Page
webserver/opt/webfs/webcam/index.html
Reduced image refresh interval from 2000ms to 125ms; removed fixed container dimensions; changed image to responsive width: 100% and added a 3px solid black border.

Sequence Diagram(s)

sequenceDiagram
    participant Client
    participant HTTP as HTTP Handler
    participant Thread as Capture Thread
    participant Camera as Webcam (v_open/v_capture/v_close)
    participant FS as Filesystem (/tmp)

    rect rgb(230,245,255)
    Note over HTTP,Thread: Request-driven background capture lifecycle
    end

    Client->>HTTP: GET /mnt/.../webcam/cam.jpg
    activate HTTP
    HTTP->>HTTP: lock mutex
    HTTP->>HTTP: check thread running & update timestamp
    alt thread not running
        HTTP->>Thread: pthread_create(webcam_capture_thread)
    end
    HTTP->>HTTP: unlock mutex
    HTTP->>FS: return `/tmp/cam.jpg` (or fallback image)
    deactivate HTTP

    activate Thread
    Thread->>Camera: v_open_camera()
    Camera-->>Thread: opened (v_is_open = true)
    loop capture loop (until inactivity)
        Thread->>Camera: v_capture_frame_to_file("/tmp/cam.tmp")
        Camera->>FS: write `/tmp/cam.tmp`
        Thread->>FS: rename `/tmp/cam.tmp` -> `/tmp/cam.jpg`
        Thread->>Thread: check last_request_time (exit if >2s)
    end
    Thread->>Camera: v_close_camera()
    Camera-->>Thread: closed (v_is_open = false)
    deactivate Thread
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Potential focus areas:

  • Thread safety: mutex coverage around thread state and last-request timestamp.
  • Camera lifecycle: correctness of v_is_open handling across open/fail/close paths.
  • Race conditions during rename/copy of /tmp/cam.tmp/tmp/cam.jpg.
  • Resource cleanup: thread exit timing and interactions with incoming requests.
  • Performance impacts: reduced select timeout and increased UI refresh rate.

Poem

🐇 I nudge the camera, open wide, then hop away to play,

Frames tumble into /tmp like carrots fresh each day.
A thread hums softly, captures, then sleeps when quiet's nigh,
Open, capture, close — a tidy hop beneath the sky.

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 50.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately reflects the main change: implementing threaded streaming for high FPS through a background pthread that keeps the camera open and continuously captures frames.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feature/speedy-webcam

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 123e4b5 and f1e313e.

📒 Files selected for processing (5)
  • src/config-parser.c (1 hunks)
  • src/httpd.h (1 hunks)
  • src/request.c (3 hunks)
  • src/webcam.c (3 hunks)
  • webserver/opt/webfs/webcam/index.html (2 hunks)
🧰 Additional context used
🧬 Code graph analysis (2)
src/httpd.h (1)
src/webcam.c (3)
  • v_open_camera (735-793)
  • v_capture_frame_to_file (795-804)
  • v_close_camera (806-818)
src/request.c (1)
src/webcam.c (3)
  • v_open_camera (735-793)
  • v_capture_frame_to_file (795-804)
  • v_close_camera (806-818)
🔇 Additional comments (12)
src/config-parser.c (1)

24-24: LGTM! Enhanced error reporting.

Including the file path in the error message improves diagnostics when configuration file access fails.

webserver/opt/webfs/webcam/index.html (2)

17-17: LGTM! Increased refresh rate for smoother streaming.

The 125ms interval provides approximately 8 FPS, aligning with the PR objectives to improve the live stream experience.


45-58: LGTM! Responsive image styling.

The changes make the webcam feed responsive with width: 100%, improving display across different screen sizes.

src/request.c (3)

17-17: LGTM! Threading support added.

pthread.h is correctly included to support the new threaded webcam capture implementation.


1213-1216: LGTM! Well-structured thread state management.

The global state variables are appropriately guarded by the mutex to coordinate the webcam thread lifecycle and timeout tracking.


1339-1363: LGTM! Well-designed on-demand thread lifecycle.

The request handler properly:

  • Uses mutex protection for shared state access
  • Updates the last request timestamp to keep the thread alive
  • Starts the thread only when needed
  • Provides a placeholder image while the background thread initializes

The atomic rename from /tmp/cam.tmp to /tmp/cam.jpg (line 1245) ensures clients never read partially-written images.

src/httpd.h (1)

187-189: LGTM! Clean lifecycle-based camera API.

The new three-function API (open/capture/close) provides better control over camera lifecycle compared to the previous single-function approach, enabling the persistent camera connection needed for high-FPS streaming.

src/webcam.c (5)

52-52: LGTM! State tracking for camera lifecycle.

The v_is_open flag properly tracks camera state to prevent double-open/close operations.


301-302: LGTM! Reduced timeout improves responsiveness.

Changing from 2-second to 0.5-second timeout reduces the delay when capturing frames, contributing to the improved frame rate. The 0.5s timeout is still generous enough to handle transient delays.


735-793: LGTM! Robust camera initialization.

The v_open_camera implementation:

  • Prevents double-open via the v_is_open guard
  • Intelligently tries the last working device first, falling back to scanning /dev/video0-3
  • Tests both format modes (0 and 1)
  • Properly cleans up resources on failure via mem_manager_end()
  • Tracks successful configuration for future opens

This design minimizes initialization time for subsequent opens while maintaining robustness.


795-804: LGTM! Simple and correct frame capture.

The function properly validates that the camera is open before attempting capture and provides good debug logging.


806-818: LGTM! Proper cleanup sequence.

The v_close_camera function follows the correct teardown order: stop capturing → uninitialize device → close device → release memory → clear state.

Comment thread src/request.c
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

♻️ Duplicate comments (1)
src/request.c (1)

1232-1260: Race condition: camera access while thread is exiting.

The thread sets webcam_thread_running = 0 at line 1236, then releases the mutex and breaks from the loop, but doesn't close the camera until line 1258. During this window, the request handler could see the flag as 0, create a new thread, which would then try to access the camera while the old thread is still closing it.

The camera functions in webcam.c use the v_is_open flag without mutex protection, so concurrent access from two threads could lead to race conditions, resulting in camera errors or undefined behavior.

Restructure the cleanup to close the camera before marking the thread as not running:

     while (1) {
+        int should_exit = 0;
         pthread_mutex_lock(&webcam_mutex);
         time_t now = time(NULL);
         if (now - last_webcam_request_time > 2) {
             if (debug) fprintf(stderr, "+++ webcam thread: timeout, exiting\n");
-            webcam_thread_running = 0;
-            pthread_mutex_unlock(&webcam_mutex);
-            break;
+            should_exit = 1;
         }
         pthread_mutex_unlock(&webcam_mutex);
 
+        if (should_exit) {
+            break;
+        }
+
         if (debug) fprintf(stderr, "+++ webcam thread: capturing frame\n");
         // capture one frame to the file "/tmp/cam.jpg"
         int result = v_capture_frame_to_file("/tmp/cam.tmp");
         if (result == 0) {
             if (debug) fprintf(stderr, "+++ webcam thread: capture successful\n");
             rename("/tmp/cam.tmp", "/tmp/cam.jpg");
         } else {
             if (debug) fprintf(stderr, "--- webcam thread: capture failed, result: %d\n", result);
             // errors, use the default image
             custom_copy_file("/mnt/UDISK/webfs/webcam/default.jpg", "/tmp/cam.jpg", "wb", NULL);
         }
 
         usleep(75000);  // 75ms
     }
 
+    // Close camera before marking thread as not running
     if (debug) fprintf(stderr, "+++ webcam thread: closing camera\n");
     v_close_camera();
-    if (debug) fprintf(stderr, "+++ webcam thread: exited\n");
+    if (debug) fprintf(stderr, "+++ webcam thread: camera closed\n");
+
+    pthread_mutex_lock(&webcam_mutex);
+    webcam_thread_running = 0;
+    pthread_mutex_unlock(&webcam_mutex);
+
+    if (debug) fprintf(stderr, "+++ webcam thread: exited\n");
     return NULL;
 }
🧹 Nitpick comments (1)
src/request.c (1)

1244-1252: Add error checking for rename operation.

The rename() call at line 1247 doesn't check for errors. While the impact is minor (the old image would continue to be served), adding error checking would improve observability and help diagnose issues.

         int result = v_capture_frame_to_file("/tmp/cam.tmp");
         if (result == 0) {
             if (debug) fprintf(stderr, "+++ webcam thread: capture successful\n");
-            rename("/tmp/cam.tmp", "/tmp/cam.jpg");
+            if (rename("/tmp/cam.tmp", "/tmp/cam.jpg") != 0) {
+                if (debug) fprintf(stderr, "--- webcam thread: rename failed: %s\n", strerror(errno));
+                // Fall back to copying the file
+                custom_copy_file("/tmp/cam.tmp", "/tmp/cam.jpg", "wb", NULL);
+                unlink("/tmp/cam.tmp");
+            }
         } else {
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between f1e313e and fed26d1.

📒 Files selected for processing (1)
  • src/request.c (3 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
src/request.c (1)
src/webcam.c (3)
  • v_open_camera (735-793)
  • v_capture_frame_to_file (795-804)
  • v_close_camera (806-818)
🔇 Additional comments (4)
src/request.c (4)

17-17: LGTM!

Adding pthread.h is necessary for the threading functionality introduced in this PR.


1213-1216: LGTM!

The thread state variables are well-structured with appropriate types and initialization. The static mutex initializer is correct for file-scope variables.


1219-1219: Good fix for the thread resource leak!

The addition of pthread_detach(pthread_self()) at the beginning of the thread function correctly addresses the resource leak issue flagged in the previous review. This ensures thread resources are automatically released when the thread terminates.


1340-1365: Request handler logic is well-structured.

The mutex-protected updates to last_webcam_request_time and webcam_thread_running are correct. The placeholder image creation (lines 1358-1360) prevents 404 errors on first request, and redirecting to /tmp/cam.jpg achieves the goal of serving from RAM instead of flash.

@cardil cardil merged commit 985bb5f into master Nov 7, 2025
@cardil
Copy link
Copy Markdown
Owner Author

cardil commented Nov 7, 2025

Upstream PR: AGG2017#2

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.

1 participant