Skip to content

Conversation

@LeeGoDamn
Copy link
Collaborator

@LeeGoDamn LeeGoDamn commented Dec 27, 2025

This PR introduces Rust bindings for the CameraCapture library, providing a high-performance and lightweight alternative to existing solutions.

Key Features

  • High Performance: Leverages the C++ core's AVX2 (x86) and NEON (ARM) SIMD optimizations for pixel format conversion, offering significant speedups over generic software implementations.
  • Minimal Dependencies: Designed to be ultra-lightweight. The core library has zero heavy dependencies (no image, no opencv), making it ideal for embedded systems and CLI tools.
  • Memory Safety: Implements safe Rust wrappers with proper lifetime management (fixed VideoFrameInfo lifetime issues) to prevent use-after-free errors.
  • Async Support: Includes optional tokio integration for modern async Rust applications.
  • Cross-Platform: Supports Windows, Linux, and macOS via the underlying C++ library.

Changes

  • Added bindings/rust directory containing the Cargo project.
  • Implemented Provider, VideoFrame, and Convert structs.
  • Exposed hardware-accelerated conversion functions in convert module.
  • Improved build.rs to correctly link against Debug/Release builds of the C++ library.
  • Added examples for basic capture, callbacks, and format conversion.

Comparison with nokhwa

Unlike nokhwa, which focuses on broad compatibility and ease of use (often at the cost of heavy dependencies and generic performance), this binding focuses on raw performance and minimal footprint, making it suitable for computer vision and high-throughput video processing tasks.

Summary by CodeRabbit

  • New Features

    • Added Rust bindings (ccap-rs) for camera capture with support for device enumeration, frame capture, and color conversion
    • Included four example programs demonstrating common use cases
  • Documentation

    • Added comprehensive Rust bindings documentation with setup, usage, and platform-specific guidance
    • Updated main documentation with Rust bindings references
  • Chores

    • Added GitHub Actions CI workflow for Rust bindings
    • Added build scripts and VS Code tasks for Rust development

✏️ Tip: You can customize this high-level summary in your review settings.

Copilot AI review requested due to automatic review settings December 27, 2025 20:53
@coderabbitai
Copy link

coderabbitai bot commented Dec 27, 2025

Caution

Review failed

The pull request is closed.

Walkthrough

Introduces comprehensive Rust bindings for the ccap video capture library as a new Cargo crate (ccap-rs) at bindings/rust, including a Provider API for camera operations, color conversion utilities, error handling, build system integration, CI workflow, examples, and documentation.

Changes

Cohort / File(s) Summary
GitHub Actions & CI
.github/workflows/rust.yml, .vscode/tasks.json, CMakeLists.txt
New Rust CI workflow on ubuntu/windows/macos with static-link and build-source modes; VS Code Rust build/test tasks; CMake option CCAP_BUILD_RUST with ccap-rust and ccap-rust-test targets.
Rust Crate Manifest & Build
bindings/rust/Cargo.toml, bindings/rust/build.rs, bindings/rust/wrapper.h, bindings/rust/.gitignore
Cargo.toml defines ccap crate with thiserror/bindgen/cc deps; dual features for static-link and build-source modes; build.rs auto-discovers ccap root, compiles C sources, generates FFI bindings; wrapper.h includes C API headers.
Build & Deployment Automation
scripts/update_version.sh, scripts/publish_rust_crate.sh, scripts/test_remote_crate.sh, bindings/rust/build_and_test.sh
Version update script adds Rust crate versioning; publish script orchestrates crates.io publication with pre-release handling; test_remote_crate.sh validates remote crate; build_and_test.sh coordinates C/Rust compilation and device discovery verification.
Rust Library Core
bindings/rust/src/lib.rs, bindings/rust/src/error.rs, bindings/rust/src/types.rs, bindings/rust/src/frame.rs
Library root re-exports public API; error.rs defines CcapError enum with Display/From impl; types.rs provides PixelFormat, FrameOrientation, PropertyName, ColorConversionBackend enums with C FFI conversions; frame.rs wraps VideoFrame and DeviceInfo with safe accessors.
Rust API Implementations
bindings/rust/src/provider.rs, bindings/rust/src/convert.rs, bindings/rust/src/utils.rs
Provider implements camera lifecycle (new, open, start, grab_frame, callbacks); Convert exposes color conversions (YUYV/NV12/I420 to RGB24/BGR24) with backend detection; Utils provides pixel format conversions, frame dumping, device selection, and log level control.
Examples & Tests
bindings/rust/examples/*.rs, bindings/rust/tests/integration_tests.rs
Four examples (print_camera, minimal_example, capture_grab, capture_callback) demonstrate device enumeration, frame capture, and callback usage; integration tests validate provider creation, version retrieval, device listing, and error paths.
Rust Crate Documentation
bindings/rust/README.md, docs/content/rust-bindings.md, docs/content/rust-bindings.zh.md
README covers quick-start, usage, features, building, and ASan notes; rust-bindings docs detail setup, examples, feature flags, build modes, and backend support; Chinese documentation mirrors English content.
Project Documentation Updates
README.md, README.zh-CN.md, docs/content/*.md, docs/index.html, docs/documentation.html
Add Rust bindings section, Rust CI badge, and Rust-related navigation links across README, cli.md, cmake-options.md, documentation.md, and HTML docs; update site keywords and add Rust code examples.
Source Code Corrections
src/ccap_convert_frame.cpp, src/ccap_imp_windows.cpp, src/ccap_imp_windows.h
Minor casting fix in frame size assignment; minor whitespace normalization in Windows QueryInterface method signature.

Sequence Diagram(s)

sequenceDiagram
    actor User
    participant RustApp as Rust Application
    participant Provider
    participant FFI as FFI Bindings
    participant CLib as ccap C Library
    participant Camera as Camera Device

    User->>RustApp: Create provider
    RustApp->>Provider: Provider::new()
    Provider->>FFI: ccap_provider_create()
    FFI->>CLib: Call native create
    CLib->>Camera: Enumerate devices
    CLib-->>FFI: Return provider handle

    User->>RustApp: Select and open device
    RustApp->>Provider: open_device()
    Provider->>FFI: ccap_provider_open()
    FFI->>CLib: Open device
    CLib->>Camera: Access device
    CLib-->>FFI: Success

    User->>RustApp: Configure and start
    RustApp->>Provider: set_resolution()/start()
    Provider->>FFI: ccap_provider_set_property()/ccap_provider_start()
    FFI->>CLib: Configure camera
    CLib->>Camera: Apply settings and start streaming

    loop Capture Frames
        User->>RustApp: grab_frame()
        RustApp->>Provider: grab_frame(timeout)
        Provider->>FFI: ccap_provider_grab_frame()
        FFI->>CLib: Grab from camera
        CLib->>Camera: Retrieve frame
        CLib-->>FFI: Return VideoFrame
        FFI-->>Provider: Wrapped VideoFrame
        Provider-->>RustApp: Result<Option<VideoFrame>>
        RustApp->>User: Frame data (with safe accessors)
    end

    User->>RustApp: Stop capture
    RustApp->>Provider: stop()
    Provider->>FFI: ccap_provider_stop()
    FFI->>CLib: Stop streaming
    CLib->>Camera: Release resources
    RustApp->>Provider: Drop
    Provider->>FFI: ccap_provider_destroy()
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Possibly related PRs

  • PR #4: Rust bindings directly expose the C conversion API (ccap_convert_c.h / ccap_convert_* functions) that this PR adds and modifies, including NEON/backend support.
  • PR #38: Rust build/test tooling (build_and_test.sh, CMake CCAP_BUILD_RUST flag) locates and invokes the ccap CLI binary whose implementation and CMake target definition are provided by this PR.
  • PR #20: Windows DirectShow linkage changes in src/ccap_imp_windows.* are touched by both PRs; this PR maintains strmiids linking while the related PR removes it.

Suggested labels

enhancement

Suggested reviewers

  • Lozg2
  • yixy-only

Poem

🦀 A crab scurries through the code with glee,
Binding Rust and C in harmony!
Cameras click, frames dance and twirl,
Safe abstractions now unfurl.
hop hop—tests pass, docs so bright! ✨

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title clearly describes the main change: adding Rust bindings with SIMD acceleration, which is the primary purpose of the changeset.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ 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/rust

📜 Recent review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ec77592 and b969240.

⛔ Files ignored due to path filters (1)
  • bindings/rust/Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (42)
  • .github/workflows/rust.yml
  • .vscode/tasks.json
  • CMakeLists.txt
  • README.md
  • README.zh-CN.md
  • bindings/rust/.gitignore
  • bindings/rust/Cargo.toml
  • bindings/rust/README.md
  • bindings/rust/build.rs
  • bindings/rust/build_and_test.sh
  • bindings/rust/examples/capture_callback.rs
  • bindings/rust/examples/capture_grab.rs
  • bindings/rust/examples/minimal_example.rs
  • bindings/rust/examples/print_camera.rs
  • bindings/rust/src/convert.rs
  • bindings/rust/src/error.rs
  • bindings/rust/src/frame.rs
  • bindings/rust/src/lib.rs
  • bindings/rust/src/provider.rs
  • bindings/rust/src/types.rs
  • bindings/rust/src/utils.rs
  • bindings/rust/tests/integration_tests.rs
  • bindings/rust/wrapper.h
  • docs/content/cli.md
  • docs/content/cli.zh.md
  • docs/content/cmake-options.md
  • docs/content/cmake-options.zh.md
  • docs/content/documentation.md
  • docs/content/documentation.zh.md
  • docs/content/implementation-details.md
  • docs/content/implementation-details.zh.md
  • docs/content/rust-bindings.md
  • docs/content/rust-bindings.zh.md
  • docs/documentation.html
  • docs/index.html
  • scripts/README.md
  • scripts/publish_rust_crate.sh
  • scripts/test_remote_crate.sh
  • scripts/update_version.sh
  • src/ccap_convert_frame.cpp
  • src/ccap_imp_windows.cpp
  • src/ccap_imp_windows.h

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

@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: 17

🧹 Nitpick comments (20)
CMakeLists.txt (1)

350-355: Consider respecting CMAKE_BUILD_TYPE for Rust builds.

The Rust build always uses --release mode, but CMake might be configured for Debug builds. For consistency and to enable debug symbols in Rust code when developing:

🔎 Proposed enhancement to match build type
         # Add custom target to build Rust bindings
         add_custom_target(ccap-rust
-                COMMAND ${CARGO_CMD} build --release
+                COMMAND ${CARGO_CMD} build $<$<CONFIG:Release>:--release>
                 WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/bindings/rust
                 COMMENT "Building Rust bindings"
                 DEPENDS ccap
         )
bindings/rust/RUST_INTEGRATION_SUMMARY.md (1)

29-47: Specify language identifier for code fence.

The fenced code block lacks a language identifier, which impacts syntax highlighting and automated processing.

🔎 Proposed fix
 ### 项目结构
-```
+```text
 bindings/rust/
 ├── Cargo.toml              # 包配置
bindings/rust/Cargo.toml (1)

17-17: Consider reducing tokio feature scope.

Using features = ["full"] includes all tokio features, which increases compile time and binary size. Since this is optional (behind the "async" feature), evaluate if a more minimal feature set would suffice.

Typical minimal async features might be: ["rt", "sync", "time", "macros"]. Review the actual usage in src/async.rs to determine the minimum required set.

.vscode/tasks.json (1)

1227-1326: Consider adding dependency on C library build.

The Rust binding tasks execute cargo commands without ensuring the underlying C++ ccap library is built first. Since the Rust bindings link against the C++ library (via build.rs), these tasks may fail if the library hasn't been built.

Consider adding dependsOn for tasks like "Build Rust Bindings" to depend on "Build Project (Debug)" or "Build Project (Release)":

🔎 Example enhancement
{
    "label": "Build Rust Bindings",
    "type": "shell",
    "command": "cargo",
    "args": ["build"],
    "options": {
        "cwd": "${workspaceFolder}/bindings/rust"
    },
    "group": "build",
    "problemMatcher": "$rustc",
    "dependsOn": ["Build Project (Debug)"]
}
bindings/rust/examples/minimal_example.rs (3)

5-7: Global error callback leaks memory by design.

Based on the relevant code snippet from provider.rs, the error callback intentionally leaks memory (line 308-347 note). While acceptable for examples, consider documenting this behavior or providing an example of proper lifecycle management for production use.

Add a comment explaining the memory leak:

// Set error callback to receive error notifications
// Note: This callback is global and persists for the application lifetime
Provider::set_error_callback(|error_code, description| {
    eprintln!("Error occurred - Code: {}, Description: {}", error_code, description);
});

18-21: Early return suppresses error information.

When is_started() returns false, the function returns Ok(()), which masks the actual failure. This makes debugging harder for users running the example.

🔎 Consider returning an error instead
     if !provider.is_started() {
         eprintln!("Failed to start camera!");
-        return Ok(());
+        return Err(ccap::CcapError::OperationFailed("Camera failed to start".to_string()));
     }

32-40: Error handling silently exits on frame grab failures.

The example returns Ok(()) on both None (line 34) and Err (line 38) cases, suppressing the actual error status. While this prevents example crashes, it could confuse users trying to diagnose issues.

For a more informative example, consider propagating errors:

Ok(None) => {
    return Err(ccap::CcapError::OperationFailed(
        format!("Timeout grabbing frame {}", i)
    ));
}
Err(e) => {
    return Err(e);
}
.github/workflows/rust.yml (1)

6-6: Consider expanding path filters to include C library changes.

The workflow only triggers on bindings/rust/** changes, but modifications to the C library headers (include/ccap*.h) or core implementation could break the Rust bindings. Consider adding these paths to the filter.

🔎 Proposed enhancement
   push:
     branches: [ main, develop ]
-    paths: [ 'bindings/rust/**' ]
+    paths: 
+      - 'bindings/rust/**'
+      - 'include/ccap*.h'
+      - 'src/**'
   pull_request:
     branches: [ main, develop ]
-    paths: [ 'bindings/rust/**' ]
+    paths:
+      - 'bindings/rust/**'
+      - 'include/ccap*.h'
+      - 'src/**'

Also applies to: 9-9

bindings/rust/build.rs (1)

37-38: Document or remove commented v4l2 linking.

The commented-out v4l2 linking should either be removed if not needed, or documented why it's disabled but preserved. This helps future maintainers understand the decision.

🔎 Suggested clarification
     #[cfg(target_os = "linux")]
     {
-        // v4l2 might not be available on all systems
-        // println!("cargo:rustc-link-lib=v4l2");
+        // Note: v4l2 linking is not required as the C library statically links it
+        // or handles it internally. Keeping this comment for reference.
         println!("cargo:rustc-link-lib=stdc++");
     }
bindings/rust/examples/capture_callback.rs (1)

27-31: Simplify dead conditional logic.

The conditional always evaluates to 0 regardless of the number of devices, making the if statement unnecessary. Either simplify this or add actual device selection logic.

🔎 Proposed simplification
-    // Select camera device (automatically use first device for testing)
-    let device_index = if devices.len() == 1 {
-        0
-    } else {
-        0 // Just use first device for now
-    };
+    // Select camera device (use first device for this example)
+    let device_index = 0;
bindings/rust/examples/capture_grab.rs (1)

37-37: Use FileOperationFailed for directory creation errors.

Directory creation failure is mapped to InvalidParameter, but FileOperationFailed (used in capture_callback.rs line 64) is more semantically accurate for filesystem operations.

🔎 Proposed fix
         fs::create_dir_all(capture_dir)
-            .map_err(|e| ccap::CcapError::InvalidParameter(format!("Failed to create directory: {}", e)))?;
+            .map_err(|e| ccap::CcapError::FileOperationFailed(format!("Failed to create directory: {}", e)))?;
bindings/rust/examples/print_camera.rs (1)

9-9: Fix plural grammar in output message.

The message uses singular "device" even when multiple devices are found. Consider making it grammatically correct.

🔎 Proposed fix
     if !devices.is_empty() {
-        println!("## Found {} video capture device:", devices.len());
+        println!("## Found {} video capture device{}:", devices.len(), if devices.len() == 1 { "" } else { "s" });
         for (index, name) in devices.iter().enumerate() {
             println!("    {}: {}", index, name);
         }
bindings/rust/src/error.rs (2)

99-99: Empty strings in error conversion lose context information.

When converting from integer error codes, InvalidDevice and InvalidParameter are created with empty strings, losing any context about what device or parameter failed. Consider preserving the error code or adding a helper method that accepts the context string.

This is acceptable for a From<i32> impl since the integer code contains no string context. However, you might want to add a separate constructor:

impl CcapError {
    pub fn invalid_device_with_code(code: i32) -> Self {
        CcapError::InvalidDevice(format!("Error code: {}", code))
    }
    
    pub fn invalid_parameter_with_code(code: i32) -> Self {
        CcapError::InvalidParameter(format!("Error code: {}", code))
    }
}

Also applies to: 107-107


6-7: Consider whether CcapError::None variant is necessary.

Having a None variant in an error enum is unusual in Rust - typically success is represented by Ok(()) rather than Ok(CcapError::None). This might be mirroring the C API's error codes, but could lead to confusion.

If this variant is never actually used in practice, consider removing it. If it's required for C FFI compatibility, add a doc comment explaining why it exists:

/// No error occurred
/// 
/// Note: This variant exists for compatibility with the C API's error code system,
/// but should typically not appear in Rust code as success is represented by Ok(()).
None,
bindings/rust/src/utils.rs (2)

51-56: Misleading function name and documentation.

The docstring says "Save frame as BMP file" but this delegates to dump_frame_to_file, which uses "automatic format detection" according to its own docs. This creates confusion about the actual output format.

Consider either:

  1. Renaming to reflect actual behavior, or
  2. Implementing actual BMP-specific logic if the intent is BMP output

197-206: Handle I/O errors gracefully and avoid silent fallback to valid index.

Two issues in this interactive selection:

  1. Line 199: unwrap() on flush() will panic on I/O errors
  2. Lines 205-206: unwrap_or(0) silently falls back to index 0 on parse failure, but 0 is a valid device index. Users may not realize their input was invalid.
🔎 Proposed fix
-        io::stdout().flush().unwrap();
+        io::stdout().flush()
+            .map_err(|e| CcapError::InvalidParameter(format!("Failed to flush stdout: {}", e)))?;
         
         let mut input = String::new();
         io::stdin().read_line(&mut input)
             .map_err(|e| CcapError::InvalidParameter(format!("Failed to read input: {}", e)))?;
         
-        let selected_index = input.trim().parse::<usize>()
-            .unwrap_or(0);
+        let selected_index = input.trim().parse::<usize>()
+            .map_err(|e| CcapError::InvalidParameter(format!("Invalid device index: {}", e)))?;
bindings/rust/src/async.rs (1)

88-95: Panic on second call and redundant feature gate.

  1. Line 88: The #[cfg(feature = "async")] is redundant since this is already inside a #[cfg(feature = "async")] impl block
  2. Lines 91-92: expect() will panic if frame_stream() is called twice. Consider returning Option<impl Stream> or Result instead.
🔎 Proposed fix
-    #[cfg(feature = "async")]
     /// Create a stream of frames
-    pub fn frame_stream(&mut self) -> impl futures::Stream<Item = VideoFrame> {
-        let receiver = self.frame_receiver.take()
-            .expect("Frame stream can only be created once");
-        
-        tokio_stream::wrappers::UnboundedReceiverStream::new(receiver)
+    pub fn frame_stream(&mut self) -> Option<impl futures::Stream<Item = VideoFrame>> {
+        self.frame_receiver.take()
+            .map(tokio_stream::wrappers::UnboundedReceiverStream::new)
     }
bindings/rust/src/types.rs (1)

86-104: Prefer impl From<PixelFormat> for sys::CcapPixelFormat over impl Into.

The Rust API guidelines recommend implementing From rather than Into, as From automatically provides an Into implementation. This is also what Clippy warns about with clippy::from_over_into.

🔎 Proposed fix
-impl Into<sys::CcapPixelFormat> for PixelFormat {
-    fn into(self) -> sys::CcapPixelFormat {
+impl From<PixelFormat> for sys::CcapPixelFormat {
+    fn from(format: PixelFormat) -> Self {
-        match self {
+        match format {
             PixelFormat::Unknown => sys::CcapPixelFormat_CCAP_PIXEL_FORMAT_UNKNOWN,
             // ... rest unchanged
         }
     }
 }
bindings/rust/src/convert.rs (2)

66-88: Consider validating source buffer size and preventing overflow.

  1. No validation that src_data.len() is sufficient for the given dimensions. For YUYV422, expected size is width * height * 2. Passing undersized buffers to C could cause buffer overreads.

  2. width * height * 3 could theoretically overflow for very large dimensions (e.g., 37000×37000), though this is unlikely in practice for camera frames.

🔎 Proposed validation example
     pub fn yuyv422_to_rgb24(
         src_data: &[u8],
         width: u32,
         height: u32,
     ) -> Result<Vec<u8>> {
+        let expected_src_size = (width as usize)
+            .checked_mul(height as usize)
+            .and_then(|v| v.checked_mul(2))
+            .ok_or(CcapError::InvalidParameter("dimensions overflow".to_string()))?;
+        
+        if src_data.len() < expected_src_size {
+            return Err(CcapError::InvalidParameter(
+                format!("source buffer too small: {} < {}", src_data.len(), expected_src_size)
+            ));
+        }
+
         let dst_size = (width * height * 3) as usize;

5-5: Unused import.

CString is imported but never used in this file (only CStr is used).

🔎 Proposed fix
-use std::ffi::{CStr, CString};
+use std::ffi::CStr;
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between e721398 and f9c64ad.

⛔ Files ignored due to path filters (1)
  • bindings/rust/Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (23)
  • .github/workflows/rust.yml
  • .vscode/tasks.json
  • CMakeLists.txt
  • bindings/rust/.gitignore
  • bindings/rust/Cargo.toml
  • bindings/rust/README.md
  • bindings/rust/RUST_INTEGRATION_SUMMARY.md
  • bindings/rust/build.rs
  • bindings/rust/build_and_test.sh
  • bindings/rust/examples/capture_callback.rs
  • bindings/rust/examples/capture_grab.rs
  • bindings/rust/examples/minimal_example.rs
  • bindings/rust/examples/print_camera.rs
  • bindings/rust/src/async.rs
  • bindings/rust/src/convert.rs
  • bindings/rust/src/error.rs
  • bindings/rust/src/frame.rs
  • bindings/rust/src/lib.rs
  • bindings/rust/src/provider.rs
  • bindings/rust/src/types.rs
  • bindings/rust/src/utils.rs
  • bindings/rust/tests/integration_tests.rs
  • bindings/rust/wrapper.h
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-12-27T19:15:25.440Z
Learnt from: CR
Repo: wysaid/CameraCapture PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-12-27T19:15:25.440Z
Learning: Applies to dev.cmake : `dev.cmake` in project root (git-ignored) overrides default CMake options. Remove it for standard builds/tests; keep only when verifying custom settings

Applied to files:

  • CMakeLists.txt
🧬 Code graph analysis (12)
bindings/rust/src/error.rs (1)
bindings/rust/src/types.rs (4)
  • from (35-52)
  • from (116-122)
  • from (150-159)
  • from (208-213)
bindings/rust/examples/minimal_example.rs (3)
bindings/rust/src/provider.rs (3)
  • set_error_callback (309-348)
  • new (17-27)
  • pixel_format (298-301)
bindings/rust/src/utils.rs (1)
  • select_camera (182-215)
bindings/rust/src/frame.rs (5)
  • index (143-145)
  • width (123-125)
  • height (128-130)
  • data_size (138-140)
  • pixel_format (133-135)
bindings/rust/src/async.rs (1)
bindings/rust/src/provider.rs (8)
  • new (17-27)
  • find_device_names (286-288)
  • open (141-153)
  • start (176-178)
  • stop (181-183)
  • is_opened (186-188)
  • is_started (171-173)
  • grab_frame (232-243)
bindings/rust/src/utils.rs (4)
bindings/rust/src/provider.rs (1)
  • new (17-27)
bindings/rust/src/async.rs (1)
  • new (25-34)
include/ccap_utils.h (1)
  • LogLevel (80-140)
bindings/rust/src/types.rs (3)
  • to_c_enum (57-59)
  • to_c_enum (144-146)
  • to_c_enum (177-184)
bindings/rust/examples/print_camera.rs (2)
bindings/rust/src/provider.rs (3)
  • new (17-27)
  • with_device_name (43-56)
  • device_info (166-168)
bindings/rust/src/utils.rs (1)
  • set_log_level (218-222)
bindings/rust/src/lib.rs (2)
include/ccap_utils.h (1)
  • LogLevel (80-140)
bindings/rust/src/provider.rs (1)
  • version (266-277)
bindings/rust/src/convert.rs (2)
bindings/rust/src/types.rs (2)
  • from_c_enum (62-64)
  • from_c_enum (187-195)
bindings/rust/src/frame.rs (3)
  • from_c_ptr (49-51)
  • width (123-125)
  • height (128-130)
bindings/rust/build.rs (3)
bindings/rust/src/error.rs (1)
  • from (92-112)
bindings/rust/src/types.rs (4)
  • from (35-52)
  • from (116-122)
  • from (150-159)
  • from (208-213)
bindings/rust/src/provider.rs (1)
  • new (17-27)
bindings/rust/examples/capture_grab.rs (6)
bindings/rust/examples/capture_callback.rs (1)
  • main (6-100)
bindings/rust/examples/minimal_example.rs (1)
  • main (3-46)
bindings/rust/examples/print_camera.rs (1)
  • main (56-74)
bindings/rust/src/utils.rs (2)
  • set_log_level (218-222)
  • dump_frame_to_directory (102-142)
bindings/rust/src/provider.rs (2)
  • set_error_callback (309-348)
  • new (17-27)
bindings/rust/src/frame.rs (2)
  • width (123-125)
  • height (128-130)
bindings/rust/src/frame.rs (3)
include/ccap_def.h (1)
  • DeviceInfo (372-389)
bindings/rust/src/types.rs (6)
  • from_c_enum (62-64)
  • from_c_enum (187-195)
  • from (35-52)
  • from (116-122)
  • from (150-159)
  • from (208-213)
bindings/rust/src/error.rs (1)
  • from (92-112)
bindings/rust/examples/capture_callback.rs (3)
bindings/rust/src/utils.rs (1)
  • dump_frame_to_directory (102-142)
bindings/rust/src/provider.rs (3)
  • set_error_callback (309-348)
  • new (17-27)
  • with_device (30-40)
bindings/rust/src/frame.rs (4)
  • index (143-145)
  • width (123-125)
  • height (128-130)
  • data_size (138-140)
bindings/rust/src/provider.rs (4)
include/ccap_def.h (1)
  • DeviceInfo (372-389)
bindings/rust/src/types.rs (4)
  • from (35-52)
  • from (116-122)
  • from (150-159)
  • from (208-213)
bindings/rust/src/frame.rs (4)
  • width (123-125)
  • height (128-130)
  • from_c_ptr (49-51)
  • from_c_ptr_ref (54-56)
bindings/rust/src/lib.rs (1)
  • version (38-40)
🪛 actionlint (1.7.9)
.github/workflows/rust.yml

35-35: shellcheck reported issue in this script: SC2046:warning:4:8: Quote this to prevent word splitting

(shellcheck)


99-99: shellcheck reported issue in this script: SC2046:warning:4:8: Quote this to prevent word splitting

(shellcheck)

🪛 GitHub Actions: Rust CI
bindings/rust/examples/minimal_example.rs

[warning] 1-1: Formatted error callback logging blocks for consistency.

bindings/rust/src/async.rs

[warning] 1-1: Code formatting updates: added/adjusted comments and reordered imports for async module.


[warning] 1-1: Async feature code formatting: introduced explicit braces and alignment in spawn_blocking usage.


[warning] 1-1: Updated frame_stream creation to align with new receiver extraction style.

bindings/rust/examples/print_camera.rs

[warning] 1-1: Code style changes: adjusted spacing and formatting in provider/device info output paths.

bindings/rust/src/convert.rs

[warning] 1-1: File unchanged semantics; diff indicates reformatting only.

bindings/rust/build.rs

[warning] 1-1: Formatting/style changes detected in rust build script (diffs introduced by CI).


[warning] 1-1: Build profile string formatting expanded to multi-line for readability.


[warning] 1-1: Adjusted cargo:rustc-link-search logic to print multiple lines with explicit build_type.


[warning] 1-1: Expanded platform-specific linking blocks with consistent formatting.


[warning] 1-1: Added re-run triggers for header/include changes in build script.

bindings/rust/examples/capture_grab.rs

[warning] 1-1: Formatted Camera error logging and provider setup calls; minor style adjustments.

bindings/rust/examples/capture_callback.rs

[warning] 1-1: Code style changes: formatted eprintln! calls across error callback blocks.

🪛 GitHub Check: Build and Test
bindings/rust/build.rs

[warning] 66-66:
Diff in /home/runner/work/CameraCapture/CameraCapture/bindings/rust/build.rs


[warning] 52-52:
Diff in /home/runner/work/CameraCapture/CameraCapture/bindings/rust/build.rs


[warning] 45-45:
Diff in /home/runner/work/CameraCapture/CameraCapture/bindings/rust/build.rs


[warning] 38-38:
Diff in /home/runner/work/CameraCapture/CameraCapture/bindings/rust/build.rs


[warning] 31-31:
Diff in /home/runner/work/CameraCapture/CameraCapture/bindings/rust/build.rs


[warning] 16-16:
Diff in /home/runner/work/CameraCapture/CameraCapture/bindings/rust/build.rs


[warning] 6-6:
Diff in /home/runner/work/CameraCapture/CameraCapture/bindings/rust/build.rs

bindings/rust/examples/capture_callback.rs

[warning] 29-29:
Diff in /home/runner/work/CameraCapture/CameraCapture/bindings/rust/examples/capture_callback.rs


[warning] 9-9:
Diff in /home/runner/work/CameraCapture/CameraCapture/bindings/rust/examples/capture_callback.rs


[warning] 1-1:
Diff in /home/runner/work/CameraCapture/CameraCapture/bindings/rust/examples/capture_callback.rs

🪛 GitHub Check: CodeQL
.github/workflows/rust.yml

[warning] 16-65: Workflow does not contain permissions
Actions job or workflow does not limit the permissions of the GITHUB_TOKEN. Consider setting an explicit permissions block, using the following as a minimal starting point: {{contents: read}}


[warning] 66-119: Workflow does not contain permissions
Actions job or workflow does not limit the permissions of the GITHUB_TOKEN. Consider setting an explicit permissions block, using the following as a minimal starting point: {{contents: read}}

🪛 LanguageTool
bindings/rust/README.md

[style] ~180-~180: Consider using a less common alternative to make your writing sound more unique and professional.
Context: ...ontributing Contributions are welcome! Please feel free to submit a Pull Request. ## License Thi...

(FEEL_FREE_TO_STYLE_ME)

🪛 markdownlint-cli2 (0.18.1)
bindings/rust/RUST_INTEGRATION_SUMMARY.md

29-29: Fenced code blocks should have a language specified

(MD040, fenced-code-language)

🪛 Shellcheck (0.11.0)
bindings/rust/build_and_test.sh

[warning] 29-29: Quote this to prevent word splitting.

(SC2046)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (20)
  • GitHub Check: macOS Build (Release-static)
  • GitHub Check: Build Fedora (Debug-shared)
  • GitHub Check: Build (Release-gcc-shared)
  • GitHub Check: Build (Debug-gcc-shared)
  • GitHub Check: Build Fedora (Debug-static)
  • GitHub Check: Build Fedora (Release-static)
  • GitHub Check: Build (Release-gcc-static)
  • GitHub Check: Build ARM64 (Debug-gcc-shared)
  • GitHub Check: Build ARM64 (Release-gcc-shared)
  • GitHub Check: Build (Debug-clang-shared)
  • GitHub Check: Build ARM64 (Debug-gcc-static)
  • GitHub Check: Build (Release-clang-static)
  • GitHub Check: Build (Debug-clang-static)
  • GitHub Check: Windows Build VS2022 (Release-shared)
  • GitHub Check: Windows Build MinGW (Debug-static)
  • GitHub Check: Windows Build VS2022 (Release-static)
  • GitHub Check: Windows Build MinGW (Release-shared)
  • GitHub Check: Windows Build VS2022 (Debug-shared)
  • GitHub Check: Windows Build MinGW (Debug-shared)
  • GitHub Check: Windows Build MinGW (Release-static)
🔇 Additional comments (12)
bindings/rust/.gitignore (1)

1-2: LGTM!

The ignore patterns appropriately exclude Rust build artifacts (target/) and example output (image_capture/).

bindings/rust/tests/integration_tests.rs (2)

7-12: LGTM!

The test correctly verifies that a newly created provider is not opened, which aligns with the expected behavior shown in the code snippet from provider.rs:16-26.


35-41: LGTM!

The round-trip conversion test for PixelFormat ensures type safety between Rust and C enums, which is critical for FFI correctness.

bindings/rust/wrapper.h (1)

1-3: Header files are properly configured and accessible.

All three included headers exist in the include directory and are correctly referenced in the build.rs configuration with proper include path settings and include guards in place.

bindings/rust/build.rs (1)

63-66: Deriving Default on opaque handle types is not a practical risk given the API design.

The C headers define CcapProvider and CcapVideoFrame as forward-declared opaque types. Bindgen does generate Default derives for these, which is technically unsafe since a default/null pointer would be invalid. However, the Rust codebase mitigates this risk:

  • The public API wraps opaque types in safe wrappers (Provider struct) that never use .default()
  • The low-level sys module containing raw opaque types is marked for advanced users only
  • Concrete types that actually call .default() in the code (CcapDeviceNamesList, CcapDeviceInfo) are safe data containers

No action required; the current design prevents unsafe usage of Default on opaque handles.

bindings/rust/src/lib.rs (1)

1-40: LGTM!

The crate structure is well-organized with appropriate module separation, feature-gated async support, and clear public API surface. The #![warn(missing_docs)] and #![warn(rust_2018_idioms)] lints are good hygiene for a library crate.

bindings/rust/src/utils.rs (1)

225-251: LGTM!

The LogLevel enum properly mirrors the C API levels and the to_c_enum conversion is correct.

bindings/rust/src/async.rs (1)

72-86: LGTM!

The grab_frame_timeout correctly uses spawn_blocking to offload the blocking FFI call, and blocking_lock() is the right choice inside spawn_blocking. The timeout-to-millis conversion and error propagation are handled properly.

bindings/rust/src/types.rs (1)

162-196: LGTM!

The ColorConversionBackend enum correctly maps to and from C API values. The fallback to Cpu for unknown values is a reasonable defensive choice for FFI code.

bindings/rust/src/convert.rs (1)

10-47: LGTM!

The backend management methods (backend(), set_backend(), backend_name(), is_backend_available()) are well-implemented with proper error handling and C string conversion.

bindings/rust/src/provider.rs (2)

376-419: LGTM with memory leak caveat.

The set_new_frame_callback implementation correctly handles the callback type through Box::into_raw and reconstruction. The memory leak is acceptable for callback registration that lives for the provider's lifetime, though adding Drop cleanup or a dedicated unregister mechanism would be ideal for long-running applications.


439-448: LGTM!

The Drop implementation correctly releases the C handle and nullifies the pointer to prevent double-free.

Comment on lines 158 to 160
// Make VideoFrame Send + Sync if the underlying C library supports it
unsafe impl Send for VideoFrame {}
unsafe impl Sync for VideoFrame {}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

cd bindings/rust && head -200 src/frame.rs | tail -50

Repository: wysaid/CameraCapture

Length of output: 1445


🏁 Script executed:

cat -n bindings/rust/src/frame.rs | sed -n '1,50p'

Repository: wysaid/CameraCapture

Length of output: 1945


🏁 Script executed:

# Check what C library this binding is for
git ls-files | grep -E "(Cargo.toml|build\.rs)" | head -5

Repository: wysaid/CameraCapture

Length of output: 111


🌐 Web query:

ccap camera capture library thread safety documentation

💡 Result:

I found the library's primary docs on its GitHub repo (ccap / CameraCapture). The README + API reference describe Provider/VideoFrame APIs (provider.open/start/stop/grab, new-frame callbacks, C API equivalents) but do not include an explicit "thread-safety" section or per-function thread-safety guarantees. See the repo README/API reference for the details to inspect (Provider::grab, setNewFrameCallback, ccap_provider_grab, etc.). [1]

If you need definitive thread-safety guarantees I recommend:

  • Inspecting the relevant headers/source for locking or std::mutex usage in Provider and C API implementations, and
  • Opening an issue or asking the maintainer on the repo for explicit thread-safety guidance.

Source: GitHub — wysaid/CameraCapture (ccap) README/API. [1]

References
[1] wysaid/CameraCapture — README & API (ccap).


🏁 Script executed:

cat bindings/rust/Cargo.toml

Repository: wysaid/CameraCapture

Length of output: 1198


🏁 Script executed:

# Search for other unsafe impl Send/Sync patterns and safety documentation
rg "unsafe impl (Send|Sync)" bindings/rust/src/ -A 2

Repository: wysaid/CameraCapture

Length of output: 424


🏁 Script executed:

# Check if there are any safety comments or documentation about thread safety
rg -i "thread|safe|send|sync" bindings/rust/src/frame.rs -B 2 -A 2

Repository: wysaid/CameraCapture

Length of output: 1869


🏁 Script executed:

# Look at the C library source to understand frame lifecycle
cat bindings/rust/build.rs

Repository: wysaid/CameraCapture

Length of output: 3011


Verify and document thread-safety assumptions for Send+Sync implementations.

The unsafe impl Send + Sync for VideoFrame assumes the underlying C library is thread-safe, but the ccap library's public documentation does not include explicit thread-safety guarantees. VideoFrame holds a raw *mut CcapVideoFrame pointer, which requires verified thread-safety to safely implement these traits. Either provide concrete evidence of the C library's thread-safety (with a detailed safety comment citing the source), or make these trait bounds conditional on a feature flag until thread-safety is confirmed with the library maintainers. The same issue applies to unsafe impl Send for Provider.

🤖 Prompt for AI Agents
In bindings/rust/src/frame.rs around lines 158-160, the unsafe impls making
VideoFrame Send+Sync (and similarly Provider) assume the underlying C ccap
library is thread-safe but lack justification; either add a detailed safety
comment above each impl documenting concrete evidence (link to ccap docs or
maintainer statement, describe invariants that make raw *mut CcapVideoFrame safe
across threads) and explain why no interior mutability/races occur, OR
remove/guard the impls behind a feature flag (e.g., "ccap_threadsafe") and add a
clear TODO to enable only after confirmation from library maintainers; update
Cargo.toml features accordingly and ensure tests/builds reflect the conditional
implementation.

Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR introduces comprehensive Rust bindings for the CameraCapture library, providing a memory-safe, high-performance interface with optional async support. The implementation wraps the underlying C API with idiomatic Rust patterns including RAII resource management, Result-based error handling, and zero-copy data access.

Key changes:

  • Complete Rust FFI bindings with safe wrappers for camera capture operations
  • Hardware-accelerated pixel format conversion support (AVX2, NEON, Apple Accelerate)
  • Optional async/await interface using tokio
  • Cross-platform build configuration integrated with CMake

Reviewed changes

Copilot reviewed 23 out of 24 changed files in this pull request and generated 23 comments.

Show a summary per file
File Description
bindings/rust/src/lib.rs Main library entry point with module organization and public API exports
bindings/rust/src/error.rs Error type definitions and conversions from C error codes
bindings/rust/src/types.rs Type-safe Rust enums for pixel formats, properties, and backend selection
bindings/rust/src/frame.rs VideoFrame wrapper with lifetime management and data access methods
bindings/rust/src/provider.rs Main Provider API for camera control and frame capture
bindings/rust/src/utils.rs Utility functions for logging, file I/O, and device selection
bindings/rust/src/convert.rs Pixel format conversion functions leveraging SIMD acceleration
bindings/rust/src/async.rs Tokio-based async wrapper for non-blocking camera operations
bindings/rust/build.rs Build script for generating FFI bindings via bindgen
bindings/rust/Cargo.toml Package manifest with dependencies and feature flags
bindings/rust/tests/integration_tests.rs Integration tests for core API functionality
bindings/rust/examples/*.rs Example programs demonstrating various usage patterns
bindings/rust/README.md Documentation with usage examples and API reference
bindings/rust/build_and_test.sh Build automation script for C library and Rust bindings
CMakeLists.txt CMake integration for optional Rust bindings build
.github/workflows/rust.yml CI workflow for cross-platform testing

use crate::types::PixelFormat;
use crate::frame::VideoFrame;
use crate::sys;
use std::ffi::{ CString};
Copy link

Copilot AI Dec 27, 2025

Choose a reason for hiding this comment

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

Unused import: CString is imported but never used in this file. Consider removing it to clean up the imports.

Suggested change
use std::ffi::{ CString};

Copilot uses AI. Check for mistakes.
Comment on lines 377 to 610
pub fn set_new_frame_callback<F>(&mut self, callback: F) -> Result<()>
where
F: Fn(&VideoFrame) -> bool + Send + Sync + 'static,
{
use std::os::raw::c_void;

unsafe extern "C" fn new_frame_callback_wrapper(
frame: *const sys::CcapVideoFrame,
user_data: *mut c_void,
) -> bool {
if user_data.is_null() || frame.is_null() {
return false;
}

let callback = &*(user_data as *const Box<dyn Fn(&VideoFrame) -> bool + Send + Sync>);

// Create a temporary VideoFrame wrapper that doesn't own the frame
let video_frame = VideoFrame::from_c_ptr_ref(frame as *mut sys::CcapVideoFrame);
callback(&video_frame)
}

// Store the callback to prevent it from being dropped
let callback_box = Box::new(callback);
let callback_ptr = Box::into_raw(callback_box);

let success = unsafe {
sys::ccap_provider_set_new_frame_callback(
self.handle,
Some(new_frame_callback_wrapper),
callback_ptr as *mut c_void,
)
};

if success {
Ok(())
} else {
// Clean up on failure
unsafe {
let _ = Box::from_raw(callback_ptr);
}
Err(CcapError::CaptureStartFailed)
}
}
Copy link

Copilot AI Dec 27, 2025

Choose a reason for hiding this comment

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

Memory leak: The callback pointer is converted to a raw pointer and never freed. When set_new_frame_callback is called multiple times or when the Provider is dropped, the previous callback is leaked. Store the callback pointer in the Provider struct and free it in the Drop implementation or when a new callback is set.

Copilot uses AI. Check for mistakes.
if success {
Ok(())
} else {
Err(CcapError::CaptureStartFailed)
Copy link

Copilot AI Dec 27, 2025

Choose a reason for hiding this comment

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

Incorrect error handling: When remove_new_frame_callback fails, it returns CcapError::CaptureStartFailed which is semantically incorrect. This should return a more appropriate error like CaptureStopFailed or a new error variant specific to callback removal.

Suggested change
Err(CcapError::CaptureStartFailed)
Err(CcapError::CaptureStopFailed)

Copilot uses AI. Check for mistakes.
Comment on lines 1 to 356
use crate::error::{CcapError, Result};
use crate::types::{PixelFormat, ColorConversionBackend};
use crate::frame::VideoFrame;
use crate::sys;
use std::ffi::{CStr, CString};

/// Color conversion utilities
pub struct Convert;

impl Convert {
/// Get current color conversion backend
pub fn backend() -> ColorConversionBackend {
let backend = unsafe { sys::ccap_convert_get_backend() };
ColorConversionBackend::from_c_enum(backend)
}

/// Set color conversion backend
pub fn set_backend(backend: ColorConversionBackend) -> Result<()> {
let success = unsafe {
sys::ccap_convert_set_backend(backend.to_c_enum())
};

if success {
Ok(())
} else {
Err(CcapError::BackendSetFailed)
}
}

/// Get backend name as string
pub fn backend_name() -> Result<String> {
let backend_ptr = unsafe { sys::ccap_convert_get_backend_name() };
if backend_ptr.is_null() {
return Err(CcapError::InternalError("Failed to get backend name".to_string()));
}

let backend_cstr = unsafe { CStr::from_ptr(backend_ptr) };
backend_cstr
.to_str()
.map(|s| s.to_string())
.map_err(|_| CcapError::StringConversionError("Invalid backend name".to_string()))
}

/// Check if backend is available
pub fn is_backend_available(backend: ColorConversionBackend) -> bool {
unsafe { sys::ccap_convert_is_backend_available(backend.to_c_enum()) }
}

/// Convert frame to different pixel format
pub fn convert_frame(
src_frame: &VideoFrame,
dst_format: PixelFormat,
) -> Result<VideoFrame> {
let dst_ptr = unsafe {
sys::ccap_convert_frame(src_frame.as_c_ptr(), dst_format.to_c_enum())
};

if dst_ptr.is_null() {
Err(CcapError::ConversionFailed)
} else {
Ok(VideoFrame::from_c_ptr(dst_ptr))
}
}

/// Convert YUYV422 to RGB24
pub fn yuyv422_to_rgb24(
src_data: &[u8],
width: u32,
height: u32,
) -> Result<Vec<u8>> {
let dst_size = (width * height * 3) as usize;
let mut dst_data = vec![0u8; dst_size];

let success = unsafe {
sys::ccap_convert_yuyv422_to_rgb24(
src_data.as_ptr(),
dst_data.as_mut_ptr(),
width,
height,
)
};

if success {
Ok(dst_data)
} else {
Err(CcapError::ConversionFailed)
}
}

/// Convert YUYV422 to BGR24
pub fn yuyv422_to_bgr24(
src_data: &[u8],
width: u32,
height: u32,
) -> Result<Vec<u8>> {
let dst_size = (width * height * 3) as usize;
let mut dst_data = vec![0u8; dst_size];

let success = unsafe {
sys::ccap_convert_yuyv422_to_bgr24(
src_data.as_ptr(),
dst_data.as_mut_ptr(),
width,
height,
)
};

if success {
Ok(dst_data)
} else {
Err(CcapError::ConversionFailed)
}
}

/// Convert RGB24 to BGR24
pub fn rgb24_to_bgr24(
src_data: &[u8],
width: u32,
height: u32,
) -> Result<Vec<u8>> {
let dst_size = (width * height * 3) as usize;
let mut dst_data = vec![0u8; dst_size];

let success = unsafe {
sys::ccap_convert_rgb24_to_bgr24(
src_data.as_ptr(),
dst_data.as_mut_ptr(),
width,
height,
)
};

if success {
Ok(dst_data)
} else {
Err(CcapError::ConversionFailed)
}
}

/// Convert BGR24 to RGB24
pub fn bgr24_to_rgb24(
src_data: &[u8],
width: u32,
height: u32,
) -> Result<Vec<u8>> {
let dst_size = (width * height * 3) as usize;
let mut dst_data = vec![0u8; dst_size];

let success = unsafe {
sys::ccap_convert_bgr24_to_rgb24(
src_data.as_ptr(),
dst_data.as_mut_ptr(),
width,
height,
)
};

if success {
Ok(dst_data)
} else {
Err(CcapError::ConversionFailed)
}
}

/// Convert MJPEG to RGB24
pub fn mjpeg_to_rgb24(
src_data: &[u8],
width: u32,
height: u32,
) -> Result<Vec<u8>> {
let dst_size = (width * height * 3) as usize;
let mut dst_data = vec![0u8; dst_size];

let success = unsafe {
sys::ccap_convert_mjpeg_to_rgb24(
src_data.as_ptr(),
src_data.len(),
dst_data.as_mut_ptr(),
width,
height,
)
};

if success {
Ok(dst_data)
} else {
Err(CcapError::ConversionFailed)
}
}

/// Convert MJPEG to BGR24
pub fn mjpeg_to_bgr24(
src_data: &[u8],
width: u32,
height: u32,
) -> Result<Vec<u8>> {
let dst_size = (width * height * 3) as usize;
let mut dst_data = vec![0u8; dst_size];

let success = unsafe {
sys::ccap_convert_mjpeg_to_bgr24(
src_data.as_ptr(),
src_data.len(),
dst_data.as_mut_ptr(),
width,
height,
)
};

if success {
Ok(dst_data)
} else {
Err(CcapError::ConversionFailed)
}
}

/// Convert NV12 to RGB24
pub fn nv12_to_rgb24(
y_data: &[u8],
uv_data: &[u8],
width: u32,
height: u32,
) -> Result<Vec<u8>> {
let dst_size = (width * height * 3) as usize;
let mut dst_data = vec![0u8; dst_size];

let success = unsafe {
sys::ccap_convert_nv12_to_rgb24(
y_data.as_ptr(),
uv_data.as_ptr(),
dst_data.as_mut_ptr(),
width,
height,
)
};

if success {
Ok(dst_data)
} else {
Err(CcapError::ConversionFailed)
}
}

/// Convert NV12 to BGR24
pub fn nv12_to_bgr24(
y_data: &[u8],
uv_data: &[u8],
width: u32,
height: u32,
) -> Result<Vec<u8>> {
let dst_size = (width * height * 3) as usize;
let mut dst_data = vec![0u8; dst_size];

let success = unsafe {
sys::ccap_convert_nv12_to_bgr24(
y_data.as_ptr(),
uv_data.as_ptr(),
dst_data.as_mut_ptr(),
width,
height,
)
};

if success {
Ok(dst_data)
} else {
Err(CcapError::ConversionFailed)
}
}

/// Convert YV12 to RGB24
pub fn yv12_to_rgb24(
y_data: &[u8],
u_data: &[u8],
v_data: &[u8],
width: u32,
height: u32,
) -> Result<Vec<u8>> {
let dst_size = (width * height * 3) as usize;
let mut dst_data = vec![0u8; dst_size];

let success = unsafe {
sys::ccap_convert_yv12_to_rgb24(
y_data.as_ptr(),
u_data.as_ptr(),
v_data.as_ptr(),
dst_data.as_mut_ptr(),
width,
height,
)
};

if success {
Ok(dst_data)
} else {
Err(CcapError::ConversionFailed)
}
}

/// Convert YV12 to BGR24
pub fn yv12_to_bgr24(
y_data: &[u8],
u_data: &[u8],
v_data: &[u8],
width: u32,
height: u32,
) -> Result<Vec<u8>> {
let dst_size = (width * height * 3) as usize;
let mut dst_data = vec![0u8; dst_size];

let success = unsafe {
sys::ccap_convert_yv12_to_bgr24(
y_data.as_ptr(),
u_data.as_ptr(),
v_data.as_ptr(),
dst_data.as_mut_ptr(),
width,
height,
)
};

if success {
Ok(dst_data)
} else {
Err(CcapError::ConversionFailed)
}
}
}
Copy link

Copilot AI Dec 27, 2025

Choose a reason for hiding this comment

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

Missing test coverage: The Convert struct and all its conversion functions lack test coverage. Given that this is a core feature of the library (hardware-accelerated conversions), tests should be added to verify: 1) Backend selection works correctly, 2) Conversion functions produce valid output, 3) Error cases are handled properly.

Copilot uses AI. Check for mistakes.
println!("Camera opened successfully!");

// Capture a frame
if let Some(frame) = provider.grab_frame_blocking()? {
Copy link

Copilot AI Dec 27, 2025

Choose a reason for hiding this comment

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

Inconsistent API naming: The method is named grab_frame_blocking in the README example, but the actual method in Provider is grab_frame. Either update the README to use the correct method name or add a grab_frame_blocking alias for consistency with the async API.

Suggested change
if let Some(frame) = provider.grab_frame_blocking()? {
if let Some(frame) = provider.grab_frame()? {

Copilot uses AI. Check for mistakes.

/// Set pixel format
pub fn set_pixel_format(&mut self, format: PixelFormat) -> Result<()> {
self.set_property(PropertyName::PixelFormatOutput, format as u32 as f64)
Copy link

Copilot AI Dec 27, 2025

Choose a reason for hiding this comment

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

Incorrect type conversion: Using format as u32 as f64 to convert PixelFormat enum to a property value is incorrect. The enum doesn't derive Copy or have a guaranteed integer representation. Use format.to_c_enum() as u32 as f64 instead.

Suggested change
self.set_property(PropertyName::PixelFormatOutput, format as u32 as f64)
self.set_property(
PropertyName::PixelFormatOutput,
format.to_c_enum() as u32 as f64,
)

Copilot uses AI. Check for mistakes.
/// Get current pixel format (convenience getter)
pub fn pixel_format(&self) -> Result<PixelFormat> {
let format_val = self.get_property(PropertyName::PixelFormatOutput)? as u32;
Ok(PixelFormat::from(format_val))
Copy link

Copilot AI Dec 27, 2025

Choose a reason for hiding this comment

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

Incorrect type conversion: Converting format_val as u32 to PixelFormat using PixelFormat::from() is incorrect. The From implementation expects sys::CcapPixelFormat, not u32. This will cause a compilation error. Use PixelFormat::from_c_enum(format_val as sys::CcapPixelFormat) instead.

Suggested change
Ok(PixelFormat::from(format_val))
Ok(PixelFormat::from_c_enum(format_val as sys::CcapPixelFormat))

Copilot uses AI. Check for mistakes.
```

## Building

Copy link

Copilot AI Dec 27, 2025

Choose a reason for hiding this comment

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

Incorrect API usage in documentation: The method provider.set_resolution() is called with a Resolution struct, but the actual implementation expects two separate u32 parameters: set_resolution(width: u32, height: u32). Update the example to use provider.set_resolution(640, 480).

Copilot uses AI. Check for mistakes.
pub struct AsyncProvider {
provider: Arc<Mutex<SyncProvider>>,
frame_receiver: Option<mpsc::UnboundedReceiver<VideoFrame>>,
_frame_sender: mpsc::UnboundedSender<VideoFrame>,
Copy link

Copilot AI Dec 27, 2025

Choose a reason for hiding this comment

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

Redundant field in AsyncProvider: The _frame_sender field is stored but never used. The sender is only used to keep the channel alive, but since the receiver is stored and owns the channel, the sender can be dropped. Consider removing this field or document why it needs to be kept.

Copilot uses AI. Check for mistakes.
Comment on lines 205 to 206
let selected_index = input.trim().parse::<usize>()
.unwrap_or(0);
Copy link

Copilot AI Dec 27, 2025

Choose a reason for hiding this comment

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

Unreachable code in error handling: The unwrap_or(0) will never be executed because parse() returns a Result, and if parsing fails, the error is not propagated - it just defaults to 0. However, the subsequent bounds check makes this safe. Consider using unwrap_or(0) or unwrap_or_else(|_| 0) for clarity, or better yet, handle the error explicitly with proper error messaging.

Suggested change
let selected_index = input.trim().parse::<usize>()
.unwrap_or(0);
let selected_index = match input.trim().parse::<usize>() {
Ok(index) => index,
Err(_) => {
println!("Invalid input, using the first device: {}", devices[0]);
0
}
};

Copilot uses AI. Check for mistakes.
Copy link

@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: 3

🧹 Nitpick comments (5)
bindings/rust/Cargo.toml (3)

17-17: Consider narrowing tokio features for a lighter footprint.

The "full" feature set includes I/O drivers, filesystem, process, signal handling, and other heavy components that a camera capture library likely doesn't need. For a library crate, consider specifying only the required features (e.g., ["rt", "sync", "macros"]) to reduce compile times and binary size for downstream users.

🔎 Proposed refinement
-tokio = { version = "1.0", features = ["full"], optional = true }
+tokio = { version = "1.0", features = ["rt", "sync", "macros"], optional = true }

16-19: Consider adding patch version constraints for stability.

Version specifications like "1.0" allow any 1.x release, which could introduce unexpected behavior changes. For better stability, consider specifying minimum patch versions (e.g., "1.0.0") or using more conservative ranges.


28-32: Consider enforcing mutual exclusivity between static-link and build-source.

The features static-link and build-source appear to be mutually exclusive by design (one for development, one for distribution), but nothing prevents both from being enabled. While build.rs checks CARGO_FEATURE_BUILD_SOURCE first and may handle this gracefully, explicit documentation or compile-time enforcement would clarify the intended usage pattern.

bindings/rust/build.rs (2)

59-60: C++17 requirement should be documented.

The build script requires C++17, but this minimum compiler version requirement isn't documented in the package metadata or README. Consider documenting the compiler requirements for users building from source.


79-81: Multiple library search paths could mask linking issues.

The build adds three search paths with fallbacks (specific build type, Debug, Release). While this provides flexibility during development, it could mask issues if the linker finds a library from an unexpected build configuration. Consider documenting this behavior or adding a warning when falling back to a different build type.

📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between f9c64ad and 2afc385.

📒 Files selected for processing (2)
  • bindings/rust/Cargo.toml
  • bindings/rust/build.rs
🧰 Additional context used
🪛 GitHub Actions: Rust CI
bindings/rust/build.rs

[error] 12-12: unused variable: is_packaged

🪛 GitHub Check: Build and Test
bindings/rust/build.rs

[warning] 97-97:
Diff in /home/runner/work/CameraCapture/CameraCapture/bindings/rust/build.rs


[warning] 79-79:
Diff in /home/runner/work/CameraCapture/CameraCapture/bindings/rust/build.rs


[warning] 73-73:
Diff in /home/runner/work/CameraCapture/CameraCapture/bindings/rust/build.rs


[warning] 65-65:
Diff in /home/runner/work/CameraCapture/CameraCapture/bindings/rust/build.rs


[warning] 52-52:
Diff in /home/runner/work/CameraCapture/CameraCapture/bindings/rust/build.rs


[warning] 47-47:
Diff in /home/runner/work/CameraCapture/CameraCapture/bindings/rust/build.rs


[warning] 40-40:
Diff in /home/runner/work/CameraCapture/CameraCapture/bindings/rust/build.rs


[warning] 24-24:
Diff in /home/runner/work/CameraCapture/CameraCapture/bindings/rust/build.rs


[warning] 12-12:
Diff in /home/runner/work/CameraCapture/CameraCapture/bindings/rust/build.rs


[warning] 5-5:
Diff in /home/runner/work/CameraCapture/CameraCapture/bindings/rust/build.rs

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (16)
  • GitHub Check: Build (Release-gcc-static)
  • GitHub Check: Build (Release-clang-shared)
  • GitHub Check: Build Fedora (Release-static)
  • GitHub Check: Build (Debug-clang-shared)
  • GitHub Check: Build Fedora (Debug-static)
  • GitHub Check: Build (Release-clang-static)
  • GitHub Check: Build Fedora (Release-shared)
  • GitHub Check: Build (Debug-clang-static)
  • GitHub Check: Build Fedora (Debug-shared)
  • GitHub Check: Build ARM64 (Release-gcc-static)
  • GitHub Check: Windows Build MinGW (Debug-static)
  • GitHub Check: Windows Build VS2022 (Release-static)
  • GitHub Check: Windows Build MinGW (Release-shared)
  • GitHub Check: Windows Build MinGW (Release-static)
  • GitHub Check: Windows Build VS2022 (Release-shared)
  • GitHub Check: Windows Build MinGW (Debug-shared)
🔇 Additional comments (5)
bindings/rust/Cargo.toml (3)

1-14: Package metadata looks good.

The package configuration is well-structured with appropriate metadata for publishing to crates.io.


21-23: Build dependencies are appropriately specified.

The build dependencies are correctly configured for FFI binding generation and C++ compilation.


34-48: Example configurations are correct.

The example binaries are properly configured with explicit paths.

bindings/rust/build.rs (2)

122-134: Bindgen configuration is well-structured.

The bindgen setup correctly uses allowlists to generate bindings only for ccap symbols, and derives useful traits (Default, Debug, PartialEq, Eq) for the generated types.


26-35: The review comment is incorrect. The code already has comprehensive SIMD feature detection and dispatch logic in place:

AVX2: Runtime CPUID detection via hasAVX2_() checks for AVX, OSXSAVE, and AVX2 support with _xgetbv() validation. All AVX2 functions are guarded with #if ENABLE_AVX2_IMP (x86_64-only compile gate) and canUseAVX2() runtime checks before execution.

NEON: Platform-specific hasNEON_() with compile-time gate (#if ENABLE_NEON_IMP for ARM64 only). NEON is mandatory on ARM64 per architecture spec, but the code includes platform-specific checks (Apple/Windows always true; Android/Linux checks HWCAP or assumes AArch64 support). All NEON functions are guarded with #if ENABLE_NEON_IMP and canUseNEON() runtime checks.

Dispatch: Every conversion function conditionally selects the appropriate SIMD backend based on detected capabilities before calling any SIMD variant.

No changes needed—the implementation already prevents executing unsupported SIMD instructions on incompatible hardware.

Likely an incorrect or invalid review comment.

Copy link

@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)
.github/workflows/rust.yml (1)

21-24: Add explicit permissions block to follow least-privilege principle.

Both CodeQL and previous reviews have flagged the missing permissions block as a security concern. The workflow should explicitly set minimal permissions for GITHUB_TOKEN.

🔎 Proposed fix
 env:
   CARGO_TERM_COLOR: always
 
+permissions:
+  contents: read
+
 jobs:
🧹 Nitpick comments (1)
.github/workflows/rust.yml (1)

52-56: Consider adding Cargo dependency caching for faster builds.

The Rust toolchain setup looks good, but you could improve CI performance by caching Cargo dependencies and build artifacts. The actions-rust-lang/setup-rust-toolchain@v1 action includes built-in caching, but it can be further optimized.

🔎 Suggested enhancement

The action you're using already includes some caching, but you can add explicit Cargo caching for better performance:

     - name: Install Rust toolchain
       uses: actions-rust-lang/setup-rust-toolchain@v1
       with:
         toolchain: stable
         components: clippy, rustfmt
+        cache: true
+        cache-directories: |
+          ~/.cargo/bin/
+          ~/.cargo/registry/index/
+          ~/.cargo/registry/cache/
+          ~/.cargo/git/db/
+          bindings/rust/target/

Alternatively, add a separate caching step:

    - name: Cache Cargo dependencies
      uses: actions/cache@v3
      with:
        path: |
          ~/.cargo/bin/
          ~/.cargo/registry/index/
          ~/.cargo/registry/cache/
          ~/.cargo/git/db/
          bindings/rust/target/
        key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }}
        restore-keys: |
          ${{ runner.os }}-cargo-
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 2afc385 and ebd9343.

📒 Files selected for processing (1)
  • .github/workflows/rust.yml
🧰 Additional context used
🪛 GitHub Check: CodeQL
.github/workflows/rust.yml

[warning] 28-100: Workflow does not contain permissions
Actions job or workflow does not limit the permissions of the GITHUB_TOKEN. Consider setting an explicit permissions block, using the following as a minimal starting point: {{contents: read}}


[warning] 101-132: Workflow does not contain permissions
Actions job or workflow does not limit the permissions of the GITHUB_TOKEN. Consider setting an explicit permissions block, using the following as a minimal starting point: {{contents: read}}

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (20)
  • GitHub Check: Build ARM64 (Debug-gcc-static)
  • GitHub Check: Build ARM64 (Release-gcc-shared)
  • GitHub Check: Build Fedora (Debug-shared)
  • GitHub Check: Build (Release-gcc-shared)
  • GitHub Check: Build Fedora (Release-shared)
  • GitHub Check: Build Fedora (Debug-static)
  • GitHub Check: Build (Release-clang-shared)
  • GitHub Check: Build (Debug-clang-static)
  • GitHub Check: Build (Release-clang-static)
  • GitHub Check: macOS Build (Debug-static)
  • GitHub Check: macOS Build (Debug-shared)
  • GitHub Check: macOS Build (Release-shared)
  • GitHub Check: Windows Build MinGW (Debug-shared)
  • GitHub Check: Windows Build MinGW (Release-shared)
  • GitHub Check: Windows Build MinGW (Debug-static)
  • GitHub Check: Windows Build MinGW (Release-static)
  • GitHub Check: Windows Build VS2022 (Debug-static)
  • GitHub Check: Windows Build VS2022 (Debug-shared)
  • GitHub Check: Windows Build VS2022 (Release-static)
  • GitHub Check: Windows Build VS2022 (Release-shared)
🔇 Additional comments (6)
.github/workflows/rust.yml (6)

1-19: LGTM! Well-configured workflow triggers.

The workflow name, branch triggers, and path filters are appropriate for the Rust bindings CI pipeline. The path filters will ensure this workflow only runs when relevant code changes.


34-50: LGTM! Platform-specific dependency installation is correct.

The conditional steps correctly install required build tools for each platform. Ubuntu gets cmake, build-essential, and pkg-config; Windows gets cmake via choco; macOS gets cmake via brew.


58-77: LGTM! Platform-specific C++ library build steps are correct.

The Unix and Windows build steps correctly handle platform differences:

  • Unix builds in build/Debug to match build.rs expectations for Makefiles
  • Windows builds in build which creates build/Debug/ccap.lib for MSVC
  • Both disable examples and tests to speed up CI builds

79-95: LGTM! Rust validation steps are well-structured.

The formatting and clippy checks run only on Ubuntu (efficient), while builds and tests run on all platforms (thorough). The --features static-link flag is correctly applied to all Rust commands.


111-115: Verify build-source works on Windows/macOS without cmake installation.

The build-source job only installs system dependencies on Ubuntu, while the static-link job explicitly installs cmake on all platforms. Although GitHub-hosted runners come with cmake pre-installed, this inconsistency raises a question: does the build.rs script with the build-source feature successfully compile the C++ library on Windows and macOS without explicit cmake installation?

Consider explicitly installing cmake on all platforms for consistency and to ensure the build-source feature works reliably:

🔎 Suggested enhancement for consistency
     - name: Install system dependencies (Ubuntu)
       if: matrix.os == 'ubuntu-latest'
       run: |
         sudo apt-get update
         sudo apt-get install -y build-essential pkg-config
+    
+    - name: Install system dependencies (Windows)
+      if: matrix.os == 'windows-latest'
+      run: choco install cmake
+    
+    - name: Install system dependencies (macOS)
+      if: matrix.os == 'macos-latest'
+      run: brew install cmake

If testing confirms the current setup works without these additions, please add a comment explaining that cmake is pre-installed on GitHub runners and the build-source feature doesn't require explicit installation.


117-132: LGTM! Build-source validation is correctly configured.

The job correctly validates the build-source distribution path by:

  • Not pre-building the C++ library (letting build.rs handle it via the cc crate)
  • Using --no-default-features --features build-source to disable static linking
  • Running both build and test to ensure the feature works end-to-end

The helpful comment on lines 122-123 clearly explains the intentional difference from the static-link job.

Copy link

@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: 4

♻️ Duplicate comments (6)
bindings/rust/build.rs (2)

15-24: Fragile path traversal will panic with unclear error message.

The chained .parent().unwrap().parent().unwrap() (lines 17-20) will panic if the manifest directory doesn't have two parent directories. While unlikely in normal repository structure, this creates a fragile assumption that will fail with an unclear "called Option::unwrap() on a None value" message.

🔎 Proposed fix with better error handling
     } else {
         (
             manifest_path
-                .parent()
-                .unwrap()
-                .parent()
-                .unwrap()
-                .to_path_buf(),
+                .ancestors()
+                .nth(2)
+                .expect("Cargo manifest must be at least 2 directories deep (expected bindings/rust/Cargo.toml)")
+                .to_path_buf(),
             false,
         )
     };

165-168: Hardcoded paths break rerun triggers in packaged mode.

The rerun-if-changed directives (lines 166-168) use hardcoded ../../include/* paths that assume repository layout. When the crate is packaged for crates.io (ccap_root points to native/), these paths won't exist, causing rebuild triggers to silently fail.

🔎 Proposed fix using ccap_root
     // Tell cargo to invalidate the built crate whenever the wrapper changes
     println!("cargo:rerun-if-changed=wrapper.h");
-    println!("cargo:rerun-if-changed=../../include/ccap_c.h");
-    println!("cargo:rerun-if-changed=../../include/ccap_utils_c.h");
-    println!("cargo:rerun-if-changed=../../include/ccap_convert_c.h");
+    println!("cargo:rerun-if-changed={}/include/ccap_c.h", ccap_root.display());
+    println!("cargo:rerun-if-changed={}/include/ccap_utils_c.h", ccap_root.display());
+    println!("cargo:rerun-if-changed={}/include/ccap_convert_c.h", ccap_root.display());
bindings/rust/README.md (1)

147-155: Outdated API call in error handling example.

Line 150 still uses provider.grab_frame_blocking(), which was flagged in previous reviews as non-existent. The synchronous API requires a timeout parameter: provider.grab_frame(timeout_ms). This should be updated to match the corrected usage shown in the Basic Usage example (line 53).

🔎 Proposed fix
 ```rust
-match provider.grab_frame_blocking() {
+match provider.grab_frame(3000) {
     Ok(Some(frame)) => { /* process frame */ },
     Ok(None) => println!("No frame available"),
     Err(e) => eprintln!("Capture error: {:?}", e),
 }
</details>

</blockquote></details>
<details>
<summary>bindings/rust/src/async.rs (1)</summary><blockquote>

`95-104`: **Frame stream implementation is incomplete.**

This was already flagged in previous reviews - the receiver will never yield items since nothing sends to the channel. The doc comment at lines 17-18 acknowledges this limitation.

</blockquote></details>
<details>
<summary>bindings/rust/src/frame.rs (1)</summary><blockquote>

`191-193`: **Document thread-safety assumptions for unsafe Send+Sync.**

The previous review flagged that these unsafe impls require documented justification. The underlying C library's thread-safety guarantees should be verified and documented in a `// SAFETY:` comment.



At minimum, add safety documentation:

```diff
-// Make VideoFrame Send + Sync if the underlying C library supports it
-unsafe impl Send for VideoFrame {}
-unsafe impl Sync for VideoFrame {}
+// SAFETY: VideoFrame wraps a CcapVideoFrame pointer. The underlying ccap library
+// guarantees that video frame data is immutable after creation and frame handles
+// can be safely shared across threads. The frame's lifetime is managed by 
+// reference counting in the C library.
+unsafe impl Send for VideoFrame {}
+unsafe impl Sync for VideoFrame {}
bindings/rust/src/provider.rs (1)

361-373: Global callback memory leak is acknowledged but not addressed.

The previous review suggested using OnceLock to avoid repeated leaks on multiple calls. The current implementation will leak memory each time set_error_callback is called. While acceptable for single-call usage, consider the OnceLock pattern if users might call this multiple times.

🧹 Nitpick comments (6)
bindings/rust/src/error.rs (1)

4-89: Consider using thiserror to reduce boilerplate.

The manual Display and Error implementations (lines 64-89) work correctly but could be simplified using the thiserror crate that's already in your dependencies. This would reduce boilerplate, ensure consistent error message formatting, and align with Rust ecosystem conventions.

🔎 Example with thiserror
+use thiserror::Error;
+
 /// Error types for ccap operations
-#[derive(Debug)]
+#[derive(Debug, Error)]
 pub enum CcapError {
     /// No error occurred
+    #[error("No error")]
     None,
 
     /// No camera device found
+    #[error("No camera device found")]
     NoDeviceFound,
 
     /// Invalid device specified
+    #[error("Invalid device: {0}")]
     InvalidDevice(String),
 
     /// Camera device open failed
+    #[error("Camera device open failed")]
     DeviceOpenFailed,
 
     // ... (apply pattern to remaining variants)
 
     /// Unknown error with error code
+    #[error("Unknown error: {code}")]
     Unknown {
         /// Error code from the underlying system
         code: i32,
     },
 }
-
-impl std::fmt::Display for CcapError {
-    // ... (remove manual Display impl)
-}
-
-impl std::error::Error for CcapError {}
bindings/rust/examples/minimal_example.rs (1)

17-17: Consider safe integer conversion to prevent potential overflow.

Line 17 casts camera_index (usize) directly to i32. While unlikely in practice, on 64-bit systems with large indices, this could overflow and produce negative or incorrect values. Consider using a checked conversion for robustness.

🔎 Proposed fix with safe conversion
-    let mut provider = Provider::with_device(camera_index as i32)?;
+    let device_index = i32::try_from(camera_index)
+        .map_err(|_| ccap::CcapError::InvalidParameter(
+            format!("Device index {} is too large", camera_index)
+        ))?;
+    let mut provider = Provider::with_device(device_index)?;
bindings/rust/examples/capture_callback.rs (1)

37-37: Consider safe integer conversion to prevent potential overflow.

Line 37 has the same integer cast issue as minimal_example.rs: device_index as i32 could overflow on 64-bit systems. Consider using i32::try_from() for safer conversion.

🔎 Proposed fix
-    let mut provider = Provider::with_device(device_index as i32)?;
+    let device_index_i32 = i32::try_from(device_index)
+        .map_err(|_| ccap::CcapError::InvalidParameter(
+            format!("Device index {} is too large", device_index)
+        ))?;
+    let mut provider = Provider::with_device(device_index_i32)?;
bindings/rust/src/async.rs (1)

95-97: Redundant feature gate.

The #[cfg(feature = "async")] on line 95 is unnecessary since the entire impl AsyncProvider block starting at line 27 is already gated by the same feature.

🔎 Proposed fix
-    #[cfg(feature = "async")]
     /// Create a stream of frames
     pub fn frame_stream(&mut self) -> impl futures::Stream<Item = VideoFrame> {
bindings/rust/src/frame.rs (1)

154-178: Consider documenting FFI overhead in convenience methods.

Each convenience method (width(), height(), etc.) makes a separate FFI call via info(). For performance-critical code paths where multiple properties are needed, users should call info() once and access fields directly. A doc comment would help clarify this.

bindings/rust/src/provider.rs (1)

14-14: Add safety documentation for unsafe impl Send.

Similar to VideoFrame, the Send impl for Provider assumes thread-safety of the underlying C library. Add a // SAFETY: comment documenting the invariants.

+// SAFETY: Provider's handle operations are thread-safe in the ccap C library.
+// The callback_ptr is only accessed while holding appropriate ownership.
 unsafe impl Send for Provider {}
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ebd9343 and ffad966.

📒 Files selected for processing (17)
  • .github/workflows/rust.yml
  • bindings/rust/README.md
  • bindings/rust/build.rs
  • bindings/rust/build_and_test.sh
  • bindings/rust/examples/capture_callback.rs
  • bindings/rust/examples/capture_grab.rs
  • bindings/rust/examples/minimal_example.rs
  • bindings/rust/examples/print_camera.rs
  • bindings/rust/src/async.rs
  • bindings/rust/src/convert.rs
  • bindings/rust/src/error.rs
  • bindings/rust/src/frame.rs
  • bindings/rust/src/lib.rs
  • bindings/rust/src/provider.rs
  • bindings/rust/src/types.rs
  • bindings/rust/src/utils.rs
  • bindings/rust/tests/integration_tests.rs
🚧 Files skipped from review as they are similar to previous changes (5)
  • bindings/rust/examples/print_camera.rs
  • bindings/rust/build_and_test.sh
  • .github/workflows/rust.yml
  • bindings/rust/examples/capture_grab.rs
  • bindings/rust/src/types.rs
🧰 Additional context used
🧬 Code graph analysis (6)
bindings/rust/tests/integration_tests.rs (4)
bindings/rust/src/async.rs (2)
  • new (30-39)
  • is_opened (66-69)
bindings/rust/src/provider.rs (4)
  • new (18-29)
  • is_opened (209-211)
  • version (291-302)
  • with_device (32-46)
bindings/rust/src/lib.rs (1)
  • version (38-40)
bindings/rust/src/types.rs (2)
  • from_c_enum (62-64)
  • from_c_enum (197-207)
bindings/rust/src/lib.rs (2)
include/ccap_utils.h (1)
  • LogLevel (80-140)
bindings/rust/src/provider.rs (1)
  • version (291-302)
bindings/rust/src/async.rs (1)
bindings/rust/src/provider.rs (8)
  • new (18-29)
  • find_device_names (311-313)
  • open (147-159)
  • start (199-201)
  • stop (204-206)
  • is_opened (209-211)
  • is_started (194-196)
  • grab_frame (257-268)
bindings/rust/src/convert.rs (1)
bindings/rust/src/types.rs (2)
  • from_c_enum (62-64)
  • from_c_enum (197-207)
bindings/rust/src/utils.rs (2)
include/ccap_utils.h (1)
  • LogLevel (80-140)
bindings/rust/src/types.rs (3)
  • to_c_enum (57-59)
  • to_c_enum (148-150)
  • to_c_enum (185-194)
bindings/rust/examples/minimal_example.rs (4)
bindings/rust/examples/print_camera.rs (1)
  • main (62-83)
bindings/rust/src/provider.rs (4)
  • set_error_callback (334-373)
  • new (18-29)
  • with_device (32-46)
  • pixel_format (323-326)
bindings/rust/src/utils.rs (1)
  • select_camera (201-234)
bindings/rust/src/frame.rs (5)
  • index (176-178)
  • width (154-156)
  • height (159-161)
  • data_size (171-173)
  • pixel_format (164-168)
🪛 GitHub Actions: Rust CI
bindings/rust/src/convert.rs

[error] 205-205: this function has too many arguments (8/7) during cargo clippy linting. Command: cargo clippy --all-targets --features static-link -- -D warnings

🪛 LanguageTool
bindings/rust/README.md

[style] ~180-~180: Consider using a less common alternative to make your writing sound more unique and professional.
Context: ...ontributing Contributions are welcome! Please feel free to submit a Pull Request. ## License Thi...

(FEEL_FREE_TO_STYLE_ME)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (16)
  • GitHub Check: Build (Release-clang-shared)
  • GitHub Check: Build ARM64 (Release-gcc-shared)
  • GitHub Check: Build (Release-gcc-static)
  • GitHub Check: Build (Debug-gcc-shared)
  • GitHub Check: Build (Debug-gcc-static)
  • GitHub Check: Build (Debug-clang-shared)
  • GitHub Check: Static Link (Dev) (windows-latest)
  • GitHub Check: Static Link (Dev) (ubuntu-latest)
  • GitHub Check: Static Link (Dev) (macos-latest)
  • GitHub Check: macOS Build (Debug-static)
  • GitHub Check: Windows Build MinGW (Debug-static)
  • GitHub Check: Windows Build MinGW (Release-shared)
  • GitHub Check: Windows Build MinGW (Release-static)
  • GitHub Check: Windows Build VS2022 (Release-static)
  • GitHub Check: Windows Build VS2022 (Release-shared)
  • GitHub Check: Windows Build VS2022 (Debug-shared)
🔇 Additional comments (8)
bindings/rust/README.md (1)

68-90: The async API signatures in the example are correct. The review shows:

  • AsyncProvider::open(&self, device_name: Option<&str>, auto_start: bool) matches the implementation at line 48 of src/async.rs
  • AsyncProvider::grab_frame() matches the implementation at line 91, which defaults to a 0ms timeout

No issues found.

bindings/rust/src/async.rs (2)

17-25: Acknowledged incomplete feature with proper documentation.

The doc comment and #[allow(dead_code)] appropriately document that frame streaming is reserved for future implementation. The current approach is acceptable for initial release.


78-88: Correct async-to-sync bridge pattern.

Using spawn_blocking with blocking_lock() is the right approach to avoid blocking the async runtime while calling synchronous FFI code.

bindings/rust/src/frame.rs (2)

24-35: Proper bounds checking implemented.

The use of .min() to clamp counts to array lengths prevents potential panics from malformed C data. This addresses the previous review concern.


93-106: Plane size calculations now correctly account for stride × height.

The fix properly computes plane sizes: full height for plane 0, and (height + 1) / 2 for chroma planes, which is correct for common YUV formats like NV12 and I420.

bindings/rust/src/provider.rs (3)

161-186: Device name parameter is now properly handled.

The implementation correctly destroys the old handle, creates a new provider with the specified device name, and handles the auto_start flag. This addresses the previous review concern.


322-326: Type conversion is now correct.

Using PixelFormat::from_c_enum(format_val as sys::CcapPixelFormat) properly converts the property value to the enum type.


469-477: Callback cleanup and Drop implementation are correct.

The cleanup_callback method properly frees the boxed callback, and Drop ensures resources are released. This addresses the previous memory leak concerns for frame callbacks.

Also applies to: 480-491

Comment on lines 48 to 86
) -> Result<Vec<u8>> {
let dst_stride = (width * 3) as usize;
let dst_size = dst_stride * height as usize;
let mut dst_data = vec![0u8; dst_size];

unsafe {
sys::ccap_convert_yuyv_to_rgb24(
src_data.as_ptr(),
src_stride as c_int,
dst_data.as_mut_ptr(),
dst_stride as c_int,
width as c_int,
height as c_int,
sys::CcapConvertFlag_CCAP_CONVERT_FLAG_DEFAULT,
)
};

Ok(dst_data)
}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

find . -type f -name "*.h" | head -20

Repository: wysaid/CameraCapture

Length of output: 878


🏁 Script executed:

fd -e h -e rs "convert" | grep -E "\.(h|rs)$" | head -20

Repository: wysaid/CameraCapture

Length of output: 238


🏁 Script executed:

git ls-files | grep -E "bindings/rust/src/convert\.rs"

Repository: wysaid/CameraCapture

Length of output: 92


🏁 Script executed:

cat include/ccap_convert.h | head -100

Repository: wysaid/CameraCapture

Length of output: 3532


🏁 Script executed:

cat include/ccap_convert_c.h | head -100

Repository: wysaid/CameraCapture

Length of output: 3536


🏁 Script executed:

cat -n bindings/rust/src/convert.rs | head -250

Repository: wysaid/CameraCapture

Length of output: 8617


🏁 Script executed:

cat include/ccap_convert_c.h | tail -150

Repository: wysaid/CameraCapture

Length of output: 6348


🏁 Script executed:

grep -A 5 "ccap_convert_yuyv_to_rgb24\|ccap_convert_nv12_to_rgb24\|ccap_convert_i420_to_rgb24" include/ccap_convert_c.h

Repository: wysaid/CameraCapture

Length of output: 999


Remove misleading Result wrapper—C functions return void with no failure path.

All conversion functions return Result<Vec<u8>> despite the underlying C FFI functions returning void and having no error indication mechanism. The return type should be Vec<u8> directly since there is no error case to handle.

This applies to all conversion methods in this file: yuyv_to_rgb24, yuyv_to_bgr24, rgb_to_bgr, bgr_to_rgb, nv12_to_rgb24, nv12_to_bgr24, i420_to_rgb24, i420_to_bgr24, and any others following this pattern.

🤖 Prompt for AI Agents
In bindings/rust/src/convert.rs around lines 48 to 66, the conversion functions
currently return Result<Vec<u8>> even though the underlying C FFI conversion
functions return void and cannot fail; change the signature of yuyv_to_rgb24
(and all other conversion functions listed: yuyv_to_bgr24, rgb_to_bgr,
bgr_to_rgb, nv12_to_rgb24, nv12_to_bgr24, i420_to_rgb24, i420_to_bgr24, etc.) to
return Vec<u8> directly, remove Result usage and any Ok(...) wrappers, and
eliminate unused Result/any error imports so each function allocates the dst
buffer, calls the unsafe FFI, and returns the Vec<u8> without wrapping it in
Result.

@wysaid
Copy link
Owner

wysaid commented Dec 31, 2025

@coderabbitai review

@coderabbitai
Copy link

coderabbitai bot commented Dec 31, 2025

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Copy link

@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: 4

♻️ Duplicate comments (3)
bindings/rust/src/convert.rs (1)

43-66: Result wrapper is misleading—C FFI functions return void.

All conversion functions (including this one) return Result<Vec<u8>> but the underlying C functions like ccap_convert_yuyv_to_rgb24 return void with no error indication. The Ok(dst_data) on line 65 always succeeds, making the Result type misleading to callers.

Either change the return type to Vec<u8> directly, or add actual validation (e.g., input size checks) that could produce errors.

CMakeLists.txt (1)

404-406: Circular dependency between ccap and ccap-rust targets.

Line 394 declares ccap-rust with DEPENDS ccap, meaning ccap-rust depends on ccap. Line 405 then adds add_dependencies(ccap ccap-rust), creating the reverse dependency. This circular ordering will cause CMake build failures.

Remove line 405 (add_dependencies(ccap ccap-rust)) or introduce a separate aggregation target (e.g., ccap-all) that depends on both ccap and ccap-rust if you want a unified build command.

🔎 Proposed fix
         # Add to main build if requested
         if (CCAP_IS_ROOT_PROJECT)
-            add_dependencies(ccap ccap-rust)
+            # Create an aggregation target instead of circular dependency
+            add_custom_target(ccap-all DEPENDS ccap ccap-rust)
         endif ()
bindings/rust/src/provider.rs (1)

338-359: Undefined behavior: pointer layout mismatch in callback reconstruction.

Line 338 creates Arc<Mutex<F>> (concrete type), but line 349 casts user_data to *const Arc<Mutex<dyn Fn(u32, &str) + Send + Sync>> (trait object). These have incompatible layouts—the trait object pointer is a fat pointer (two words: data + vtable), but the concrete pointer is a single word.

Construct the trait object type from the start:

🔎 Proposed fix
     pub fn set_error_callback<F>(callback: F)
     where
         F: Fn(u32, &str) + Send + Sync + 'static,
     {
         use std::os::raw::c_char;
         use std::sync::{Arc, Mutex};

-        let callback = Arc::new(Mutex::new(callback));
+        let callback: Arc<Mutex<dyn Fn(u32, &str) + Send + Sync>> = Arc::new(Mutex::new(callback));
🧹 Nitpick comments (2)
CMakeLists.txt (1)

390-395: Cargo always builds in --release mode regardless of CMake build type.

When CMake is configured for Debug, the C++ ccap library will have a debug postfix (ccapd on MSVC per line 178). However, cargo build --release here will link against release artifacts. This may cause linking failures or mismatched symbols in Debug configurations.

Consider passing the build profile based on CMAKE_BUILD_TYPE:

🔎 Proposed fix
+        # Determine Cargo profile based on CMake build type
+        if (CMAKE_BUILD_TYPE STREQUAL "Debug")
+            set(CARGO_PROFILE "")
+        else ()
+            set(CARGO_PROFILE "--release")
+        endif ()
+
         # Add custom target to build Rust bindings
         add_custom_target(ccap-rust
-                COMMAND ${CARGO_CMD} build --release
+                COMMAND ${CARGO_CMD} build ${CARGO_PROFILE}
                 WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/bindings/rust
                 COMMENT "Building Rust bindings"
                 DEPENDS ccap
         )
bindings/rust/src/utils.rs (1)

217-226: flush().unwrap() can panic; parse error handling is silent.

Line 219 uses .unwrap() which can panic if stdout flush fails (e.g., broken pipe). Consider using let _ = io::stdout().flush(); or propagating the error.

Line 226 silently defaults to index 0 on parse failure without informing the user. The bounds check at line 228 partially addresses this, but the user receives no feedback that their input was invalid.

🔎 Proposed improvement
         print!("Enter the index of the device you want to use: ");
         use std::io::{self, Write};
-        io::stdout().flush().unwrap();
+        let _ = io::stdout().flush();

         let mut input = String::new();
         io::stdin()
             .read_line(&mut input)
             .map_err(|e| CcapError::InvalidParameter(format!("Failed to read input: {}", e)))?;

-        let selected_index = input.trim().parse::<usize>().unwrap_or(0);
+        let selected_index = match input.trim().parse::<usize>() {
+            Ok(index) => index,
+            Err(_) => {
+                println!("Invalid input, using the first device: {}", devices[0]);
+                return Ok(0);
+            }
+        };
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ffad966 and e049718.

📒 Files selected for processing (6)
  • .vscode/tasks.json
  • CMakeLists.txt
  • bindings/rust/src/convert.rs
  • bindings/rust/src/provider.rs
  • bindings/rust/src/types.rs
  • bindings/rust/src/utils.rs
🚧 Files skipped from review as they are similar to previous changes (2)
  • .vscode/tasks.json
  • bindings/rust/src/types.rs
🧰 Additional context used
🧬 Code graph analysis (3)
bindings/rust/src/utils.rs (2)
include/ccap_utils.h (1)
  • LogLevel (80-140)
bindings/rust/src/types.rs (3)
  • to_c_enum (57-59)
  • to_c_enum (148-150)
  • to_c_enum (185-194)
bindings/rust/src/provider.rs (5)
include/ccap_def.h (1)
  • DeviceInfo (425-442)
bindings/rust/src/types.rs (5)
  • from (35-52)
  • from (87-103)
  • from (116-126)
  • from (154-167)
  • from (220-225)
bindings/rust/src/error.rs (1)
  • from (92-114)
bindings/rust/src/frame.rs (6)
  • width (154-156)
  • height (159-161)
  • from_c_ptr (52-57)
  • info (87-138)
  • from_c_ptr_ref (60-65)
  • drop (182-188)
bindings/rust/src/lib.rs (1)
  • version (38-40)
bindings/rust/src/convert.rs (2)
bindings/rust/src/types.rs (2)
  • from_c_enum (62-64)
  • from_c_enum (197-207)
bindings/rust/src/frame.rs (2)
  • width (154-156)
  • height (159-161)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (20)
  • GitHub Check: Windows Build VS2022 (Release-static)
  • GitHub Check: Windows Build MinGW (Release-static)
  • GitHub Check: Build ARM64 (Debug-gcc-static)
  • GitHub Check: Windows Build VS2022 (Debug-shared)
  • GitHub Check: Build ARM64 (Debug-gcc-shared)
  • GitHub Check: Build (Release-gcc-static)
  • GitHub Check: Build ARM64 (Release-gcc-shared)
  • GitHub Check: Build (Debug-gcc-shared)
  • GitHub Check: Windows Build MinGW (Debug-static)
  • GitHub Check: Build (Release-clang-static)
  • GitHub Check: Build Fedora (Debug-shared)
  • GitHub Check: Windows Build VS2022 (Release-shared)
  • GitHub Check: Windows Build MinGW (Release-shared)
  • GitHub Check: Build Fedora (Release-static)
  • GitHub Check: Build ARM64 (Release-gcc-static)
  • GitHub Check: Build Fedora (Release-shared)
  • GitHub Check: Windows VS2022 Release - No File Playback
  • GitHub Check: Build (Release-gcc-shared)
  • GitHub Check: macOS Release - No File Playback
  • GitHub Check: macOS Build (Release-shared)
🔇 Additional comments (7)
bindings/rust/src/convert.rs (1)

9-40: LGTM!

The backend management and feature detection methods are well-implemented with appropriate error handling for set_backend.

bindings/rust/src/utils.rs (3)

13-29: LGTM!

The two-step pattern (check return code, then convert) correctly handles the FFI string conversion with appropriate error handling.


62-106: LGTM!

The two-step buffer sizing pattern is correctly implemented: first call gets required size, second call fills the buffer. Error handling covers both the FFI failure and UTF-8 conversion cases.


245-270: LGTM!

The LogLevel enum provides a clean Rust abstraction over the C API's bitmask-based log levels. The to_c_enum method correctly maps to the underlying C constants.

bindings/rust/src/provider.rs (3)

161-186: LGTM!

The open_device method now properly handles the device_name parameter by recreating the provider with the specified device, addressing the previous review feedback.


319-323: LGTM!

The pixel_format method now correctly uses PixelFormat::from_c_enum for the type conversion, addressing the previous review feedback.


477-488: LGTM!

The Drop implementation properly cleans up the callback before destroying the handle, preventing resource leaks.

callback_ptr: Option<*mut std::ffi::c_void>,
}

unsafe impl Send for Provider {}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# First, explore the repository structure
echo "=== Repository structure ==="
git ls-files | head -20

echo -e "\n=== Looking for C API headers ==="
fd -t f -e h . | head -20

echo -e "\n=== Checking provider.rs location and structure ==="
find . -name "provider.rs" -type f

Repository: wysaid/CameraCapture

Length of output: 1394


🏁 Script executed:

#!/bin/bash
# Check the Provider struct definition and surrounding code
echo "=== Provider struct definition ==="
head -100 bindings/rust/src/provider.rs | cat -n

Repository: wysaid/CameraCapture

Length of output: 4210


🏁 Script executed:

#!/bin/bash
# Search for C API headers related to provider/camera
echo "=== Looking for C API provider headers ==="
fd -t f -e h . | xargs grep -l "provider\|Provider\|camera\|Camera" 2>/dev/null | head -10

Repository: wysaid/CameraCapture

Length of output: 358


🏁 Script executed:

#!/bin/bash
# Check the main C API header for CcapProvider definition and documentation
echo "=== Checking ccap_c.h for CcapProvider ==="
cat include/ccap_c.h | head -200

Repository: wysaid/CameraCapture

Length of output: 8957


🏁 Script executed:

#!/bin/bash
# Search for CcapProvider definition and any thread-safety comments
echo "=== Searching for CcapProvider definition ==="
rg -n "CcapProvider\|typedef.*Provider" include/ -C 3

Repository: wysaid/CameraCapture

Length of output: 109


🏁 Script executed:

#!/bin/bash
# Search for any thread-safety documentation in C headers
echo "=== Searching for thread-safety comments ==="
rg -n "thread|Thread|concurrent|Concurrent|thread-safe|thread safe" include/ -C 2

Repository: wysaid/CameraCapture

Length of output: 3500


🏁 Script executed:

#!/bin/bash
# Check if there are any mutex/lock patterns in the C API implementation
echo "=== Searching for synchronization primitives in source ==="
rg -n "mutex|Mutex|lock|Lock|atomic|Atomic|critical" src/ --type c --type h -C 1 | head -100

Repository: wysaid/CameraCapture

Length of output: 2883


🏁 Script executed:

#!/bin/bash
# Double-check the exact thread-safety statement in the header
grep -A 5 "not thread-safe" include/ccap_core.h

Repository: wysaid/CameraCapture

Length of output: 380


🏁 Script executed:

#!/bin/bash
# Check if there's any synchronization wrapper in the Rust binding
rg -n "Mutex|RwLock|Arc" bindings/rust/src/provider.rs

Repository: wysaid/CameraCapture

Length of output: 263


Remove unsafe impl Send or wrap Provider in Arc<Mutex<>>.

The underlying C++ Provider is explicitly documented as not thread-safe: "This class is not thread-safe. It is recommended to use it in a single thread." The current unsafe impl Send allows transferring the non-thread-safe handle across thread boundaries without synchronization, violating the C API contract and risking undefined behavior. Either remove the Send impl or wrap the handle with Arc<Mutex<>> for actual thread-safe sharing.

Copy link

@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

🧹 Nitpick comments (3)
.github/workflows/rust.yml (3)

55-60: Consider enabling Rust caching to improve CI performance.

The workflow explicitly sets cache: false, which disables caching of Rust dependencies and build artifacts. This increases CI runtime.

🔎 Proposed fix
     - name: Install Rust toolchain
       uses: actions-rust-lang/setup-rust-toolchain@v1
       with:
         toolchain: stable
         components: clippy, rustfmt
-        cache: false

Removing cache: false enables the action's default caching behavior, which caches cargo registry, git dependencies, and target directories.


88-91: Consider running clippy for build-source feature as well.

Clippy currently only checks code with the static-link feature enabled. Conditional compilation blocks under the build-source feature won't be linted, potentially missing issues in distribution mode.

🔎 Suggested addition

Add a clippy check in the build-source job as well:

    - name: Run clippy (Source)
      if: matrix.os == 'ubuntu-latest'
      working-directory: bindings/rust
      run: cargo clippy --all-targets --no-default-features --features build-source -- -D warnings

121-125: Consider enabling Rust caching to improve CI performance.

Same issue as the static-link job: cache: false disables caching and slows down CI.

🔎 Proposed fix
     - name: Install Rust toolchain
       uses: actions-rust-lang/setup-rust-toolchain@v1
       with:
         toolchain: stable
-        cache: false
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between e049718 and f499ce2.

📒 Files selected for processing (3)
  • .github/workflows/rust.yml
  • bindings/rust/examples/capture_callback.rs
  • bindings/rust/examples/capture_grab.rs
🚧 Files skipped from review as they are similar to previous changes (2)
  • bindings/rust/examples/capture_callback.rs
  • bindings/rust/examples/capture_grab.rs
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (20)
  • GitHub Check: Build (Debug-clang-static)
  • GitHub Check: macOS Build (Debug-static)
  • GitHub Check: Build Fedora (Release-static)
  • GitHub Check: Build Fedora (Release-shared)
  • GitHub Check: macOS Build (Release-static)
  • GitHub Check: Build Fedora (Debug-shared)
  • GitHub Check: Build Source (Dist) (ubuntu-latest)
  • GitHub Check: Build Source (Dist) (windows-latest)
  • GitHub Check: Build Source (Dist) (macos-latest)
  • GitHub Check: Static Link (Dev) (macos-latest)
  • GitHub Check: Static Link (Dev) (windows-latest)
  • GitHub Check: Windows Build MinGW (Release-shared)
  • GitHub Check: Windows Build MinGW (Debug-static)
  • GitHub Check: Windows Build MinGW (Debug-shared)
  • GitHub Check: Windows Build MinGW (Release-static)
  • GitHub Check: Windows VS2022 Release - No File Playback
  • GitHub Check: Windows Build VS2022 (Release-shared)
  • GitHub Check: Windows Build VS2022 (Release-static)
  • GitHub Check: Static Link (Dev) (windows-latest)
  • GitHub Check: Static Link (Dev) (macos-latest)
🔇 Additional comments (4)
.github/workflows/rust.yml (4)

1-19: LGTM! Trigger configuration is well-designed.

The workflow triggers and path filters are appropriate, ensuring CI runs when Rust bindings or their C++ dependencies change.


21-25: LGTM! Permissions follow least-privilege principle.

The explicit contents: read permission addresses the security concern flagged by CodeQL.


27-135: Workflow structure effectively tests both build modes.

The separation into static-link (development) and build-source (distribution) jobs provides good coverage for both usage scenarios of the Rust bindings.


115-119: The build-source job correctly omits cmake as it compiles C++ directly with the cc crate.

The build.rs script uses cc::Build::new() to compile C++ sources directly without requiring CMake. It includes only the project's standard headers from include/ and src/ directories, not cmake-generated files. The build-essential and pkg-config packages are sufficient since system compilers (g++, clang, MSVC) are pre-installed on GitHub runners.

Copy link

@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

♻️ Duplicate comments (2)
bindings/rust/build.rs (2)

15-21: Chained unwrap() could panic in unexpected directory structures.

The .parent().unwrap().parent().unwrap() chain will panic if the manifest directory doesn't have at least two parent directories. While unlikely in the expected repository structure, this creates a fragile assumption.

🔎 Proposed fix with better error handling
-        (
-            manifest_path
-                .parent()
-                .unwrap()
-                .parent()
-                .unwrap()
-                .to_path_buf(),
-            false,
-        )
+        (
+            manifest_path
+                .parent()
+                .and_then(|p| p.parent())
+                .expect("Cargo manifest must be at least 2 directories deep (bindings/rust/)")
+                .to_path_buf(),
+            false,
+        )

170-174: Rerun triggers use hardcoded paths that break in packaged mode.

The rerun-if-changed directives use hardcoded relative paths (../../include/*) that assume repository layout. When the crate is packaged (with ccap_root pointing to native/), these paths won't exist, causing the rerun triggers to fail silently.

🔎 Proposed fix using ccap_root
     println!("cargo:rerun-if-changed=wrapper.h");
-    println!("cargo:rerun-if-changed=../../include/ccap_c.h");
-    println!("cargo:rerun-if-changed=../../include/ccap_utils_c.h");
-    println!("cargo:rerun-if-changed=../../include/ccap_convert_c.h");
+    println!("cargo:rerun-if-changed={}/include/ccap_c.h", ccap_root.display());
+    println!("cargo:rerun-if-changed={}/include/ccap_utils_c.h", ccap_root.display());
+    println!("cargo:rerun-if-changed={}/include/ccap_convert_c.h", ccap_root.display());
🧹 Nitpick comments (1)
bindings/rust/build.rs (1)

176-189: Update CargoCallbacks to use the new constructor API.

Starting in bindgen 0.68, CargoCallbacks requires explicit instantiation with ::new(). The current code uses the deprecated unit-struct form, which triggers deprecation warnings.

Proposed fix
     let bindings = bindgen::Builder::default()
         .header("wrapper.h")
         .clang_arg(format!("-I{}/include", ccap_root.display()))
-        .parse_callbacks(Box::new(bindgen::CargoCallbacks))
+        .parse_callbacks(Box::new(bindgen::CargoCallbacks::new()))
         .allowlist_function("ccap_.*")
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between f499ce2 and 351ab6a.

📒 Files selected for processing (1)
  • bindings/rust/build.rs
🧰 Additional context used
🧬 Code graph analysis (1)
bindings/rust/build.rs (2)
bindings/rust/src/types.rs (5)
  • from (35-52)
  • from (87-103)
  • from (116-126)
  • from (154-167)
  • from (220-225)
bindings/rust/src/error.rs (1)
  • from (92-114)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (19)
  • GitHub Check: Build (Release-gcc-static)
  • GitHub Check: Build ARM64 (Release-gcc-shared)
  • GitHub Check: Build ARM64 (Debug-gcc-static)
  • GitHub Check: Build (Release-clang-static)
  • GitHub Check: Build Source (Dist) (windows-latest)
  • GitHub Check: Windows Build MinGW (Release-shared)
  • GitHub Check: Static Link (Dev) (ubuntu-latest)
  • GitHub Check: Windows Build VS2022 (Release-static)
  • GitHub Check: Windows Build MinGW (Release-static)
  • GitHub Check: Windows Build VS2022 (Debug-shared)
  • GitHub Check: Build Source (Dist) (macos-latest)
  • GitHub Check: Static Link (Dev) (windows-latest)
  • GitHub Check: Windows Build MinGW (Debug-static)
  • GitHub Check: Windows VS2022 Release - No File Playback
  • GitHub Check: Windows Build VS2022 (Release-shared)
  • GitHub Check: macOS Build (Release-shared)
  • GitHub Check: macOS Build (Debug-shared)
  • GitHub Check: Static Link (Dev) (windows-latest)
  • GitHub Check: Static Link (Dev) (macos-latest)
🔇 Additional comments (5)
bindings/rust/build.rs (5)

1-7: LGTM!

Standard imports and manifest directory initialization are correct.


29-82: LGTM!

The build-from-source configuration is well-structured with appropriate platform-specific handling, C++17 standard, and ARC support for macOS.


112-142: LGTM!

The pre-built library linking logic with Debug/Release fallback paths is well-designed for development workflows.


144-168: LGTM!

Platform-specific library linking is comprehensive and appropriate for the underlying C++ library's dependencies.


191-195: LGTM!

Standard bindgen output handling with appropriate error messages.

Copy link

@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

♻️ Duplicate comments (6)
bindings/rust/src/provider.rs (3)

14-14: Verify that Send is safe for non-thread-safe C API.

Previous reviews flagged this unsafe impl Send because the underlying C++ Provider is documented as "not thread-safe." However, Send only allows transferring ownership between threads (not concurrent access, which would require Sync). If the C API can safely be used from any single thread (just not concurrently), then Send is appropriate. If there's thread-local state or platform restrictions, this may be unsound.

Based on learnings from past reviews.


347-368: Critical UB: Incompatible pointer cast remains unfixed.

This critical issue was flagged in previous reviews but remains unresolved. Line 347 creates Arc<Mutex<F>> (concrete type), line 368 boxes it as Box<Arc<Mutex<F>>>, but line 358 reconstructs it as *const Arc<Mutex<dyn Fn...>> (trait object).

The problem: Arc<Mutex<F>> and Arc<Mutex<dyn Fn...>> have different memory layouts. The latter requires a fat pointer (data + vtable), while the former is a thin pointer. Casting between them is undefined behavior.

🔎 Required fix: box the trait object from the start
-        let callback = Arc::new(Mutex::new(callback));
+        let callback: Arc<Mutex<dyn Fn(i32, &str) + Send + Sync>> = Arc::new(Mutex::new(callback));

This ensures the Arc wraps the trait object directly, making the cast at line 358 valid.

Based on learnings from past reviews.


429-438: Critical UB: Incompatible Box cast remains unfixed.

This critical issue was flagged in previous reviews but remains unresolved. Line 437 creates Box::new(callback) where callback is the concrete type F, but line 429 reconstructs it as *const Box<dyn Fn(&VideoFrame) -> bool + Send + Sync>.

The problem: Box<F> is a thin pointer, while Box<dyn Fn...> is a fat pointer requiring two words (data + vtable). Casting between them is undefined behavior.

🔎 Required fix: box as trait object from the start
-        let callback_box = Box::new(callback);
+        let callback_box: Box<dyn Fn(&VideoFrame) -> bool + Send + Sync> = Box::new(callback);
         let callback_ptr = Box::into_raw(callback_box);

This ensures the Box contains the trait object directly, making the cast at line 429 valid.

Based on learnings from past reviews.

bindings/rust/build.rs (3)

15-21: Chained unwrap() remains fragile and will cause cryptic build panics.

This issue was flagged in previous reviews but remains unresolved. The .parent().unwrap().parent().unwrap() chain will panic with an unhelpful error if the manifest isn't at least two directories deep.

🔎 Proposed fix with explicit error message
-        (
-            manifest_path
-                .parent()
-                .unwrap()
-                .parent()
-                .unwrap()
-                .to_path_buf(),
-            false,
-        )
+        (
+            manifest_path
+                .parent()
+                .and_then(|p| p.parent())
+                .expect("Cargo manifest must be at least 2 directories deep (bindings/rust/)")
+                .to_path_buf(),
+            false,
+        )

Based on learnings from past reviews.


84-97: AVX2 flags won't compile on Windows MSVC (still unresolved).

This issue was flagged in previous reviews but remains unresolved. The flags -mavx2 and -mfma are GCC/Clang-specific and are silently ignored on MSVC, resulting in non-SIMD code that defeats the PR's performance goals.

🔎 Proposed fix with compiler detection
         #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
         {
             let mut avx2_build = cc::Build::new();
             avx2_build
                 .file(ccap_root.join("src/ccap_convert_avx2.cpp"))
                 .include(ccap_root.join("include"))
                 .include(ccap_root.join("src"))
                 .cpp(true)
-                .std("c++17")
-                .flag("-mavx2")
-                .flag("-mfma")
-                .compile("ccap_avx2");
+                .std("c++17");
+
+            if avx2_build.get_compiler().is_like_msvc() {
+                avx2_build.flag("/arch:AVX2");
+            } else {
+                avx2_build.flag("-mavx2").flag("-mfma");
+            }
+
+            avx2_build.compile("ccap_avx2");
         }

Based on learnings from past reviews.


176-178: Hardcoded rerun paths break in packaged mode (still unresolved).

This issue was flagged in previous reviews but remains unresolved. The hardcoded ../../include/* paths assume repository layout and will fail when the crate is packaged with ccap_root pointing to native/.

🔎 Proposed fix using ccap_root
     println!("cargo:rerun-if-changed=wrapper.h");
-    println!("cargo:rerun-if-changed=../../include/ccap_c.h");
-    println!("cargo:rerun-if-changed=../../include/ccap_utils_c.h");
-    println!("cargo:rerun-if-changed=../../include/ccap_convert_c.h");
+    println!("cargo:rerun-if-changed={}/include/ccap_c.h", ccap_root.display());
+    println!("cargo:rerun-if-changed={}/include/ccap_utils_c.h", ccap_root.display());
+    println!("cargo:rerun-if-changed={}/include/ccap_convert_c.h", ccap_root.display());

Based on learnings from past reviews.

📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b7b2140 and 1f01fe6.

📒 Files selected for processing (3)
  • CMakeLists.txt
  • bindings/rust/build.rs
  • bindings/rust/src/provider.rs
🚧 Files skipped from review as they are similar to previous changes (1)
  • CMakeLists.txt
🧰 Additional context used
🧬 Code graph analysis (1)
bindings/rust/src/provider.rs (6)
bindings/rust/src/async.rs (5)
  • new (30-39)
  • open (48-51)
  • start (54-57)
  • stop (60-63)
  • is_opened (66-69)
include/ccap_def.h (1)
  • DeviceInfo (425-442)
bindings/rust/src/error.rs (1)
  • from (92-118)
bindings/rust/src/types.rs (5)
  • from (35-52)
  • from (87-103)
  • from (116-126)
  • from (154-167)
  • from (220-225)
bindings/rust/src/frame.rs (6)
  • width (154-156)
  • height (159-161)
  • from_c_ptr (52-57)
  • pixel_format (164-168)
  • from_raw (75-84)
  • drop (182-188)
bindings/rust/src/lib.rs (1)
  • version (38-40)
🪛 GitHub Check: Static Link (Dev) (ubuntu-latest)
bindings/rust/src/provider.rs

[warning] 453-453:
Diff in /home/runner/work/CameraCapture/CameraCapture/bindings/rust/src/provider.rs


[warning] 337-337:
Diff in /home/runner/work/CameraCapture/CameraCapture/bindings/rust/src/provider.rs


[warning] 328-328:
Diff in /home/runner/work/CameraCapture/CameraCapture/bindings/rust/src/provider.rs

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (19)
  • GitHub Check: Windows Build VS2026 (Release-static)
  • GitHub Check: Windows Build MinGW (Release-static)
  • GitHub Check: Windows Build MinGW (Debug-shared)
  • GitHub Check: Windows VS2022 Release - No File Playback
  • GitHub Check: macOS Build (Release-static)
  • GitHub Check: macOS Build (Release-shared)
  • GitHub Check: macOS Release - No File Playback
  • GitHub Check: Build ARM64 (Debug-gcc-static)
  • GitHub Check: Build Source (Dist) (windows-latest)
  • GitHub Check: Build ARM64 (Debug-gcc-shared)
  • GitHub Check: Build (Release-clang-shared)
  • GitHub Check: Build Fedora (Debug-static)
  • GitHub Check: Build ARM64 (Release-gcc-static)
  • GitHub Check: Build Source (Dist) (macos-latest)
  • GitHub Check: Build (Release-gcc-shared)
  • GitHub Check: Build (Release-clang-static)
  • GitHub Check: Build Fedora (Release-shared)
  • GitHub Check: Build ARM64 (Release-gcc-shared)
  • GitHub Check: Build Source (Dist) (macos-latest)

Copy link

@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)
bindings/rust/src/utils.rs (1)

1-6: LGTM!

All imports are necessary and used appropriately throughout the file. The CString import is utilized by the path_to_cstring helper function.

🧹 Nitpick comments (2)
bindings/rust/src/utils.rs (2)

84-122: Consider extracting the common two-phase buffer allocation pattern.

Both dump_frame_to_file and dump_frame_to_directory share nearly identical logic for the two-phase buffer size query and data retrieval pattern. Extracting this into a helper function would reduce duplication and improve maintainability.

🔎 Example refactoring approach
fn call_with_buffer_allocation<F>(
    operation: F,
    error_msg: &str,
) -> Result<String>
where
    F: Fn(*mut i8, usize) -> i32,
{
    // First call to get required buffer size
    let buffer_size = operation(std::ptr::null_mut(), 0);

    if buffer_size <= 0 {
        return Err(CcapError::FileOperationFailed(error_msg.to_string()));
    }

    // Second call to get actual result
    let mut buffer = vec![0u8; buffer_size as usize];
    let result_len = operation(buffer.as_mut_ptr() as *mut i8, buffer.len());

    if result_len <= 0 {
        return Err(CcapError::FileOperationFailed(error_msg.to_string()));
    }

    buffer.truncate(result_len as usize);
    String::from_utf8(buffer)
        .map_err(|_| CcapError::StringConversionError("Invalid output path string".to_string()))
}

Then simplify both functions to use this helper.

Also applies to: 125-168


226-239: Improve user feedback for invalid input.

The current code silently defaults to 0 when parsing fails, which can confuse users who enter non-numeric input. They'll see "Invalid index" even though the actual issue was a parse error, not an out-of-bounds index.

🔎 Clearer error handling for user input
-    let selected_index = input.trim().parse::<usize>().unwrap_or(0);
-
-    if selected_index >= devices.len() {
-        println!("Invalid index, using the first device: {}", devices[0]);
-        Ok(0)
-    } else {
-        println!("Using device: {}", devices[selected_index]);
-        Ok(selected_index)
-    }
+    let selected_index = match input.trim().parse::<usize>() {
+        Ok(index) if index < devices.len() => {
+            println!("Using device: {}", devices[index]);
+            index
+        }
+        Ok(index) => {
+            println!("Index {} is out of range, using the first device: {}", index, devices[0]);
+            0
+        }
+        Err(_) => {
+            println!("Invalid input (expected a number), using the first device: {}", devices[0]);
+            0
+        }
+    };
+
+    Ok(selected_index)
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 3dae4bf and 4c758d6.

📒 Files selected for processing (1)
  • bindings/rust/src/utils.rs
🧰 Additional context used
🧬 Code graph analysis (1)
bindings/rust/src/utils.rs (3)
bindings/rust/src/frame.rs (3)
  • data (141-151)
  • width (154-156)
  • height (159-161)
include/ccap_utils.h (1)
  • LogLevel (80-140)
bindings/rust/src/types.rs (3)
  • to_c_enum (57-59)
  • to_c_enum (148-150)
  • to_c_enum (185-194)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (18)
  • GitHub Check: Build Source (Dist) (ubuntu-latest)
  • GitHub Check: Static Link (Dev) (windows-latest)
  • GitHub Check: Windows Build MinGW (Release-static)
  • GitHub Check: Windows Build VS2022 (Debug-static)
  • GitHub Check: Build ARM64 (Release-gcc-shared)
  • GitHub Check: Build ARM64 (Debug-gcc-static)
  • GitHub Check: Build ARM64 (Debug-gcc-shared)
  • GitHub Check: Build (Debug-clang-static)
  • GitHub Check: Build (Release-clang-shared)
  • GitHub Check: Build (Release-clang-static)
  • GitHub Check: Build ARM64 (Release-gcc-static)
  • GitHub Check: Build (Debug-clang-shared)
  • GitHub Check: Build Fedora (Release-shared)
  • GitHub Check: Build Fedora (Release-static)
  • GitHub Check: Build (Release-gcc-shared)
  • GitHub Check: Build Source (Dist) (macos-latest)
  • GitHub Check: Static Link (Dev) (windows-latest)
  • GitHub Check: Static Link (Dev) (macos-latest)
🔇 Additional comments (5)
bindings/rust/src/utils.rs (5)

62-81: LGTM! Excellent Windows Unicode path handling.

The platform-specific path conversion correctly addresses Windows UTF-16 to UTF-8 issues using to_string_lossy() while maintaining strict validation on Unix systems. This matches the fix described in the commit message.


171-204: LGTM!

The parameter count is justified by the BMP format requirements, and the clippy::too_many_arguments suppression is appropriate. The implementation correctly wraps the C API with proper error handling.


8-9: LGTM!

The Utils struct as a namespace for utility functions is a standard Rust pattern, and save_frame_as_bmp appropriately delegates to dump_frame_to_file for implementation.

Also applies to: 54-59


32-52: All 13 PixelFormat enum variants are correctly included in the string_to_pixel_format mapping. The implementation is complete with proper case-insensitive matching and error handling for unknown formats.


265-276: LogLevel constant mappings are correct.

The Rust LogLevel enum correctly maps to the C API constants defined in ccap_utils_c.h. Each variant (None, Error, Warning, Info, Verbose) maps to the corresponding CCAP_LOG_LEVEL_* constant (0, 1, 3, 7, 15) via the to_c_enum conversion function. The implementation is complete and consistent with the C API.

Copy link

@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

♻️ Duplicate comments (2)
bindings/rust/build.rs (2)

16-21: Chained unwrap() is fragile and could panic.

This issue was previously flagged but not yet addressed. The chained .parent().unwrap().parent().unwrap() will panic if the manifest directory structure is unexpected. This creates a fragile assumption about repository layout.

🔎 Proposed fix with better error handling
         (
-            manifest_path
-                .parent()
-                .unwrap()
-                .parent()
-                .unwrap()
-                .to_path_buf(),
+            manifest_path
+                .parent()
+                .and_then(|p| p.parent())
+                .expect("Cargo manifest must be at least 2 directories deep (bindings/rust/)")
+                .to_path_buf(),
             false,
         )

194-196: Hardcoded paths in rerun triggers break in packaged mode.

This issue was previously flagged but not yet addressed. The rerun-if-changed directives use hardcoded relative paths (../../include/*) that assume repository layout. When the crate is packaged with ccap_root pointing to native/, these paths won't exist, causing the rerun triggers to fail silently.

🔎 Proposed fix using ccap_root
     println!("cargo:rerun-if-changed=wrapper.h");
-    println!("cargo:rerun-if-changed=../../include/ccap_c.h");
-    println!("cargo:rerun-if-changed=../../include/ccap_utils_c.h");
-    println!("cargo:rerun-if-changed=../../include/ccap_convert_c.h");
+    println!("cargo:rerun-if-changed={}/include/ccap_c.h", ccap_root.display());
+    println!("cargo:rerun-if-changed={}/include/ccap_utils_c.h", ccap_root.display());
+    println!("cargo:rerun-if-changed={}/include/ccap_convert_c.h", ccap_root.display());
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 4c758d6 and f491794.

📒 Files selected for processing (2)
  • bindings/rust/build.rs
  • src/ccap_convert_frame.cpp
🧰 Additional context used
🧬 Code graph analysis (1)
bindings/rust/build.rs (8)
bindings/rust/examples/capture_callback.rs (1)
  • main (6-108)
bindings/rust/examples/capture_grab.rs (1)
  • main (4-73)
bindings/rust/examples/minimal_example.rs (1)
  • main (3-55)
bindings/rust/examples/print_camera.rs (1)
  • main (62-83)
bindings/rust/src/error.rs (1)
  • from (92-118)
bindings/rust/src/types.rs (5)
  • from (35-52)
  • from (87-103)
  • from (116-126)
  • from (154-167)
  • from (220-225)
bindings/rust/src/provider.rs (1)
  • new (18-29)
bindings/rust/src/async.rs (1)
  • new (30-39)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (20)
  • GitHub Check: Static Link (Dev) (windows-latest)
  • GitHub Check: Build (Debug-gcc-shared)
  • GitHub Check: Windows Build MinGW (Release-shared)
  • GitHub Check: Build (Release-clang-static)
  • GitHub Check: Build Fedora (Release-shared)
  • GitHub Check: Build ARM64 (Release-gcc-static)
  • GitHub Check: Build Fedora (Release-static)
  • GitHub Check: Build (Release-clang-shared)
  • GitHub Check: Windows Build VS2022 (Debug-static)
  • GitHub Check: Windows Build MinGW (Release-static)
  • GitHub Check: Windows VS2022 Release - No File Playback
  • GitHub Check: Windows Build VS2022 (Release-shared)
  • GitHub Check: Windows Build VS2022 (Release-static)
  • GitHub Check: Windows Build MinGW (Debug-static)
  • GitHub Check: Windows Build VS2022 (Debug-shared)
  • GitHub Check: Windows Build MinGW (Debug-shared)
  • GitHub Check: Build Source (Dist) (macos-latest)
  • GitHub Check: Static Link (Dev) (ubuntu-latest)
  • GitHub Check: Static Link (Dev) (macos-latest)
  • GitHub Check: Static Link (Dev) (windows-latest)

assert(frame->pixelFormat == toFormat);
assert(frame->allocator != nullptr && frame->data[0] == frame->allocator->data());
frame->sizeInBytes = frame->allocator->size();
frame->sizeInBytes = static_cast<uint32_t>(frame->allocator->size());
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Add bounds check to prevent silent truncation.

The explicit cast to uint32_t fixes the Windows compiler warning but introduces a potential silent truncation risk. If frame->allocator->size() exceeds UINT32_MAX (4GB), the value will be truncated, leading to an incorrect sizeInBytes. While video frames rarely exceed 4GB, high-resolution formats or future use cases could trigger this. Silent truncation in size fields can cause memory safety issues if sizeInBytes is used for bounds checking downstream.

🔎 Proposed fix: Add assertion to catch overflow
-        frame->sizeInBytes = static_cast<uint32_t>(frame->allocator->size());
+        auto allocatorSize = frame->allocator->size();
+        assert(allocatorSize <= UINT32_MAX && "Frame size exceeds uint32_t maximum");
+        frame->sizeInBytes = static_cast<uint32_t>(allocatorSize);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
frame->sizeInBytes = static_cast<uint32_t>(frame->allocator->size());
auto allocatorSize = frame->allocator->size();
assert(allocatorSize <= UINT32_MAX && "Frame size exceeds uint32_t maximum");
frame->sizeInBytes = static_cast<uint32_t>(allocatorSize);
🤖 Prompt for AI Agents
In src/ccap_convert_frame.cpp around line 245, the assignment casts
frame->allocator->size() to uint32_t which can silently truncate values >
UINT32_MAX; add an explicit bounds check: obtain the allocator size in a size_t,
compare it to std::numeric_limits<uint32_t>::max(), and if it exceeds the limit,
handle the overflow deterministically (e.g., log an error and return/propagate a
failure, or assert in debug builds) instead of performing the cast; only perform
the static_cast when the check passes so sizeInBytes remains correct and
truncation cannot occur.

Copy link

@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: 2

🧹 Nitpick comments (2)
.github/workflows/rust.yml (2)

30-106: Consider adding timeout for job reliability.

No timeout is specified for this job. Consider adding timeout-minutes to prevent runaway builds from consuming excessive CI minutes if a step hangs.

🔎 Suggested addition
   static-link:
     name: Static Link (Dev)
+    timeout-minutes: 30
     strategy:
       matrix:

111-116: Consider adding timeout for job reliability.

Similar to the static-link job, consider adding timeout-minutes to prevent hanging builds from consuming excessive CI minutes.

🔎 Suggested addition
   build-source:
     name: Build Source (Dist)
+    timeout-minutes: 30
     strategy:
       matrix:
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d37f746 and 72ad68b.

📒 Files selected for processing (1)
  • .github/workflows/rust.yml
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (16)
  • GitHub Check: Build ARM64 (Release-gcc-shared)
  • GitHub Check: Build (Debug-gcc-static)
  • GitHub Check: Build (Release-gcc-static)
  • GitHub Check: Build Source (Dist) (windows-latest)
  • GitHub Check: Build Fedora (Release-shared)
  • GitHub Check: Build (Release-clang-shared)
  • GitHub Check: Build (Debug-clang-static)
  • GitHub Check: Build ARM64 (Release-gcc-static)
  • GitHub Check: Build Fedora (Debug-static)
  • GitHub Check: Build (Release-clang-static)
  • GitHub Check: Build (Release-gcc-shared)
  • GitHub Check: Build Fedora (Release-static)
  • GitHub Check: Windows Build MinGW (Release-static)
  • GitHub Check: Windows Build VS2026 (Release-static)
  • GitHub Check: Windows Build VS2022 (Release-static)
  • GitHub Check: Windows Build VS2022 (Release-shared)
🔇 Additional comments (2)
.github/workflows/rust.yml (2)

1-26: LGTM! Workflow triggers and permissions are well-configured.

The workflow correctly triggers on relevant path changes and implements least-privilege permissions with contents: read. The path filters appropriately scope CI runs to changes affecting Rust bindings or the underlying C++ library.


111-142: No cmake installation needed for build-source job.

The build-source feature uses the cc crate for direct C++ compilation (build.rs lines 29-104), which does not require cmake. The static-link job installs cmake because it needs to build a pre-built library using cmake first. The build-source job bypasses the cmake build step entirely and compiles sources directly via cargo.

Likely an incorrect or invalid review comment.

Copy link

@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

♻️ Duplicate comments (1)
.github/workflows/rust.yml (1)

135-139: Remove ineffective refreshenv command.

The refreshenv command on line 139 won't have the intended effect in GitHub Actions since each step runs in a separate shell session. Environment variables set by the choco installations will automatically be available in subsequent steps without this command. This same issue was addressed in the static-link job but remains here.

🔎 Proposed fix
     - name: Install system dependencies (Windows)
       if: matrix.os == 'windows-latest'
       run: |
         choco install llvm -y
-        refreshenv
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 7fdaebd and 18a6be3.

📒 Files selected for processing (2)
  • .github/workflows/rust.yml
  • bindings/rust/tests/integration_tests.rs
🧰 Additional context used
🧬 Code graph analysis (1)
bindings/rust/tests/integration_tests.rs (3)
bindings/rust/src/provider.rs (3)
  • is_opened (209-211)
  • version (288-299)
  • with_device (32-46)
bindings/rust/src/lib.rs (1)
  • version (38-40)
bindings/rust/src/types.rs (2)
  • from_c_enum (62-64)
  • from_c_enum (197-207)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (17)
  • GitHub Check: Windows Build VS2022 (Release-shared)
  • GitHub Check: Windows Build MinGW (Debug-shared)
  • GitHub Check: Windows Build VS2022 (Debug-static)
  • GitHub Check: Windows Build MinGW (Debug-static)
  • GitHub Check: Windows Build VS2026 (Debug-static)
  • GitHub Check: Build ARM64 (Release-gcc-shared)
  • GitHub Check: Build (Release-gcc-shared)
  • GitHub Check: Build Fedora (Release-static)
  • GitHub Check: Windows Build MinGW (Release-static)
  • GitHub Check: Build ARM64 (Release-gcc-static)
  • GitHub Check: Windows Build VS2022 (Release-static)
  • GitHub Check: Build (Release-clang-static)
  • GitHub Check: Windows VS2022 Release - No File Playback
  • GitHub Check: macOS Build (Debug-static)
  • GitHub Check: Build Source (Dist) (windows-latest)
  • GitHub Check: Static Link (Dev) (macos-latest)
  • GitHub Check: Static Link (Dev) (windows-latest)
🔇 Additional comments (2)
.github/workflows/rust.yml (1)

1-164: Well-structured CI workflow for dual build modes.

The workflow appropriately separates static linking (development mode) from source building (distribution mode), testing both across all major platforms. The platform-specific dependency installation, LIBCLANG_PATH configuration, and build steps are correctly implemented to support the Rust bindings' build.rs expectations.

bindings/rust/tests/integration_tests.rs (1)

1-90: Comprehensive integration test coverage.

The test suite provides good coverage of the ccap bindings' core functionality with appropriate separation between camera-dependent and camera-independent tests. The use of the CCAP_SKIP_CAMERA_TESTS environment variable correctly allows CI to run without physical cameras, and the tests include validation of both success and error paths.

Comment on lines +77 to +80
if skip_camera_tests() {
eprintln!("Skipping device_operations_without_camera due to CCAP_SKIP_CAMERA_TESTS");
return;
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Remove skip guard from camera-independent test.

The test test_device_operations_without_camera is designed to verify that basic operations (provider creation, device listing, version retrieval) work regardless of camera presence, as indicated by the comment on line 81. However, the skip guard on lines 77-80 prevents it from running when CCAP_SKIP_CAMERA_TESTS is set, which is the case in CI (line 23 of rust.yml).

This defeats the purpose of the test—validating camera-independent operations should always run in CI to catch regressions in basic functionality.

🔎 Proposed fix
 #[test]
 fn test_device_operations_without_camera() {
-    if skip_camera_tests() {
-        eprintln!("Skipping device_operations_without_camera due to CCAP_SKIP_CAMERA_TESTS");
-        return;
-    }
     // Test that operations work regardless of camera presence
     let provider = Provider::new().expect("Failed to create provider");
🤖 Prompt for AI Agents
In bindings/rust/tests/integration_tests.rs around lines 77 to 80, the test is
incorrectly gated by a CCAP_SKIP_CAMERA_TESTS skip guard which prevents
test_device_operations_without_camera from running in CI; remove the if
skip_camera_tests() { eprintln!(...); return; } block (and any associated early
return) so the test always executes, leaving any camera-dependent tests separate
and unchanged.

Copy link

@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

🧹 Nitpick comments (2)
scripts/update_version.sh (2)

94-95: Consider a more targeted pattern for Cargo.toml version.

The current sed pattern matches any line starting with version = , which could inadvertently match dependency version specifications if the file structure is unusual. While this works in typical cases (where the package version appears first), you could make it more robust by:

  1. Using a multi-line sed/awk command to scope the replacement to the [package] section, or
  2. Using cargo directly: cargo metadata --format-version 1 --no-deps to validate, or leveraging tools like tomlq/cargo-edit.
Example: More targeted awk-based approach
-# 5. Update Rust crate version (Cargo.toml)
-update_file "s/^version = \".*\"$/version = \"$NEW_VERSION\"/" "$PROJECT_ROOT/bindings/rust/Cargo.toml" "Rust crate version (Cargo.toml)"
+# 5. Update Rust crate version (Cargo.toml)
+CARGO_TOML="$PROJECT_ROOT/bindings/rust/Cargo.toml"
+if [ -f "$CARGO_TOML" ]; then
+    awk -v ver="$NEW_VERSION" '
+        /^\[package\]/ { in_package=1 }
+        /^\[/ && !/^\[package\]/ { in_package=0 }
+        in_package && /^version = / { $0 = "version = \"" ver "\"" }
+        { print }
+    ' "$CARGO_TOML" > "$CARGO_TOML.tmp" && mv "$CARGO_TOML.tmp" "$CARGO_TOML"
+    echo "✅ Updated Rust crate version (Cargo.toml)"
+else
+    echo "⚠️  $CARGO_TOML not found, skipping Rust crate version"
+fi

97-116: Prefer using Cargo to regenerate the lockfile instead of manual editing.

Manually editing Cargo.lock (an auto-generated file) can lead to inconsistencies and is not standard Rust practice. When Cargo rebuilds or updates dependencies, it may overwrite your manual changes or produce unexpected results.

Recommended approach:

  1. Update Cargo.toml (which you already do in Step 5)
  2. Run cargo update -p ccap (or cargo check) to let Cargo regenerate Cargo.lock with the correct version

This ensures consistency with Cargo's dependency resolution and follows Rust ecosystem conventions.

Proposed alternative using Cargo
-# 6. Update Rust lockfile entry for ccap (Cargo.lock)
-RUST_LOCKFILE="$PROJECT_ROOT/bindings/rust/Cargo.lock"
-if [ -f "$RUST_LOCKFILE" ]; then
-    python3 - "$RUST_LOCKFILE" "$NEW_VERSION" <<'PY'
-import pathlib, re, sys
-
-path = pathlib.Path(sys.argv[1])
-new_ver = sys.argv[2]
-text = path.read_text()
-pattern = r'(?m)(^name = "ccap"\nversion = ")[^"]+("\n)'
-new_text, count = re.subn(pattern, rf"\1{new_ver}\2", text, count=1)
-if count:
-    path.write_text(new_text)
-    print(f"✅ Updated Rust lockfile ccap version to {new_ver}")
-else:
-    print(f"⚠️  ccap entry not found in {path}, lockfile not modified")
-PY
-else
-    echo "⚠️  $RUST_LOCKFILE not found, skipping Rust lockfile update"
-fi
+# 6. Regenerate Rust lockfile using Cargo
+RUST_DIR="$PROJECT_ROOT/bindings/rust"
+if [ -d "$RUST_DIR" ] && [ -f "$RUST_DIR/Cargo.toml" ]; then
+    if command -v cargo &> /dev/null; then
+        (cd "$RUST_DIR" && cargo update -p ccap 2>&1) && \
+            echo "✅ Regenerated Rust lockfile (Cargo.lock)" || \
+            echo "⚠️  Failed to update Rust lockfile, please run 'cargo update -p ccap' manually"
+    else
+        echo "⚠️  cargo not found, skipping Rust lockfile update (run 'cargo update -p ccap' manually)"
+    fi
+else
+    echo "⚠️  Rust bindings directory not found, skipping Rust lockfile update"
+fi
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 18a6be3 and 83f93aa.

⛔ Files ignored due to path filters (1)
  • bindings/rust/Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (3)
  • bindings/rust/Cargo.toml
  • bindings/rust/build_and_test.sh
  • scripts/update_version.sh
🚧 Files skipped from review as they are similar to previous changes (2)
  • bindings/rust/build_and_test.sh
  • bindings/rust/Cargo.toml
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-12-31T04:11:08.021Z
Learnt from: CR
Repo: wysaid/CameraCapture PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-12-31T04:11:08.021Z
Learning: Run `./scripts/update_version.sh <version>` to update version files

Applied to files:

  • scripts/update_version.sh
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (18)
  • GitHub Check: Build Source (Dist) (macos-latest)
  • GitHub Check: Static Link (Dev) (macos-latest)
  • GitHub Check: Build Source (Dist) (windows-latest)
  • GitHub Check: Build ARM64 (Release-gcc-shared)
  • GitHub Check: Build (Release-gcc-shared)
  • GitHub Check: Build (Release-clang-static)
  • GitHub Check: Build ARM64 (Release-gcc-static)
  • GitHub Check: Build Fedora (Debug-shared)
  • GitHub Check: Build (Release-clang-shared)
  • GitHub Check: Build Fedora (Release-shared)
  • GitHub Check: Windows Build MinGW (Release-static)
  • GitHub Check: Windows Build VS2022 (Release-static)
  • GitHub Check: Windows Build VS2022 (Debug-static)
  • GitHub Check: Windows Build VS2022 (Debug-shared)
  • GitHub Check: Windows VS2022 Release - No File Playback
  • GitHub Check: Windows Build VS2022 (Release-shared)
  • GitHub Check: Windows Build MinGW (Debug-shared)
  • GitHub Check: Windows Build MinGW (Debug-static)

Copy link

@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

🧹 Nitpick comments (1)
bindings/rust/README.md (1)

187-187: Minor: Improve phrasing for professional tone.

The phrase "Please feel free to submit a Pull Request" is somewhat informal. Consider a more direct alternative like "Contributions and pull requests are welcome" for a more professional tone.

📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 83f93aa and e10615d.

📒 Files selected for processing (2)
  • bindings/rust/README.md
  • bindings/rust/build.rs
🧰 Additional context used
🧬 Code graph analysis (1)
bindings/rust/build.rs (2)
bindings/rust/src/error.rs (1)
  • from (92-118)
bindings/rust/src/types.rs (5)
  • from (35-52)
  • from (87-103)
  • from (116-126)
  • from (154-167)
  • from (220-225)
🪛 LanguageTool
bindings/rust/README.md

[style] ~187-~187: Consider using a less common alternative to make your writing sound more unique and professional.
Context: ...ontributing Contributions are welcome! Please feel free to submit a Pull Request. ## License Thi...

(FEEL_FREE_TO_STYLE_ME)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (20)
  • GitHub Check: Build (Debug-gcc-shared)
  • GitHub Check: Build (Debug-gcc-static)
  • GitHub Check: Build (Release-clang-static)
  • GitHub Check: Windows Build VS2022 (Release-static)
  • GitHub Check: Build Fedora (Debug-shared)
  • GitHub Check: Build Fedora (Debug-static)
  • GitHub Check: Build Fedora (Release-static)
  • GitHub Check: Build ARM64 (Debug-gcc-static)
  • GitHub Check: Windows Build VS2026 (Release-static)
  • GitHub Check: Build ARM64 (Release-gcc-static)
  • GitHub Check: Windows Build VS2022 (Debug-shared)
  • GitHub Check: Build Source (Dist) (windows-latest)
  • GitHub Check: Windows Build MinGW (Release-static)
  • GitHub Check: Windows Build VS2022 (Release-shared)
  • GitHub Check: Build Source (Dist) (macos-latest)
  • GitHub Check: Windows Build MinGW (Debug-shared)
  • GitHub Check: Windows Build MinGW (Release-shared)
  • GitHub Check: Windows Build MinGW (Debug-static)
  • GitHub Check: Static Link (Dev) (windows-latest)
  • GitHub Check: Static Link (Dev) (macos-latest)
🔇 Additional comments (9)
bindings/rust/README.md (2)

68-90: Verify API consistency between sync and async examples.

The async example shows different method names/signatures compared to the sync version:

  • Line 79 calls provider.open(Some(&devices[0]), true) but the sync example (line 49) uses provider.open_device(). Clarify if AsyncProvider has different method names or if this is an error.
  • Line 82 calls provider.grab_frame() without a timeout parameter, whereas the sync version requires a timeout (line 53: grab_frame(3000)). Confirm whether the async API supports timeout differently or if the example is incomplete.

Also verify that the async example documentation aligns with the actual AsyncProvider API.


92-111: Verify that referenced examples exist and are executable.

The Examples section lists several example invocations (print_camera, minimal_example, capture_grab, capture_callback) but does not reference the corresponding example file names. Ensure that:

  1. All referenced example names match the actual file names in bindings/rust/examples/
  2. Each example can be executed with the provided cargo run commands
  3. If the async example in line 110 requires specific feature flags, they are correctly specified
bindings/rust/build.rs (7)

1-23: LGTM! Previous issues resolved.

The ccap_root detection logic correctly handles both packaged (crates.io) and repository modes. The previously flagged issues have been fixed:

  • is_packaged is now used on line 194
  • Safe navigation with .and_then() and .expect() replaces the chained .unwrap()

25-81: LGTM!

The build-from-source configuration is well-structured with appropriate platform-specific sources and compiler flags. The C++17 standard and file playback support are correctly enabled.


83-103: LGTM! MSVC AVX2 issue resolved.

The AVX2 build now correctly detects MSVC and applies /arch:AVX2 instead of GCC-style flags. This resolves the previously flagged critical issue.


105-123: LGTM!

Building the NEON file unconditionally to provide the hasNEON() symbol across all platforms is a sound design. The comment clearly explains the behavior.


161-189: LGTM!

Platform-specific linking configuration is appropriate for macOS (frameworks), Linux (stdc++), and Windows (COM and Media Foundation libraries).


191-207: LGTM! Rerun triggers now use ccap_root.

The previously flagged issue with hardcoded ../../include/* paths has been resolved. The rerun triggers now correctly use ccap_root.display() and are only added in repository mode (!is_packaged).


209-229: LGTM!

The bindgen configuration is standard and appropriate. The allowlists correctly target the C API surface, and the derives are suitable for FFI types.

Comment on lines +153 to +347
// Note: On MSVC, we always link to the Release version (ccap.lib)
// to avoid CRT mismatch issues, since Rust uses the release CRT
// even in debug builds by default
println!("cargo:rustc-link-lib=static=ccap");

println!("cargo:warning=Linking against pre-built ccap library (dev mode)...");
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

rg -i -C 3 "msvc.*crt|crt.*mismatch" --type rust

Repository: wysaid/CameraCapture

Length of output: 503


🏁 Script executed:

# Also search for MSVC-related discussions more broadly
rg -i "msvc" bindings/rust/build.rs -C 5

Repository: wysaid/CameraCapture

Length of output: 1049


🌐 Web query:

Rust MSVC CRT linking behavior in debug builds - does Rust use release CRT by default

💡 Result:

Short answer: Yes — on MSVC targets Rust links the (dynamic) release CRT by default even for Debug builds; it does not automatically switch to the MSVC debug CRT (MDd/msvcrtd). [1][2]

Details / notes:

  • The Rust toolchain exposes CRT linkage via the crt-static target feature (dynamic by default), but that controls static vs dynamic, not debug vs release. Rust’s default dynamic CRT corresponds to the normal/release MSVC runtime (msvcrt / vcruntime*.dll). [1][3]
  • This has been a longstanding limitation/issue (see rust-lang/rust issue #39016). Workarounds include passing explicit linker args (/nodefaultlib:/defaultlib or -Clink-arg), using crates like static_vcruntime, or aligning the C/C++ side to use the release CRT. [2][3][4]

References:
[1] Rust Reference — Linkage (CRT / crt-static).
[2] rust-lang/rust issue #39016 (always links non-debug Windows runtime).
[3] static_vcruntime crate documentation (options for VCRuntime/UCRT).
[4] Blog post describing MSVC debug/Release CRT mismatch with Rust (example).


🌐 Web query:

How to link C++ library with different CRT versions in Rust on Windows MSVC

💡 Result:

Short answer: you must ensure the Rust binary and the C++ library use compatible CRT linkage (same MSVC CRT type/version) or isolate them (DLL boundary, C ABI), otherwise you get ODR/heap/pointer issues. Options:

  1. Build the C++ library with the same CRT as Rust:

    • Default Rust MSVC links dynamically (/MD). Compile the C++ lib with /MD (dynamic CRT) using the same toolset (same MSVC version). [1][2]
  2. Make Rust use the static VC runtime (so it matches a /MT C++ lib) or statically link vcruntime:

    • Use RUSTFLAGS -C target-feature=+crt-static to make Rust link the CRT statically (/MT). Note this also statically links UCRT unless you use the helper below. [1][6]
    • Or use the static_vcruntime crate to statically link the VC runtime while still using the dynamic UCRT (common compromise). Add static_vcruntime in build-dependencies and call metabuild() in build.rs; optionally set the target rustflags in .cargo/config.toml. [2][3][7]
  3. Isolate differing CRTs across process/DLL boundaries:

    • Build the C++ code as a DLL and expose a plain C ABI (extern "C") boundary. Each DLL can use its own CRT safely if ownership of CRT-managed resources never crosses the ABI (no crossing of malloc/free, FILE*, STL objects, C++ exceptions, FILE handles, std::string, etc.). Prefer returning simple PODs and use explicit allocation/deallocation functions. [common Windows/MSVC practice; see 1]
  4. Link-time tweaks (advanced / fragile):

    • You can force or suppress default libraries with MSVC link flags (/DEFAULTLIB:, /NODEFAULTLIB:) via rustc link-args, but mixing CRTs manually is error-prone and not recommended for production. Use only if you understand the consequences. [2][7]

Practical checklist

  • Pick a strategy (match CRT or DLL boundary).
  • Build C++ with the matching MSVC toolset and flags (/MD vs /MT).
  • If changing Rust: set RUSTFLAGS or .cargo/config.toml (-C target-feature=+crt-static) or add static_vcruntime. [1][2][3]
  • Expose C ABI for cross-language calls; avoid sharing CRT-allocated resources. Test for memory/heap/CRT symbol issues.

References

  • Rust reference on CRT/linkage and crt-static. [1]
  • static_vcruntime crate docs (how to statically link vcruntime). [2][3]
  • Cargo/gcc crate docs (msvc static_crt and build-time flags) and guidance about /MT vs /MD. [5]

If you want, tell me:

  • whether your Rust toolchain is x86_64-pc-windows-msvc and currently using dynamic or static CRT, and
  • how the C++ library is built (/MD or /MT, DLL or static lib),
    and I’ll give exact Cargo config and build flags.

🏁 Script executed:

sed -n '120,165p' bindings/rust/build.rs

Repository: wysaid/CameraCapture

Length of output: 1608


Fix MSVC CRT mismatch: search path order contradicts comment and creates runtime risk.

The comment correctly states that Rust uses the release CRT by default on MSVC even in debug builds, but the code contradicts this. The search paths are added as build/{build_type} first, then Debug, then Release. When PROFILE=debug, the linker will find and link the Debug version of ccap first if it exists.

On MSVC, linking a Debug ccap library (built with /MDd) against a Rust binary using the release CRT (/MD) will cause heap and memory management errors at runtime.

To match the comment's intent, either reorder the search paths to always prioritize Release on MSVC targets, or update the comment to reflect the actual behavior.

🤖 Prompt for AI Agents
bindings/rust/build.rs around lines 153 to 158: the comment says MSVC should
always link the Release CRT but the search-path order currently allows a Debug
ccap to be found first; change the logic so that for MSVC targets the Release
build directory is added to cargo:rustc-link-search before any
Debug/build/{build_type} paths (i.e., prioritize build/Release before
build/Debug or build/{PROFILE}), so the linker always resolves to the Release
ccap on MSVC, and keep the comment consistent with this behavior.

Copy link

@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: 8

🤖 Fix all issues with AI agents
In @bindings/rust/Cargo.toml:
- Line 28: Update the bindgen dependency version in Cargo.toml from "0.68" to
"0.72.1" (or a newer stable release); locate the bindgen entry in Cargo.toml and
change its version string to "0.72.1", then run cargo update/build to ensure
compatibility and resolve any new warnings or API changes.

In @bindings/rust/RUST_INTEGRATION_SUMMARY.md:
- Around line 151-152: The docs and manifest disagree on the minimum Rust
version: RUST_INTEGRATION_SUMMARY.md lists "Rust 1.70+" while Cargo.toml sets
rust-version = "1.65"; choose one and make them consistent by either updating
Cargo.toml's rust-version field to "1.70" (if you want to require 1.70) or
changing the "### Build Requirements" line in RUST_INTEGRATION_SUMMARY.md to
"Rust 1.65+" (if you want to support 1.65); ensure both the rust-version key in
Cargo.toml and the Build Requirements header text match the chosen minimum
version.
- Around line 96-122: The example uses non-existent/incorrect APIs; update it to
use Provider::new() to create the provider, call Provider::find_device_names()
or Provider::get_devices() to enumerate devices, call
Provider::with_device(device_index: i32) with an integer index (not a device
ref), call provider.set_resolution(width: u32, height: u32) with two separate
parameters (not a Resolution struct), and handle provider.grab_frame(timeout_ms)
which returns Result<Option<VideoFrame>> by matching like if let Some(frame) =
provider.grab_frame(1000)? { ... } (also adjust any start/open calls to the
correct methods used in the README).

In @scripts/publish_rust_crate.sh:
- Around line 652-657: The script currently checks for a non-existent standalone
command `cargo-clippy` using require_cmd, causing clippy detection to fail;
update the check in the block that uses require_cmd so it verifies clippy by
running `cargo clippy --version` (or otherwise invoking `cargo clippy` with
--version) instead of `cargo-clippy`, so the subsequent `cargo clippy
--all-targets -- -D warnings` branch runs when clippy is actually available.
- Around line 239-246: The install_rustup function currently pipes curl to sh
which is insecure; change it to first detect existing Rust (check rustc or
cargo) and skip if present, otherwise download the rustup install script to a
temporary file (using curl -f -o) and verify it (e.g., checksum or signature) or
at minimum prompt/require explicit consent before executing; then source
"${HOME}/.cargo/env" after a successful install; update any failure paths
(require_cmd, die) to report the temporary file path and verification result so
the install flow for install_rustup is safe and auditable.
- Around line 318-324: The problem is that cratesio_has_exact_crate() passes a
regex-anchored string ("^${crate}$") to cargo search, but cargo search does
simple substring matching and treats ^/$ as literal, so it returns wrong
results; fix by querying cargo search with the plain crate name (e.g.,
"${crate}" or "${crate}" with --limit) and keep the existing grep -E "^${crate}
= \"" to perform the exact-match check; update the cargo search invocation
inside cratesio_has_exact_crate to remove the ^ and $ anchors so the downstream
grep can correctly detect exact crate names.
- Around line 326-332: cratesio_latest_version is vulnerable to unescaped regex
interpolation of ${crate}; change the cargo search call to use the literal crate
string (cargo search "$crate" 2>/dev/null || true) and avoid embedding ${crate}
into the sed regex by instead finding the exact line with a fixed literal match
and then extracting the version, e.g. pipe out into grep -F "${crate} =" | head
-n1 and then extract the quoted version with a safe sed/cut (e.g. sed -E
's/^[^"]*"([^"]+)".*$/\1/'); update the function cratesio_latest_version
accordingly so no shell regex/meta-characters from crate can alter matching.
🧹 Nitpick comments (4)
bindings/rust/Cargo.toml (1)

23-23: Consider specifying only required tokio features instead of "full".

The "full" feature enables all tokio components including net, fs, process, signal, etc. For camera capture bindings, you likely only need a subset like rt, rt-multi-thread, sync, and macros. This reduces compile times and binary size for users enabling the async feature.

Suggested minimal tokio features
-tokio = { version = "1.0", features = ["full"], optional = true }
+tokio = { version = "1.0", features = ["rt", "rt-multi-thread", "sync", "macros"], optional = true }
bindings/rust/build.rs (2)

194-197: Empty cfg block could be more explicit.

The empty #[cfg(target_arch = "aarch64")] block is technically correct since NEON is always available on aarch64, but it may confuse readers. Consider adding a flag or a more explicit comment.

Suggested improvement
             // Only add NEON flags on aarch64
             #[cfg(target_arch = "aarch64")]
             {
-                // NEON is always available on aarch64, no special flags needed
+                // NEON is always available on aarch64, no special compiler flags needed.
+                // The C++ source handles ENABLE_NEON_IMP based on architecture detection.
             }

287-290: For bindgen 0.70+ compatibility, migrate CargoCallbacks to the new API.

The project currently uses bindgen 0.68 where Box::new(bindgen::CargoCallbacks) works as-is. However, when upgrading to bindgen 0.70+, update to Box::new(bindgen::CargoCallbacks::new()), which enables rerun-on-input-header-files by default. If the old behavior is needed, call .rerun_on_header_files(false) after new().

bindings/rust/README.md (1)

96-111: Example commands reference capture_callback with async but async example is different.

Line 110 suggests running capture_callback with async feature, but the async usage example on lines 68-90 shows a different pattern using AsyncProvider. Consider clarifying which example demonstrates async functionality.

📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between e10615d and 76c7536.

⛔ Files ignored due to path filters (1)
  • bindings/rust/Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (7)
  • bindings/rust/Cargo.toml
  • bindings/rust/README.md
  • bindings/rust/RUST_INTEGRATION_SUMMARY.md
  • bindings/rust/build.rs
  • bindings/rust/build_and_test.sh
  • scripts/publish_rust_crate.sh
  • scripts/update_version.sh
🚧 Files skipped from review as they are similar to previous changes (1)
  • bindings/rust/build_and_test.sh
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-12-31T04:11:08.028Z
Learnt from: CR
Repo: wysaid/CameraCapture PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-12-31T04:11:08.028Z
Learning: Run `./scripts/update_version.sh <version>` to update version files

Applied to files:

  • scripts/update_version.sh
🧬 Code graph analysis (1)
scripts/publish_rust_crate.sh (1)
bindings/rust/src/error.rs (1)
  • fmt (65-86)
🪛 LanguageTool
bindings/rust/README.md

[style] ~187-~187: Consider using a less common alternative to make your writing sound more unique and professional.
Context: ...ontributing Contributions are welcome! Please feel free to submit a Pull Request. ## License Thi...

(FEEL_FREE_TO_STYLE_ME)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (20)
  • GitHub Check: Build (Release-gcc-static)
  • GitHub Check: Build (Release-gcc-shared)
  • GitHub Check: Build (Debug-gcc-static)
  • GitHub Check: Windows Build MinGW (Release-static)
  • GitHub Check: Windows Build VS2026 (Debug-shared)
  • GitHub Check: Windows Build VS2022 (Debug-shared)
  • GitHub Check: Build Fedora (Debug-shared)
  • GitHub Check: Build (Release-clang-static)
  • GitHub Check: Build ARM64 (Release-gcc-shared)
  • GitHub Check: Windows Build MinGW (Release-shared)
  • GitHub Check: Build Fedora (Release-shared)
  • GitHub Check: Windows Build MinGW (Debug-shared)
  • GitHub Check: Build ARM64 (Release-gcc-static)
  • GitHub Check: Build Fedora (Release-static)
  • GitHub Check: Windows Build MinGW (Debug-static)
  • GitHub Check: macOS Release - No File Playback
  • GitHub Check: Windows Build VS2022 (Release-static)
  • GitHub Check: Windows VS2022 Release - No File Playback
  • GitHub Check: macOS Build (Debug-static)
  • GitHub Check: Static Link (Dev) (windows-latest)
🔇 Additional comments (3)
scripts/update_version.sh (1)

106-117: Loop exits after first pattern match, potentially leaving one package entry outdated.

The break on line 117 causes the loop to exit after the first successful substitution. If both ccap-rs and ccap entries exist in the lockfile (e.g., during a transition period), only one will be updated. If both should be updated, remove the break:

 for pattern in patterns:
     new_text, n = re.subn(pattern, rf"\1{new_ver}\2", new_text, count=1)
     if n:
         count += n
-        break
bindings/rust/build.rs (1)

1-6: LGTM - Well-structured build script with robust source discovery.

The build script handles both vendored/packaged and development modes cleanly. The hierarchical source discovery (native/ → parent dirs → CCAP_SOURCE_DIR) aligns well with the publish workflow in publish_rust_crate.sh.

bindings/rust/README.md (1)

1-7: LGTM - Comprehensive and well-structured documentation.

The README provides clear installation instructions, usage examples for both sync and async patterns, API documentation, and platform support information. The badges and structure follow crates.io conventions well.

futures = { version = "0.3", optional = true }

[build-dependencies]
bindgen = "0.68"
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🌐 Web query:

latest version bindgen crate crates.io 2025 2026

💡 Result:

The latest bindgen crate version on crates.io as of January 11, 2026 is 0.72.1. (docs.rs)

Citations:


Update bindgen to the latest stable version.

bindgen = "0.68" is outdated. The latest version is 0.72.1, which includes improved Rust/Clang compatibility and bug fixes. Update to bindgen = "0.72.1" or later.

🤖 Prompt for AI Agents
In @bindings/rust/Cargo.toml at line 28, Update the bindgen dependency version
in Cargo.toml from "0.68" to "0.72.1" (or a newer stable release); locate the
bindgen entry in Cargo.toml and change its version string to "0.72.1", then run
cargo update/build to ensure compatibility and resolve any new warnings or API
changes.

Comment on lines 151 to 152
### Build Requirements
- Rust 1.70+
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Inconsistent Rust version requirement.

This document states "Rust 1.70+" but Cargo.toml specifies rust-version = "1.65". Please align the documentation with the actual minimum supported version.

Suggested fix
 ### Build Requirements
-- Rust 1.70+
+- Rust 1.65+
 - CMake 3.14+
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
### Build Requirements
- Rust 1.70+
### Build Requirements
- Rust 1.65+
- CMake 3.14+
🤖 Prompt for AI Agents
In @bindings/rust/RUST_INTEGRATION_SUMMARY.md around lines 151 - 152, The docs
and manifest disagree on the minimum Rust version: RUST_INTEGRATION_SUMMARY.md
lists "Rust 1.70+" while Cargo.toml sets rust-version = "1.65"; choose one and
make them consistent by either updating Cargo.toml's rust-version field to
"1.70" (if you want to require 1.70) or changing the "### Build Requirements"
line in RUST_INTEGRATION_SUMMARY.md to "Rust 1.65+" (if you want to support
1.65); ensure both the rust-version key in Cargo.toml and the Build Requirements
header text match the chosen minimum version.

Comment on lines +239 to +251
install_rustup() {
require_cmd curl || die "curl not found; please install curl first to auto-install Rust toolchain"

log "Installing Rust toolchain via rustup..."
curl https://sh.rustup.rs -sSf | sh -s -- -y
# shellcheck disable=SC1090
source "${HOME}/.cargo/env"
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Piping curl to shell is a security risk.

The curl ... | sh pattern downloads and executes arbitrary code. While this is the official rustup installation method, it's risky in automated scripts. Consider adding a verification step or providing an alternative for environments where Rust should already be installed.

Suggested safer approach
 install_rustup() {
     require_cmd curl || die "curl not found; please install curl first to auto-install Rust toolchain"
 
+    warn "This will download and execute the rustup installer from https://sh.rustup.rs"
+    warn "For security-conscious environments, install Rust manually: https://www.rust-lang.org/tools/install"
+    if ! prompt_yn "Proceed with automatic installation?" "Y"; then
+        die "Please install Rust manually and re-run this script."
+    fi
+
     log "Installing Rust toolchain via rustup..."
     curl https://sh.rustup.rs -sSf | sh -s -- -y
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
install_rustup() {
require_cmd curl || die "curl not found; please install curl first to auto-install Rust toolchain"
log "Installing Rust toolchain via rustup..."
curl https://sh.rustup.rs -sSf | sh -s -- -y
# shellcheck disable=SC1090
source "${HOME}/.cargo/env"
}
install_rustup() {
require_cmd curl || die "curl not found; please install curl first to auto-install Rust toolchain"
warn "This will download and execute the rustup installer from https://sh.rustup.rs"
warn "For security-conscious environments, install Rust manually: https://www.rust-lang.org/tools/install"
if ! prompt_yn "Proceed with automatic installation?" "Y"; then
die "Please install Rust manually and re-run this script."
fi
log "Installing Rust toolchain via rustup..."
curl https://sh.rustup.rs -sSf | sh -s -- -y
# shellcheck disable=SC1090
source "${HOME}/.cargo/env"
}
🤖 Prompt for AI Agents
In @scripts/publish_rust_crate.sh around lines 239 - 246, The install_rustup
function currently pipes curl to sh which is insecure; change it to first detect
existing Rust (check rustc or cargo) and skip if present, otherwise download the
rustup install script to a temporary file (using curl -f -o) and verify it
(e.g., checksum or signature) or at minimum prompt/require explicit consent
before executing; then source "${HOME}/.cargo/env" after a successful install;
update any failure paths (require_cmd, die) to report the temporary file path
and verification result so the install flow for install_rustup is safe and
auditable.

Comment on lines +318 to +329
cratesio_has_exact_crate() {
local crate="$1"
# cargo search output begins with: <name> = "<ver>" ...
local out
out="$(cargo search "^${crate}$" --limit 10 2>/dev/null || true)"
echo "${out}" | grep -E "^${crate} = \"" >/dev/null 2>&1
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

cargo search does not support regex anchors.

The ^${crate}$ pattern uses regex anchors, but cargo search performs a simple substring match and doesn't interpret regex. The ^ and $ are treated as literal characters, causing the search to likely return no results.

Suggested fix
 cratesio_has_exact_crate() {
     local crate="$1"
     # cargo search output begins with: <name> = "<ver>"  ...
     local out
-    out="$(cargo search "^${crate}$" --limit 10 2>/dev/null || true)"
-    echo "${out}" | grep -E "^${crate} = \"" >/dev/null 2>&1
+    out="$(cargo search "${crate}" --limit 20 2>/dev/null || true)"
+    # Match exact crate name at line start
+    echo "${out}" | grep -E "^${crate} = \"" >/dev/null 2>&1
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
cratesio_has_exact_crate() {
local crate="$1"
# cargo search output begins with: <name> = "<ver>" ...
local out
out="$(cargo search "^${crate}$" --limit 10 2>/dev/null || true)"
echo "${out}" | grep -E "^${crate} = \"" >/dev/null 2>&1
}
cratesio_has_exact_crate() {
local crate="$1"
# cargo search output begins with: <name> = "<ver>" ...
local out
out="$(cargo search "${crate}" --limit 20 2>/dev/null || true)"
# Match exact crate name at line start
echo "${out}" | grep -E "^${crate} = \"" >/dev/null 2>&1
}
🤖 Prompt for AI Agents
In @scripts/publish_rust_crate.sh around lines 318 - 324, The problem is that
cratesio_has_exact_crate() passes a regex-anchored string ("^${crate}$") to
cargo search, but cargo search does simple substring matching and treats ^/$ as
literal, so it returns wrong results; fix by querying cargo search with the
plain crate name (e.g., "${crate}" or "${crate}" with --limit) and keep the
existing grep -E "^${crate} = \"" to perform the exact-match check; update the
cargo search invocation inside cratesio_has_exact_crate to remove the ^ and $
anchors so the downstream grep can correctly detect exact crate names.

Comment on lines +326 to +337
cratesio_latest_version() {
local crate="$1"
local out
out="$(cargo search "^${crate}$" --limit 10 2>/dev/null || true)"
# Extract the first exact match version if present
echo "${out}" | sed -n -E "s/^${crate} = \"([^\"]+)\".*$/\1/p" | head -n 1
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Same regex issue in cratesio_latest_version.

The same ^${crate}$ issue exists here. Apply the same fix as suggested for cratesio_has_exact_crate.

Suggested fix
 cratesio_latest_version() {
     local crate="$1"
     local out
-    out="$(cargo search "^${crate}$" --limit 10 2>/dev/null || true)"
+    out="$(cargo search "${crate}" --limit 20 2>/dev/null || true)"
     # Extract the first exact match version if present
     echo "${out}" | sed -n -E "s/^${crate} = \"([^\"]+)\".*$/\1/p" | head -n 1
 }
🤖 Prompt for AI Agents
In @scripts/publish_rust_crate.sh around lines 326 - 332,
cratesio_latest_version is vulnerable to unescaped regex interpolation of
${crate}; change the cargo search call to use the literal crate string (cargo
search "$crate" 2>/dev/null || true) and avoid embedding ${crate} into the sed
regex by instead finding the exact line with a fixed literal match and then
extracting the version, e.g. pipe out into grep -F "${crate} =" | head -n1 and
then extract the quoted version with a safe sed/cut (e.g. sed -E
's/^[^"]*"([^"]+)".*$/\1/'); update the function cratesio_latest_version
accordingly so no shell regex/meta-characters from crate can alter matching.

Comment on lines +652 to +686
if require_cmd cargo-clippy; then
# Clippy on the crate's default feature set.
cargo clippy --all-targets -- -D warnings
else
warn "clippy not available; skipping clippy."
fi
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Incorrect clippy detection - cargo-clippy is not a standalone command.

Clippy is invoked as cargo clippy, not as cargo-clippy. The current check will likely always fail even when clippy is installed. Use cargo clippy --version to check availability.

Suggested fix
-    if require_cmd cargo-clippy; then
+    if cargo clippy --version >/dev/null 2>&1; then
         # Clippy on the crate's default feature set.
         cargo clippy --all-targets -- -D warnings
     else
         warn "clippy not available; skipping clippy."
     fi
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if require_cmd cargo-clippy; then
# Clippy on the crate's default feature set.
cargo clippy --all-targets -- -D warnings
else
warn "clippy not available; skipping clippy."
fi
if cargo clippy --version >/dev/null 2>&1; then
# Clippy on the crate's default feature set.
cargo clippy --all-targets -- -D warnings
else
warn "clippy not available; skipping clippy."
fi
🤖 Prompt for AI Agents
In @scripts/publish_rust_crate.sh around lines 652 - 657, The script currently
checks for a non-existent standalone command `cargo-clippy` using require_cmd,
causing clippy detection to fail; update the check in the block that uses
require_cmd so it verifies clippy by running `cargo clippy --version` (or
otherwise invoking `cargo clippy` with --version) instead of `cargo-clippy`, so
the subsequent `cargo clippy --all-targets -- -D warnings` branch runs when
clippy is actually available.

Comment on lines +94 to +96
# 5. Update Rust crate version (Cargo.toml)
update_file "s/^version = \".*\"$/version = \"$NEW_VERSION\"/" "$PROJECT_ROOT/bindings/rust/Cargo.toml" "Rust crate version (Cargo.toml)"

Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Sed pattern may match unintended version = lines in Cargo.toml.

The pattern ^version = ".*"$ will match any line starting with version =, which could include dependency version specifications if they happen to be on their own line. Consider using a more targeted approach similar to the Python script used for Cargo.lock, or ensure the pattern only matches within the [package] section.

Suggested fix using Python for consistency
-# 5. Update Rust crate version (Cargo.toml)
-update_file "s/^version = \".*\"$/version = \"$NEW_VERSION\"/" "$PROJECT_ROOT/bindings/rust/Cargo.toml" "Rust crate version (Cargo.toml)"
+# 5. Update Rust crate version (Cargo.toml)
+RUST_CARGO_TOML="$PROJECT_ROOT/bindings/rust/Cargo.toml"
+if [ -f "$RUST_CARGO_TOML" ]; then
+    python3 - "$RUST_CARGO_TOML" "$NEW_VERSION" <<'PY'
+import re, sys, pathlib
+
+path = pathlib.Path(sys.argv[1])
+new_ver = sys.argv[2]
+text = path.read_text()
+
+# Match version within [package] section only
+m = re.search(r'(?ms)^\[package\]\s*(.*?)(?=^\[|\Z)', text)
+if not m:
+    raise SystemExit("[package] section not found")
+
+pkg = m.group(0)
+new_pkg, n = re.subn(r'(?m)^version\s*=\s*"[^"]+"', f'version = "{new_ver}"', pkg, count=1)
+if n != 1:
+    raise SystemExit("Failed to update version in [package]")
+
+path.write_text(text[:m.start()] + new_pkg + text[m.end():])
+print(f"✅ Updated Rust crate version to {new_ver}")
+PY
+else
+    echo "⚠️  $RUST_CARGO_TOML not found, skipping Rust crate version update"
+fi

Copy link

@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: 10

🤖 Fix all issues with AI agents
In @bindings/rust/README.md:
- Line 24: The README's ccap dependency line uses version "1.5.0" which
mismatches the actual crate version in Cargo.toml
("1.5.0-test.20260111101345.e10615d"); update the README entries for ccap /
ccap-rs to use the exact version string from Cargo.toml (or alternatively update
Cargo.toml to the intended stable version) so both the README and Cargo.toml
specify the same version for ccap-rs.

In @bindings/rust/src/async.rs:
- Around line 30-39: The new() function on AsyncProvider is marked async but
performs no awaitable work (it constructs a SyncProvider, creates an mpsc
channel, wraps things in Arc/Mutex, and returns), so remove the async keyword
and make it a synchronous constructor returning Result<Self>; update its
signature from pub async fn new() -> Result<Self> to pub fn new() ->
Result<Self> and leave the body unchanged (ensure types still match
Result<Self>, and keep construction of provider: Arc::new(Mutex::new(provider)),
frame_receiver: Some(rx), and _frame_sender: tx).
- Around line 78-88: The grab_frame_timeout function casts timeout.as_millis()
to u32 which can overflow for durations > u32::MAX; add an explicit check before
casting in grab_frame_timeout (or clamp) and return a meaningful error (e.g.,
crate::CcapError::InvalidArgument or a new TimeoutOverflow variant) if the
duration exceeds u32::MAX, otherwise safely cast and proceed; reference the
grab_frame_timeout function, the timeout.as_millis() -> u32 conversion, and the
call to provider.grab_frame(timeout_ms) when implementing the fix.
- Around line 110-117: The method frame_stream currently calls
self.frame_receiver.take().expect(...), which will panic if called a second
time; change frame_stream to return a Result instead of panicking: update the
signature from pub fn frame_stream(&mut self) -> impl futures::Stream<Item =
VideoFrame> to return Result<impl Stream<Item = VideoFrame>, YourErrorType> (or
Box<dyn Stream<...>>/Pin<Box<...>> if needed), check self.frame_receiver.take()
and return an Err with a clear descriptive error if it is None, otherwise
construct and return the UnboundedReceiverStream from the receiver in Ok(...);
include a small, descriptive error type/message referencing
frame_stream/frame_receiver so callers can handle the duplicate-call case
gracefully.

In @bindings/rust/src/convert.rs:
- Around line 69-71: The calculation of dst_stride and dst_size can overflow for
extreme width/height values; replace the direct multiplies with checked
arithmetic (e.g., use checked_mul on width and height or cast to a wider integer
before multiplying) and bail out with an error or panic if either checked_mul
returns None; then only allocate dst_data after confirming dst_size fits in
usize. Update the logic around dst_stride, dst_size, and the allocation of
dst_data to use the checked results and return/propagate an error instead of
unconditionally creating the vector.

In @bindings/rust/src/error.rs:
- Line 96: Match arms for CcapErrorCode_CCAP_ERROR_INVALID_DEVICE and the
InvalidParameter variant create CcapError::InvalidDevice("".to_string()) / empty
strings which lose context; change those arms to include useful context by
formatting the original error code or message into the string (e.g.,
format!("invalid device: {:?}", err_code) or include the numeric/enum name) so
CcapError::InvalidDevice(...) and CcapError::InvalidParameter(...) carry the
code/message instead of an empty string.
- Around line 8-10: Remove the semantically confusing CcapError::None variant
and update the conversion logic so a CCAP_ERROR_NONE code is not mapped to an
Err variant; instead delete the None enum variant, change the From<i32> impl to
either panic with a clear message instructing callers to check success before
converting or replace it with a TryFrom<i32> implementation that returns
Result<CcapError, i32>/Result<(), CcapError> as appropriate, and update any call
sites that rely on From<i32> (or Err(CcapError::None)) to handle the OK case as
Ok(()) rather than an error.

In @bindings/rust/src/provider.rs:
- Around line 285-290: set_resolution currently calls set_property twice which
can leave the device half-updated if the second call fails; update
set_resolution to either validate both width and height before mutating (e.g.,
call a validation function or a non-mutating check using the same logic as
set_property) or perform the first set_property call and, if the second fails,
call set_property(PropertyName::Width, original_width) to restore the prior
value; reference the symbols set_resolution, set_property and
PropertyName::Width/PropertyName::Height and ensure any rollback handles errors
(propagating a combined error or the original failure) and returns a Result
reflecting the final state.
- Around line 211-227: open_device currently destroys an existing provider
handle without first releasing any registered frame callback, which can leak the
callback memory referenced by self.callback_ptr; before calling
sys::ccap_provider_destroy(self.handle) in open_device, call
self.cleanup_callback() (or the equivalent cleanup method) to unregister and
free the callback and clear self.callback_ptr, then proceed to destroy the
handle and recreate it, ensuring self.callback_ptr is nulled/updated after
cleanup to avoid dangling pointers.
🧹 Nitpick comments (8)
bindings/rust/README.md (2)

190-190: Minor: Consider rephrasing for conciseness.

The phrase "Please feel free to submit a Pull Request" is common but could be more direct. Consider: "Contributions via Pull Requests are welcome!"


120-130: Build script and CMake paths verified; consider documenting the optional Rust integration flag.

The script scripts/build_and_install.sh exists and is executable. The cmake commands are correct for building the C library from build/Debug, with ../.. properly resolving to the project root. However, the instructions should clarify that users can also integrate Rust bindings into the CMake build by adding -DCCAP_BUILD_RUST=ON to the cmake command, rather than always requiring a separate cargo build step. The current approach (separate C and Rust builds) is valid but this alternative integrated method is available.

bindings/rust/src/async.rs (1)

19-25: Consider removing incomplete streaming infrastructure.

The frame_receiver and _frame_sender are allocated in new() but never used since frame_stream() is deprecated and incomplete. This creates unnecessary overhead (channel allocation) for every AsyncProvider instance.

Consider either:

  1. Remove the channel fields and frame_stream() entirely until the feature is implemented
  2. Make the fields Option<T> and only allocate when frame_stream() is called
♻️ Option 1: Remove unused fields
 pub struct AsyncProvider {
     provider: Arc<Mutex<SyncProvider>>,
-    frame_receiver: Option<mpsc::UnboundedReceiver<VideoFrame>>,
-    /// Kept for future frame streaming implementation
-    #[allow(dead_code)]
-    _frame_sender: mpsc::UnboundedSender<VideoFrame>,
 }
bindings/rust/src/convert.rs (1)

93-119: Consider extracting common conversion logic.

The YUYV to RGB24/BGR24 functions share identical buffer validation, allocation, and structure. Consider extracting the common pattern to reduce duplication and improve maintainability.

This applies to most conversion functions in this module. A helper function could handle validation, allocation, and FFI call pattern.

bindings/rust/src/frame.rs (1)

153-179: Consider documenting FFI call overhead in convenience methods.

Each convenience method calls info(), which performs an FFI call. Users calling multiple properties (e.g., width(), height(), pixel_format()) will make redundant FFI calls. Consider documenting that calling info() once and accessing fields directly is more efficient for accessing multiple properties.

bindings/rust/src/provider.rs (3)

66-78: Consider more specific error for creation failure.

DeviceOpenFailed is returned when ccap_provider_create() fails, but creation failure is distinct from opening a device. Consider using InternalError or adding a ProviderCreationFailed error variant for clarity.


115-153: Performance consideration for device enumeration.

get_devices() creates a new Provider instance for each discovered device to retrieve detailed information (lines 138-148). This could be slow if device initialization is expensive. Consider documenting this cost or checking if the C API provides a more efficient batch query method.


155-193: Code duplication with DeviceInfo::from_c_struct.

This method manually converts CcapDeviceInfo to DeviceInfo, duplicating logic from DeviceInfo::from_c_struct() in frame.rs. Consider using the existing conversion method to reduce duplication.

♻️ Suggested refactor
     fn get_device_info_direct(&self) -> Result<DeviceInfo> {
         let mut device_info = sys::CcapDeviceInfo::default();
 
         let success = unsafe { sys::ccap_provider_get_device_info(self.handle, &mut device_info) };
 
         if !success {
             return Err(CcapError::DeviceOpenFailed);
         }
 
-        let name = unsafe {
-            let cstr = CStr::from_ptr(device_info.deviceName.as_ptr());
-            cstr.to_string_lossy().to_string()
-        };
-
-        let mut formats = Vec::new();
-        for i in 0..device_info.pixelFormatCount {
-            if i < device_info.supportedPixelFormats.len() {
-                formats.push(PixelFormat::from(device_info.supportedPixelFormats[i]));
-            }
-        }
-
-        let mut resolutions = Vec::new();
-        for i in 0..device_info.resolutionCount {
-            if i < device_info.supportedResolutions.len() {
-                let res = &device_info.supportedResolutions[i];
-                resolutions.push(Resolution {
-                    width: res.width,
-                    height: res.height,
-                });
-            }
-        }
-
-        Ok(DeviceInfo {
-            name,
-            supported_pixel_formats: formats,
-            supported_resolutions: resolutions,
-        })
+        DeviceInfo::from_c_struct(&device_info)
     }
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 76c7536 and 21e5358.

📒 Files selected for processing (6)
  • bindings/rust/README.md
  • bindings/rust/src/async.rs
  • bindings/rust/src/convert.rs
  • bindings/rust/src/error.rs
  • bindings/rust/src/frame.rs
  • bindings/rust/src/provider.rs
🧰 Additional context used
🧬 Code graph analysis (3)
bindings/rust/src/convert.rs (1)
bindings/rust/src/types.rs (2)
  • from_c_enum (62-64)
  • from_c_enum (197-207)
bindings/rust/src/provider.rs (4)
include/ccap_def.h (1)
  • DeviceInfo (425-442)
bindings/rust/src/error.rs (1)
  • from (85-111)
bindings/rust/src/types.rs (5)
  • from (35-52)
  • from (87-103)
  • from (116-126)
  • from (154-167)
  • from (220-225)
bindings/rust/src/frame.rs (7)
  • width (154-156)
  • height (159-161)
  • from_c_ptr (52-57)
  • info (87-138)
  • pixel_format (164-168)
  • from_raw (75-84)
  • from_c_ptr_ref (60-65)
bindings/rust/src/frame.rs (4)
include/ccap_def.h (1)
  • DeviceInfo (425-442)
bindings/rust/src/types.rs (7)
  • from_c_enum (62-64)
  • from_c_enum (197-207)
  • from (35-52)
  • from (87-103)
  • from (116-126)
  • from (154-167)
  • from (220-225)
bindings/rust/src/error.rs (1)
  • from (85-111)
bindings/rust/src/provider.rs (1)
  • pixel_format (369-372)
🪛 LanguageTool
bindings/rust/README.md

[style] ~190-~190: Consider using a less common alternative to make your writing sound more unique and professional.
Context: ...ontributing Contributions are welcome! Please feel free to submit a Pull Request. ## License Thi...

(FEEL_FREE_TO_STYLE_ME)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (20)
  • GitHub Check: Check Environment
  • GitHub Check: Build (Release-clang-static)
  • GitHub Check: Build (Release-gcc-shared)
  • GitHub Check: Build (Debug-gcc-static)
  • GitHub Check: Build (Release-gcc-static)
  • GitHub Check: Windows Build MinGW (Debug-shared)
  • GitHub Check: Windows Build VS2026 (Debug-shared)
  • GitHub Check: Windows Build MinGW (Release-static)
  • GitHub Check: Build Fedora (Release-shared)
  • GitHub Check: Windows Build VS2022 (Release-shared)
  • GitHub Check: Windows Build VS2026 (Release-static)
  • GitHub Check: Windows Build MinGW (Debug-static)
  • GitHub Check: Windows Build MinGW (Release-shared)
  • GitHub Check: Windows Build VS2022 (Debug-shared)
  • GitHub Check: Windows VS2022 Release - No File Playback
  • GitHub Check: Windows Build VS2022 (Release-static)
  • GitHub Check: Static Link (Dev) (macos-latest)
  • GitHub Check: Build Source (Dist) (windows-latest)
  • GitHub Check: Static Link (Dev) (windows-latest)
  • GitHub Check: Build Source (Dist) (macos-latest)
🔇 Additional comments (28)
bindings/rust/README.md (4)

185-185: The MSRV is already properly enforced in Cargo.toml with rust-version = "1.65", matching the README documentation. No action required.


69-89: Async feature and AsyncProvider implementation verified.

All points are confirmed:

  1. The async feature is defined in Cargo.toml and properly links to tokio, tokio-stream, and futures dependencies.
  2. AsyncProvider is correctly exported from the ccap::r#async module with a feature gate.
  3. All shown async methods exist with correct signatures: new(), find_device_names(), open(), and grab_frame().

163-171: All thread safety claims in the README are verified and accurate.

The actual implementation confirms:

  • VideoFrame has explicit unsafe impl Send and unsafe impl Sync with clear SAFETY comments (explaining frames are independent and all public methods are read-only)
  • Provider has explicit unsafe impl Send but no Sync implementation, with a comprehensive SAFETY comment that documents the C++ API is not thread-safe and external synchronization (e.g., Mutex) is required for concurrent access
  • AsyncProvider is properly implemented and referenced in documentation

The README's thread safety guidance is well-supported by the implementation and its safety documentation. No changes needed.


32-65: Public API exports are correctly implemented and match the usage example.

All symbols used in the code example (lines 32-65) are properly exported from the crate root: Provider, Result, and their methods find_device_names(), open_device(), grab_frame(), info(), and data() exist with matching signatures and return types. The example accurately reflects the actual API.

bindings/rust/src/error.rs (1)

114-115: LGTM!

Standard Result type alias following Rust conventions.

bindings/rust/src/async.rs (2)

42-75: LGTM!

Device operation methods properly lock the provider mutex and delegate to the synchronous API. The async wrappers provide appropriate concurrent access control.


125-129: Test will need update when new() becomes synchronous.

This test currently awaits AsyncProvider::new(), but as noted in a previous comment, new() should not be async. Update this test when that change is made.

bindings/rust/src/convert.rs (4)

9-20: LGTM!

Good defensive validation with clear error messages. This prevents buffer overruns when calling unsafe FFI functions.


23-53: LGTM!

Backend management and capability detection methods are straightforward wrappers around the C API with appropriate error handling.


185-222: LGTM!

NV12 to RGB24 conversion correctly handles the planar format with proper chroma plane size calculation using (height + 1) / 2 for odd heights.


268-308: LGTM!

I420 to RGB24 conversion correctly handles the three-plane YUV format with proper validation and chroma plane sizing. The too_many_arguments clippy allow is justified given the nature of planar format conversions.

bindings/rust/src/frame.rs (7)

15-43: LGTM!

Excellent defensive programming with min() to prevent array bounds violations when converting from C structures. String and enum conversions are properly error-handled.


45-84: LGTM!

Proper ownership tracking distinguishes between owned frames (from grab operations) and borrowed frames (from callbacks). This prevents double-free and use-after-free issues.


140-151: LGTM!

The data() method correctly returns the entire frame buffer using sizeInBytes from the C API, which accounts for all planes in the complete frame data.


181-189: LGTM!

Drop implementation correctly releases the frame only when owned, preventing double-free errors for callback-borrowed frames.


202-209: Verify Sync requirements align with C++ implementation.

unsafe impl Sync allows &VideoFrame to be shared across threads. This requires that all operations through &self are thread-safe at the C++ level. The WARNING correctly notes this is unverified.

Same verification as Send - review the C++ implementation. If the C++ CcapVideoFrame uses any thread-local storage, caches, or non-atomic reference counting, this Sync implementation would be unsound.


211-232: LGTM!

VideoFrameInfo correctly uses lifetime parameter to tie data plane slices to the VideoFrame borrow, preventing use-after-free. Public fields provide convenient access to frame metadata.


92-107: The plane size calculation is correct and safe for all supported pixel formats.

The C API guarantees that stride[1] and stride[2] are 0 for non-multi-plane formats (RGB, RGBA, YUYV, UYVY), and the Rust code properly handles this by checking stride values before calculating plane sizes. For YUV multi-plane formats (NV12, I420), the stride values are always set appropriately by the C library, and the calculation (height + 1) / 2 correctly handles both even and odd heights.

bindings/rust/src/provider.rs (10)

8-20: LGTM!

The SendSyncPtr wrapper with manual Send + Sync implementation is correctly used to store callback pointers in static storage. The SAFETY documentation clearly explains why this is sound.


22-63: Excellent thread safety documentation!

The documentation clearly explains the thread-safety limitations of the underlying C++ implementation and provides concrete examples of safe usage patterns. The Send (but not Sync) implementation with warnings is the correct choice.


302-334: LGTM with minor note.

Frame grabbing and capture control methods properly manage state. Note that stop_capture() doesn't check if the device is opened or capture is started, but this may be intentional for idempotent cleanup.


398-461: LGTM with consideration for poisoned mutex.

The error callback management correctly prevents memory leaks by cleaning up the old callback before setting a new one. The double-boxing pattern ensures pointer stability.

Note: If GLOBAL_ERROR_CALLBACK.lock() fails (poisoned mutex), the callback is silently not registered. This is probably acceptable since a poisoned mutex indicates a panic has already occurred.


463-491: LGTM - Correct cleanup pattern.

This method correctly calls cleanup_callback() before destroying the handle (line 473), preventing the memory leak that exists in open_device(). This is the pattern that should be followed consistently.


511-564: LGTM!

Frame callback management correctly uses from_c_ptr_ref() for borrowed frames in the callback context, preventing double-free. Cleanup on failure and old callback cleanup prevent memory leaks.


566-578: LGTM!

Callback removal correctly clears the C API callback before freeing the Rust-side memory.


580-592: LGTM!

The cleanup helper correctly reconstructs the Box to drop the callback. The SAFETY comment and type alias consistency note are important for maintaining this unsafe code.


595-607: LGTM!

Drop implementation follows the correct sequence: clean up callbacks first, then destroy the handle. This prevents memory leaks and use-after-free issues.


90-94: The concern about device open state is unfounded. The C API does automatically open the device during creation—both ccap_provider_create_with_device and ccap_provider_create_with_index invoke the parameterized Provider constructors (in src/ccap_core.cpp), which explicitly call open(). Setting is_opened = true in the Rust bindings is correct and matches the actual behavior.

Comment on lines 30 to 39
pub async fn new() -> Result<Self> {
let provider = SyncProvider::new()?;
let (tx, rx) = mpsc::unbounded_channel();

Ok(AsyncProvider {
provider: Arc::new(Mutex::new(provider)),
frame_receiver: Some(rx),
_frame_sender: tx,
})
}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

Remove unnecessary async from new().

The new() method performs no asynchronous operations. Marking it async adds unnecessary overhead and confusion. Make it a regular synchronous function.

♻️ Proposed fix
     /// Create a new async provider
-    pub async fn new() -> Result<Self> {
+    pub fn new() -> Result<Self> {
         let provider = SyncProvider::new()?;
         let (tx, rx) = mpsc::unbounded_channel();
 
         Ok(AsyncProvider {
             provider: Arc::new(Mutex::new(provider)),
             frame_receiver: Some(rx),
             _frame_sender: tx,
         })
     }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
pub async fn new() -> Result<Self> {
let provider = SyncProvider::new()?;
let (tx, rx) = mpsc::unbounded_channel();
Ok(AsyncProvider {
provider: Arc::new(Mutex::new(provider)),
frame_receiver: Some(rx),
_frame_sender: tx,
})
}
pub fn new() -> Result<Self> {
let provider = SyncProvider::new()?;
let (tx, rx) = mpsc::unbounded_channel();
Ok(AsyncProvider {
provider: Arc::new(Mutex::new(provider)),
frame_receiver: Some(rx),
_frame_sender: tx,
})
}
🤖 Prompt for AI Agents
In @bindings/rust/src/async.rs around lines 30 - 39, The new() function on
AsyncProvider is marked async but performs no awaitable work (it constructs a
SyncProvider, creates an mpsc channel, wraps things in Arc/Mutex, and returns),
so remove the async keyword and make it a synchronous constructor returning
Result<Self>; update its signature from pub async fn new() -> Result<Self> to
pub fn new() -> Result<Self> and leave the body unchanged (ensure types still
match Result<Self>, and keep construction of provider:
Arc::new(Mutex::new(provider)), frame_receiver: Some(rx), and _frame_sender:
tx).

Comment on lines 78 to 88
pub async fn grab_frame_timeout(&self, timeout: Duration) -> Result<Option<VideoFrame>> {
let provider = Arc::clone(&self.provider);
let timeout_ms = timeout.as_millis() as u32;

tokio::task::spawn_blocking(move || {
let mut provider = provider.blocking_lock();
provider.grab_frame(timeout_ms)
})
.await
.map_err(|e| crate::CcapError::InternalError(e.to_string()))?
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Potential timeout overflow with large durations.

Converting Duration to u32 milliseconds can overflow for durations longer than ~49.7 days. While unlikely in practice for frame grabbing, consider adding a check or documenting the limitation.

🔧 Suggested improvement
     pub async fn grab_frame_timeout(&self, timeout: Duration) -> Result<Option<VideoFrame>> {
         let provider = Arc::clone(&self.provider);
-        let timeout_ms = timeout.as_millis() as u32;
+        let timeout_ms = timeout.as_millis().min(u32::MAX as u128) as u32;
 
         tokio::task::spawn_blocking(move || {

Or document the limitation in a doc comment.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
pub async fn grab_frame_timeout(&self, timeout: Duration) -> Result<Option<VideoFrame>> {
let provider = Arc::clone(&self.provider);
let timeout_ms = timeout.as_millis() as u32;
tokio::task::spawn_blocking(move || {
let mut provider = provider.blocking_lock();
provider.grab_frame(timeout_ms)
})
.await
.map_err(|e| crate::CcapError::InternalError(e.to_string()))?
}
pub async fn grab_frame_timeout(&self, timeout: Duration) -> Result<Option<VideoFrame>> {
let provider = Arc::clone(&self.provider);
let timeout_ms = timeout.as_millis().min(u32::MAX as u128) as u32;
tokio::task::spawn_blocking(move || {
let mut provider = provider.blocking_lock();
provider.grab_frame(timeout_ms)
})
.await
.map_err(|e| crate::CcapError::InternalError(e.to_string()))?
}
🤖 Prompt for AI Agents
In @bindings/rust/src/async.rs around lines 78 - 88, The grab_frame_timeout
function casts timeout.as_millis() to u32 which can overflow for durations >
u32::MAX; add an explicit check before casting in grab_frame_timeout (or clamp)
and return a meaningful error (e.g., crate::CcapError::InvalidArgument or a new
TimeoutOverflow variant) if the duration exceeds u32::MAX, otherwise safely cast
and proceed; reference the grab_frame_timeout function, the timeout.as_millis()
-> u32 conversion, and the call to provider.grab_frame(timeout_ms) when
implementing the fix.

Comment on lines 110 to 117
pub fn frame_stream(&mut self) -> impl futures::Stream<Item = VideoFrame> {
let receiver = self
.frame_receiver
.take()
.expect("Frame stream can only be created once");

tokio_stream::wrappers::UnboundedReceiverStream::new(receiver)
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Deprecated method panics on second call.

The expect() on line 114 will panic if frame_stream() is called twice. While the method is deprecated and incomplete, panicking is still a poor user experience. Consider returning a Result with a descriptive error instead.

🔧 Suggested improvement
-    pub fn frame_stream(&mut self) -> impl futures::Stream<Item = VideoFrame> {
+    pub fn frame_stream(&mut self) -> Result<impl futures::Stream<Item = VideoFrame>> {
         let receiver = self
             .frame_receiver
             .take()
-            .expect("Frame stream can only be created once");
+            .ok_or_else(|| CcapError::InvalidParameter("Frame stream can only be created once".to_string()))?;
 
-        tokio_stream::wrappers::UnboundedReceiverStream::new(receiver)
+        Ok(tokio_stream::wrappers::UnboundedReceiverStream::new(receiver))
     }
🤖 Prompt for AI Agents
In @bindings/rust/src/async.rs around lines 110 - 117, The method frame_stream
currently calls self.frame_receiver.take().expect(...), which will panic if
called a second time; change frame_stream to return a Result instead of
panicking: update the signature from pub fn frame_stream(&mut self) -> impl
futures::Stream<Item = VideoFrame> to return Result<impl Stream<Item =
VideoFrame>, YourErrorType> (or Box<dyn Stream<...>>/Pin<Box<...>> if needed),
check self.frame_receiver.take() and return an Err with a clear descriptive
error if it is None, otherwise construct and return the UnboundedReceiverStream
from the receiver in Ok(...); include a small, descriptive error type/message
referencing frame_stream/frame_receiver so callers can handle the duplicate-call
case gracefully.

Comment on lines +69 to +71
let dst_stride = (width * 3) as usize;
let dst_size = dst_stride * height as usize;
let mut dst_data = vec![0u8; dst_size];
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Potential arithmetic overflow in buffer size calculation.

(width * 3) as usize and dst_stride * height as usize could theoretically overflow for extremely large dimensions. While unlikely in practice (would require GB+ allocations), consider checking for overflow or documenting the assumptions.

For context: u32::MAX * 3 as usize is safe on 64-bit platforms, but the subsequent multiplication by height could overflow usize on 32-bit systems with large images.

🤖 Prompt for AI Agents
In @bindings/rust/src/convert.rs around lines 69 - 71, The calculation of
dst_stride and dst_size can overflow for extreme width/height values; replace
the direct multiplies with checked arithmetic (e.g., use checked_mul on width
and height or cast to a wider integer before multiplying) and bail out with an
error or panic if either checked_mul returns None; then only allocate dst_data
after confirming dst_size fits in usize. Update the logic around dst_stride,
dst_size, and the allocation of dst_data to use the checked results and
return/propagate an error instead of unconditionally creating the vector.

Comment on lines +8 to +10
/// No error occurred
#[error("No error")]
None,
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Consider removing the None error variant.

The CcapError::None variant is semantically confusing in an error enum. An error type should only represent actual error conditions. Having Err(CcapError::None) in a Result contradicts the meaning of Err. If the C API returns a "no error" code, map it to Ok(()) instead of wrapping it in the error type.

♻️ Proposed refactor

Remove the None variant from CcapError and adjust the From<i32> implementation to handle CCAP_ERROR_NONE differently (e.g., panic with a message that the caller should check for success before converting to error, or use TryFrom instead).

 /// Error types for ccap operations
 #[derive(Debug, Error)]
 pub enum CcapError {
-    /// No error occurred
-    #[error("No error")]
-    None,
-
     /// No camera device found
     #[error("No camera device found")]
     NoDeviceFound,
🤖 Prompt for AI Agents
In @bindings/rust/src/error.rs around lines 8 - 10, Remove the semantically
confusing CcapError::None variant and update the conversion logic so a
CCAP_ERROR_NONE code is not mapped to an Err variant; instead delete the None
enum variant, change the From<i32> impl to either panic with a clear message
instructing callers to check success before converting or replace it with a
TryFrom<i32> implementation that returns Result<CcapError, i32>/Result<(),
CcapError> as appropriate, and update any call sites that rely on From<i32> (or
Err(CcapError::None)) to handle the OK case as Ok(()) rather than an error.

match code_u {
CcapErrorCode_CCAP_ERROR_NONE => CcapError::None,
CcapErrorCode_CCAP_ERROR_NO_DEVICE_FOUND => CcapError::NoDeviceFound,
CcapErrorCode_CCAP_ERROR_INVALID_DEVICE => CcapError::InvalidDevice("".to_string()),
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Empty context strings lose error information.

When mapping error codes to variants with context fields (InvalidDevice and InvalidParameter), empty strings are provided. This loses potentially useful debugging information. Consider including the error code in the context string.

🔧 Suggested improvement
-            CcapErrorCode_CCAP_ERROR_INVALID_DEVICE => CcapError::InvalidDevice("".to_string()),
+            CcapErrorCode_CCAP_ERROR_INVALID_DEVICE => CcapError::InvalidDevice(format!("error code {}", code)),
             CcapErrorCode_CCAP_ERROR_PROPERTY_SET_FAILED => {
-                CcapError::InvalidParameter("".to_string())
+                CcapError::InvalidParameter(format!("property set failed (code {})", code))
             }

Also applies to: 105-106

🤖 Prompt for AI Agents
In @bindings/rust/src/error.rs at line 96, Match arms for
CcapErrorCode_CCAP_ERROR_INVALID_DEVICE and the InvalidParameter variant create
CcapError::InvalidDevice("".to_string()) / empty strings which lose context;
change those arms to include useful context by formatting the original error
code or message into the string (e.g., format!("invalid device: {:?}", err_code)
or include the numeric/enum name) so CcapError::InvalidDevice(...) and
CcapError::InvalidParameter(...) carry the code/message instead of an empty
string.

Copy link

@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

🧹 Nitpick comments (2)
scripts/publish_rust_crate.sh (2)

112-176: TOML manipulation logic is correct.

The use of embedded Python scripts for TOML parsing is a pragmatic choice given Bash's limitations. The regex patterns and logic are sound.

💡 Optional: Consider extracting Python scripts to separate files

If this script grows further or requires more TOML manipulation, consider extracting the Python logic into separate .py files for easier maintenance and testing. However, the current approach is acceptable for the script's scope.


365-393: Consider improving cross-platform CPU count detection.

The nproc command is not available on macOS, though the fallback to 4 cores works. For better macOS support, consider detecting the platform and using sysctl -n hw.ncpu on Darwin.

🔧 Improved cross-platform CPU detection
-    cmake --build "${PROJECT_ROOT}/build/Debug" --config Debug --parallel "$(nproc 2>/dev/null || echo 4)"
+    local ncpu=4
+    if [[ "$(uname)" == "Darwin" ]]; then
+        ncpu="$(sysctl -n hw.ncpu 2>/dev/null || echo 4)"
+    else
+        ncpu="$(nproc 2>/dev/null || echo 4)"
+    fi
+    cmake --build "${PROJECT_ROOT}/build/Debug" --config Debug --parallel "${ncpu}"
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 21e5358 and b55352e.

📒 Files selected for processing (1)
  • scripts/publish_rust_crate.sh
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (19)
  • GitHub Check: macOS Build (Release-shared)
  • GitHub Check: Windows Build VS2022 (Debug-static)
  • GitHub Check: Windows Build VS2022 (Debug-shared)
  • GitHub Check: Windows Build VS2022 (Release-shared)
  • GitHub Check: Build (Release-gcc-static)
  • GitHub Check: Build Fedora (Debug-shared)
  • GitHub Check: Windows Build VS2026 (Debug-shared)
  • GitHub Check: macOS Release - No File Playback
  • GitHub Check: Windows Build MinGW (Release-static)
  • GitHub Check: Build (Release-clang-shared)
  • GitHub Check: Windows Build MinGW (Debug-shared)
  • GitHub Check: Build Fedora (Release-static)
  • GitHub Check: Windows Build VS2022 (Release-static)
  • GitHub Check: Build (Release-gcc-shared)
  • GitHub Check: Windows Build MinGW (Debug-static)
  • GitHub Check: Build (Debug-clang-static)
  • GitHub Check: Windows VS2022 Release - No File Playback
  • GitHub Check: Build (Release-clang-static)
  • GitHub Check: Static Link (Dev) (windows-latest)
🔇 Additional comments (13)
scripts/publish_rust_crate.sh (13)

1-18: LGTM: Script setup follows best practices.

The shebang, error handling (set -euo pipefail), and path resolution are all correctly implemented.


20-28: LGTM: Global variables are well-defined.

All variables have clear names and sensible default values.


30-68: LGTM: Comprehensive usage documentation.

The help text is clear, complete, and provides good guidance for users.


70-110: LGTM: Logging and prompting utilities are correctly implemented.

The functions handle both interactive and non-interactive modes properly, with appropriate error handling for EOF scenarios.


178-193: LGTM: Native source vendoring is correctly implemented.

The use of tar for directory copying preserves permissions and metadata, and the function includes appropriate existence checks.


195-237: LGTM: Mode selection and text prompting are well-implemented.

The interactive mode selection provides clear options and appropriate defaults for non-interactive use.


244-251: Rustup installation follows official method.

Downloading and executing the rustup installer is the standard installation approach. The use of HTTPS mitigates MITM risks, though users should be aware that this executes remote code.


273-363: LGTM: TOML readers, crates.io checks, and version utilities are correctly implemented.

The cargo search output parsing is necessarily fragile but represents the best available approach without direct API access. All version manipulation logic is sound.


395-403: LGTM: Login hint provides helpful user guidance.

The check for credentials files and associated warning message will help users diagnose authentication issues.


405-456: LGTM: Argument parsing is complete and correct.

All documented options are handled properly with appropriate error messages for invalid inputs.


481-499: LGTM: Auto-publish test version logic is correctly implemented.

The -y --publish combination correctly generates a test version with the format <version>-test.<timestamp>.<sha>, enabling CI/CD-friendly one-step test publishing as described in the PR objectives.


501-566: Excellent safety checks for publish mode and default features.

The logic correctly:

  • Prevents stable releases from non-main branches
  • Detects when static-link is in default features (not crates.io-friendly)
  • Offers automatic remediation with build-source as the default
  • Differentiates between dry-run and actual publish scenarios

These guards help prevent common publishing mistakes.


568-762: LGTM: Version management, vendoring, testing, and publish workflow are comprehensive and correct.

This segment implements robust logic for:

  • Enforcing pre-release versions on non-main branches
  • Auto-vendoring native sources for self-contained crate distribution
  • Running both static-link (dev) and build-source (distribution) tests
  • Validating crates.io packaging requirements
  • Skipping confirmation prompts for auto-publish mode (PR objective: -y --publish)
  • Cleaning up auto-generated artifacts appropriately

The comment at lines 689-692 correctly explains the need for --no-default-features when testing static-link in isolation, and the implementation at line 706 follows this guidance correctly.

Copy link

@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: 3

🤖 Fix all issues with AI agents
In @scripts/README.md:
- Around line 75-99: Update the "Directory Structure" section of
scripts/README.md to list the new test_remote_crate.sh script, i.e., add an
entry for test_remote_crate.sh (description: "Remote Rust crate verification
script") alongside the other script entries in the scripts/ directory listing so
the directory tree includes test_remote_crate.sh and matches the examples and
verification scope documented earlier.

In @scripts/test_remote_crate.sh:
- Around line 99-110: The current extraction of actual_version uses `cargo
metadata --no-deps`, which omits dependencies so `ccap-rs` is often missing;
change the command used to populate the actual_version variable to call `cargo
metadata --format-version=1` without `--no-deps` (i.e., remove the `--no-deps`
flag) so dependency packages are included, keep the same JSON parsing pipeline
and redirections, and leave the `actual_version` variable and `log "Testing
ccap-rs version: ${actual_version}"` intact.
🧹 Nitpick comments (4)
scripts/README.md (1)

117-123: Consider updating usage recommendations for the new script.

The "Usage Recommendations" section does not mention the new test_remote_crate.sh script. Consider adding guidance on when to use it (e.g., "After release, test the published Rust crate from crates.io using test_remote_crate.sh").

scripts/test_remote_crate.sh (3)

68-71: Add a safety guard before rm -rf "${TEST_DIR}"
Right now ${TEST_DIR} is derived safely, but a small defensive check prevents catastrophic deletion if path computation ever regresses.

Proposed diff
     log "Version: ${VERSION}"
     log "Test directory: ${TEST_DIR}"

     # Clean up any existing test directory
-    rm -rf "${TEST_DIR}"
+    if [[ -z "${TEST_DIR}" || "${TEST_DIR}" == "/" || "${TEST_DIR}" == "${PROJECT_ROOT}" ]]; then
+        die "Refusing to delete unsafe TEST_DIR='${TEST_DIR}'"
+    fi
+    rm -rf "${TEST_DIR}"
     mkdir -p "${TEST_DIR}"

51-63: “latest” version resolution via cargo search | grep is brittle; prefer crates.io API
Parsing cargo search output is not a stable interface and can misbehave across cargo versions/local registry state. Consider using the crates.io HTTP API (you already depend on python3).

Sketch of a more deterministic approach
     if [[ "${VERSION}" == "latest" ]]; then
         log "Fetching latest version from crates.io..."
         local latest_version
-        latest_version=$(cargo search ccap-rs --limit 1 2>/dev/null | grep -oE '[0-9]+\.[0-9]+\.[0-9]+(-[a-zA-Z0-9.]+)?' | head -1 || echo "")
+        latest_version=$(
+          curl -fsSL "https://crates.io/api/v1/crates/ccap-rs" |
+          python3 -c "import sys, json; print(json.load(sys.stdin)['crate']['max_version'])" 2>/dev/null || echo ""
+        )

348-375: Don’t discard build/run output on failure
--quiet 2>&1 makes failures hard to diagnose (especially in CI). Consider capturing logs and printing them on error.

Proposed diff
     log "Building test project..."
-    if ! cargo build --quiet 2>&1; then
+    build_log="$(mktemp)"
+    if ! cargo build >"${build_log}" 2>&1; then
         error "Build failed"
+        cat "${build_log}" >&2 || true
         return 1
     fi
+    rm -f "${build_log}" || true
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b55352e and 5af0d76.

📒 Files selected for processing (2)
  • scripts/README.md
  • scripts/test_remote_crate.sh
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (18)
  • GitHub Check: Windows Build VS2022 (Release-shared)
  • GitHub Check: Windows Build MinGW (Debug-static)
  • GitHub Check: Windows Build VS2022 (Debug-shared)
  • GitHub Check: Windows Build VS2022 (Debug-static)
  • GitHub Check: Build Fedora (Debug-shared)
  • GitHub Check: Build ARM64 (Release-gcc-shared)
  • GitHub Check: Build Fedora (Release-static)
  • GitHub Check: Windows Build MinGW (Debug-shared)
  • GitHub Check: Build (Debug-gcc-static)
  • GitHub Check: Build (Release-gcc-shared)
  • GitHub Check: Build Fedora (Release-shared)
  • GitHub Check: Build Source (Dist) (windows-latest)
  • GitHub Check: Build (Release-clang-shared)
  • GitHub Check: Build (Release-gcc-static)
  • GitHub Check: Static Link (Dev) (macos-latest)
  • GitHub Check: Static Link (Dev) (windows-latest)
  • GitHub Check: Build Source (Dist) (macos-latest)
  • GitHub Check: macOS Build (Release-static)

Copy link

@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

🧹 Nitpick comments (7)
bindings/rust/src/frame.rs (2)

140-151: Consider documenting contiguous memory assumption.

The data() method assumes frame data is stored contiguously starting at data[0] for sizeInBytes bytes. For planar formats (NV12, I420) where planes might not be contiguous in memory, this could create an invalid slice.

If the C library guarantees contiguous allocation, a brief doc comment would clarify this contract.

📝 Suggested documentation
-    /// Get all frame data as a slice
+    /// Get all frame data as a contiguous slice.
+    ///
+    /// # Note
+    /// This assumes the underlying frame data is stored contiguously in memory.
+    /// For planar formats, use [`info()`] to access individual planes if they
+    /// may be non-contiguous.
     pub fn data(&self) -> crate::error::Result<&[u8]> {

153-178: Consider performance note for multiple accessor calls.

Each convenience method (width(), height(), etc.) calls info() which performs an FFI call. Callers using multiple accessors in sequence (e.g., frame.width() then frame.height()) will incur repeated FFI overhead. Consider adding a doc note suggesting users call info() once when multiple properties are needed.

bindings/rust/src/async.rs (2)

30-64: Consider spawn_blocking for potentially slow operations.

Methods like open() (device initialization) may take significant time and currently hold the async mutex lock while making blocking FFI calls. This could starve other tasks waiting on the mutex. Consider using spawn_blocking similar to grab_frame_timeout for operations that may block.

Not critical since camera operations typically need serialization, but worth considering for responsiveness.


66-77: Duration to u32 truncation.

Line 69 casts timeout.as_millis() (u128) to u32, which silently truncates timeouts exceeding ~49.7 days. While practically unlikely, consider using try_into() or saturating conversion for robustness.

♻️ Suggested fix
-        let timeout_ms = timeout.as_millis() as u32;
+        let timeout_ms = timeout.as_millis().min(u32::MAX as u128) as u32;
bindings/rust/README.md (1)

119-123: Fix nested list indentation for markdown consistency.

Nested list items should use 2-space indentation instead of 4 spaces to comply with markdown best practices (MD007).

📝 Proposed formatting fix
 - **Distribution mode (default):** `build-source`
-    - Builds the native C/C++ implementation via the `cc` crate.
-    - Intended for crates.io users.
+  - Builds the native C/C++ implementation via the `cc` crate.
+  - Intended for crates.io users.
 - **Development mode:** `static-link`
-    - Links against a pre-built native library from a CameraCapture checkout (e.g. `build/Debug/libccap.a`).
-    - Intended for developing this repository.
+  - Links against a pre-built native library from a CameraCapture checkout (e.g. `build/Debug/libccap.a`).
+  - Intended for developing this repository.
 - You can disable the auto-link behavior by setting:
-    - `CCAP_RUST_NO_ASAN_LINK=1`
+  - `CCAP_RUST_NO_ASAN_LINK=1`

Also applies to: 167-167

bindings/rust/build.rs (2)

163-163: Fix trailing whitespace.

Line 163 has trailing whitespace after the panic message string.

🧹 Proposed fix
-Please vendor the sources into bindings/rust/native/, or set CCAP_SOURCE_DIR to a CameraCapture checkout." 
+Please vendor the sources into bindings/rust/native/, or set CCAP_SOURCE_DIR to a CameraCapture checkout."

274-319: ASan detection and runtime linking logic is well-implemented; consider testing with ASan-instrumented builds in CI.

The code correctly detects ASan-instrumented prebuilt libraries and links the appropriate runtime:

  • Symbol detection (lines 286-287): Searches for both ___asan_init and __asan_init. The dual-symbol check is practical and defensive, though the rationale for checking both variants could be documented.
  • macOS runtime linking (lines 300-312): Gracefully handles missing clang toolchain (checks dylib.exists() before using rpath) and includes a fallback link with -lclang_rt.asan_osx_dynamic.
  • Linux runtime linking (lines 295-298): Straightforward -lasan link with clear documentation that libasan6 (or equivalent) must be installed.
  • User override: Properly supports CCAP_RUST_NO_ASAN_LINK environment variable with correct rerun-if-env-changed setup.

The Rust CI workflow currently tests static-link mode but does not build the C library with ASan enabled. While functional tests in run_tests.sh enable ASan by default, consider adding a CI job that explicitly builds with -DASAN=ON and tests the Rust bindings against it to validate the detection and linking logic end-to-end.

📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 5af0d76 and 4a484fa.

⛔ Files ignored due to path filters (1)
  • bindings/rust/Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (8)
  • .github/workflows/rust.yml
  • CMakeLists.txt
  • bindings/rust/Cargo.toml
  • bindings/rust/README.md
  • bindings/rust/build.rs
  • bindings/rust/examples/minimal_example.rs
  • bindings/rust/src/async.rs
  • bindings/rust/src/frame.rs
✅ Files skipped from review due to trivial changes (1)
  • bindings/rust/examples/minimal_example.rs
🚧 Files skipped from review as they are similar to previous changes (3)
  • bindings/rust/Cargo.toml
  • .github/workflows/rust.yml
  • CMakeLists.txt
🧰 Additional context used
🧬 Code graph analysis (2)
bindings/rust/src/frame.rs (3)
include/ccap_def.h (1)
  • DeviceInfo (425-442)
bindings/rust/src/types.rs (5)
  • from (35-52)
  • from (87-103)
  • from (116-126)
  • from (154-167)
  • from (220-225)
bindings/rust/src/error.rs (1)
  • from (85-111)
bindings/rust/src/async.rs (1)
bindings/rust/src/provider.rs (8)
  • new (67-78)
  • find_device_names (357-359)
  • open (196-208)
  • start (248-250)
  • stop (253-255)
  • is_opened (258-260)
  • is_started (243-245)
  • grab_frame (303-314)
🪛 GitHub Check: Static Link (Dev) (ubuntu-latest)
bindings/rust/build.rs

[warning] 277-277:
Diff in /home/runner/work/CameraCapture/CameraCapture/bindings/rust/build.rs

bindings/rust/src/async.rs

[warning] 80-80:
Diff in /home/runner/work/CameraCapture/CameraCapture/bindings/rust/src/async.rs

🪛 LanguageTool
bindings/rust/README.md

[style] ~219-~219: Consider using a less common alternative to make your writing sound more unique and professional.
Context: ...ontributing Contributions are welcome! Please feel free to submit a Pull Request. ## License Thi...

(FEEL_FREE_TO_STYLE_ME)

🪛 markdownlint-cli2 (0.18.1)
bindings/rust/README.md

119-119: Unordered list indentation
Expected: 2; Actual: 4

(MD007, ul-indent)


120-120: Unordered list indentation
Expected: 2; Actual: 4

(MD007, ul-indent)


122-122: Unordered list indentation
Expected: 2; Actual: 4

(MD007, ul-indent)


123-123: Unordered list indentation
Expected: 2; Actual: 4

(MD007, ul-indent)


167-167: Unordered list indentation
Expected: 2; Actual: 4

(MD007, ul-indent)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (20)
  • GitHub Check: Build (Release-clang-static)
  • GitHub Check: macOS Release - No File Playback
  • GitHub Check: Windows Build MinGW (Release-static)
  • GitHub Check: Build (Debug-clang-static)
  • GitHub Check: macOS Build (Debug-shared)
  • GitHub Check: Build (Release-clang-shared)
  • GitHub Check: Build ARM64 (Release-gcc-static)
  • GitHub Check: Build ARM64 (Release-gcc-shared)
  • GitHub Check: Build Fedora (Debug-shared)
  • GitHub Check: Windows Build MinGW (Debug-static)
  • GitHub Check: Build (Release-gcc-static)
  • GitHub Check: Build (Debug-gcc-static)
  • GitHub Check: Build ARM64 (Debug-gcc-static)
  • GitHub Check: Build Source (Dist) (windows-latest)
  • GitHub Check: Windows Build MinGW (Debug-shared)
  • GitHub Check: Windows Build VS2022 (Release-static)
  • GitHub Check: Windows Build VS2022 (Release-shared)
  • GitHub Check: Windows Build MinGW (Release-shared)
  • GitHub Check: Windows Build VS2022 (Debug-shared)
  • GitHub Check: Windows VS2022 Release - No File Playback
🔇 Additional comments (17)
bindings/rust/src/frame.rs (5)

4-43: LGTM!

The DeviceInfo struct and its from_c_struct implementation are well-designed. Good defensive programming with the .min() bounds checks on lines 25 and 31 to prevent buffer overreads if the count fields are inconsistent with array sizes.


45-84: LGTM!

The ownership model using owns_frame flag is appropriate for FFI scenarios where some frames are owned (from grab) and others are borrowed (from callbacks). The null-check in from_raw is a good safety measure.


181-200: LGTM!

The Drop implementation correctly respects the owns_frame flag. The unsafe impl Send is well-documented with clear safety rationale and an honest note about the C++ library's thread-safety guarantees.


202-223: LGTM!

The VideoFrameInfo struct properly uses lifetime 'a to tie the borrowed slices to the VideoFrame, preventing use-after-free. The optional planes handle both planar and packed formats cleanly.


93-106: The plane size calculation correctly handles all supported pixel formats. The C library properly sets stride[1] and stride[2] to 0 for packed formats like YUYV/UYVY, and the height/2 factor is correct for the supported 4:2:0 formats (NV12, I420). No code changes needed.

bindings/rust/src/async.rs (3)

14-28: LGTM!

Using Arc<Mutex<SyncProvider>> with tokio's async Mutex is appropriate for sharing the provider across async tasks. The new() method correctly propagates errors from the underlying sync provider creation.


86-113: LGTM!

The async tests appropriately handle the case where no camera hardware is available. The test_async_find_devices gracefully handles both success and error cases, which is suitable for CI environments without camera access.


79-82: No action required - implementation is correct.

The 0ms timeout semantics are verified: when timeoutInMs == 0, the C library immediately returns without waiting (condition if (m_availableFrames.empty() && timeoutInMs > 0) in src/ccap_imp.cpp:137 skips the wait logic). This correctly implements non-blocking behavior. The Rust binding at lines 79-82 properly delegates to this behavior.

bindings/rust/README.md (3)

1-28: LGTM! Clear documentation and quick start.

The header and quick start sections are well-structured. The package aliasing (package = "ccap-rs"ccap) provides a convenient shorter import name while maintaining the full crate name on crates.io.


193-200: Excellent thread-safety documentation.

The thread-safety section provides clear guidance on how to safely use the API across threads, with honest disclosure about the C++ layer's thread-safety being based on code inspection. The recommendations to use Arc<Mutex<Provider>> or AsyncProvider are appropriate.


30-110: Well-structured examples with correct syntax.

The basic and async usage examples demonstrate the API clearly. The use of r#async for the async module import is the correct raw identifier syntax for reserved keywords.

bindings/rust/build.rs (6)

6-14: LGTM! Robust byte pattern search.

The function includes good defensive checks (empty needle, file read errors) and uses an efficient sliding window approach for pattern matching.


16-54: Well-designed cross-platform clang discovery.

The function attempts multiple strategies (PATH, xcrun on macOS) with appropriate fallbacks. Good handling of command failures and empty output.


56-73: LGTM! Clear validation and discovery logic.

The root validation checks for key files, and the parent directory walk with a reasonable limit (16) handles both development and cargo publish scenarios well.


243-261: NEON build always compiles for symbol availability.

The NEON source file is compiled on all architectures to provide the hasNEON() symbol, with the implementation returning false on non-ARM via ENABLE_NEON_IMP=0. This is a reasonable trade-off for build simplicity, though it adds a small compilation cost on non-ARM platforms.


346-374: Platform-specific linking is correctly configured.

System frameworks and libraries are properly linked for each platform:

  • macOS: Apple frameworks + Accelerate for SIMD
  • Linux: stdc++ (v4l2 correctly commented out due to availability concerns)
  • Windows: DirectShow and Media Foundation libraries

426-446: Bindgen configuration is well-structured.

The bindgen setup correctly:

  • Uses wrapper.h with proper include paths
  • Applies appropriate allowlists for ccap_*, Ccap*, and CCAP_* identifiers
  • Derives useful traits (Default, Debug, PartialEq, Eq)
  • Integrates with cargo via CargoCallbacks

Copy link

@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: 4

🤖 Fix all issues with AI agents
In @bindings/rust/README.md:
- Around line 129-133: Fix the Markdown list indentation by replacing 4-space
indents with 2-space indents for the bullet lists noted in
bindings/rust/README.md (affecting the block around lines 129–133 and the lists
at 177, 183, and 189–191); update the indented list lines inside the fenced code
blocks so each list item uses two leading spaces before the list marker (e.g., "
- ..." or "  +" as appropriate) and ensure the surrounding triple-backtick
fences and surrounding text remain unchanged so MD007 is satisfied.
- Line 38: Replace the bare URL "https://crates.io/crates/ccap-rs" with a
Markdown link to satisfy MD034; update the README line that currently shows the
raw URL so it becomes something like a descriptive link (e.g.,
[crates.io/crates/ccap-rs](https://crates.io/crates/ccap-rs)) to ensure the URL
is no longer bare.

In @README.md:
- Around line 215-216: Replace the two bare URLs on the README (the lines
currently showing "Crate: https://crates.io/crates/ccap-rs" and "Docs:
https://docs.rs/ccap-rs") with markdown link syntax; e.g. convert the crate URL
into a descriptive link label like "Crate:
[ccap-rs](https://crates.io/crates/ccap-rs)" and the docs URL into "Docs:
[ccap-rs](https://docs.rs/ccap-rs)" so the lines use proper [text](url) format
and satisfy MD034.
🧹 Nitpick comments (4)
docs/content/rust-bindings.md (1)

75-77: Minor clarification opportunity for environment variable setup.

Line 77 mentions setting CCAP_SOURCE_DIR but could be slightly more explicit that this is an environment variable. Consider rephrasing to: "If needed, set the environment variable:" for improved clarity.

📝 Suggested wording improvement
-If needed, set:
+If needed, set the environment variable:
scripts/test_remote_crate.sh (3)

79-110: Consider using cargo add for simplicity.

The embedded Python script for Cargo.toml manipulation works but is complex. Since Rust 1.62+, cargo add is available and would simplify this:

cargo add ccap-rs@"${VERSION}"

This would eliminate the need for manual TOML parsing and handle edge cases automatically.

🔧 Alternative implementation using cargo add
-    # Add dependency to Cargo.toml
-    log "Adding ccap-rs dependency (version: ${VERSION})..."
-    python3 - "${VERSION}" <<'PY'
-import re
-import sys
-from pathlib import Path
-
-version = sys.argv[1]
-path = Path("Cargo.toml")
-text = path.read_text(encoding="utf-8")
-
-dep_line = f'ccap-rs = "{version}"\n'
-
-# Find the [dependencies] section (until the next [section] or EOF)
-m = re.search(r'(?ms)^\[dependencies\]\s*\n(.*?)(^\[|\Z)', text)
-
-if m:
-    body = m.group(1)
-    # Replace existing ccap-rs entry if present; otherwise insert at top of the section.
-    if re.search(r'(?m)^ccap-rs\s*=\s*"[^"]*"\s*$', body):
-        body2 = re.sub(r'(?m)^ccap-rs\s*=\s*"[^"]*"\s*$', dep_line.rstrip("\n"), body, count=1)
-    else:
-        body2 = dep_line + body
-    text = text[: m.start(1)] + body2 + text[m.end(1) :]
-else:
-    # No [dependencies] section - append one.
-    if not text.endswith("\n"):
-        text += "\n"
-    text += "\n[dependencies]\n" + dep_line
-
-path.write_text(text, encoding="utf-8")
-PY
+    # Add dependency using cargo add (available since Rust 1.62)
+    log "Adding ccap-rs dependency (version: ${VERSION})..."
+    cargo add "ccap-rs@${VERSION}" --quiet

Note: Verify that cargo add is available in your minimum supported Rust version.


194-194: Consider making camera initialization delay configurable.

The hardcoded 500ms sleep may be insufficient on slower systems or overly conservative on faster ones. Consider making this configurable via an environment variable for flexibility in different testing environments.

♻️ Proposed enhancement

Add near the top of the Rust test code:

// Allow configuring camera initialization delay via environment variable
let init_delay = std::env::var("CCAP_CAMERA_INIT_DELAY_MS")
    .ok()
    .and_then(|s| s.parse().ok())
    .unwrap_or(500);

std::thread::sleep(std::time::Duration::from_millis(init_delay));

367-374: Preserve build output for better debugging experience.

The --quiet flag suppresses all compiler output, making it difficult to diagnose build failures. Consider showing the full output or at least capturing and displaying it on failure.

📋 Suggested improvement
     # Build the test
     log "Building test project..."
-    if ! cargo build --quiet 2>&1; then
-        error "Build failed"
+    if ! cargo build 2>&1; then
+        error "Build failed - see output above for details"
         return 1
     fi

Or, if you prefer to keep quiet mode but show output on failure:

     log "Building test project..."
-    if ! cargo build --quiet 2>&1; then
-        error "Build failed"
+    if ! build_output=$(cargo build 2>&1); then
+        error "Build failed:"
+        echo "${build_output}" >&2
         return 1
+    else
+        log "Build successful"
     fi
-
-    log "Build successful"
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 4a484fa and 6fce988.

📒 Files selected for processing (17)
  • README.md
  • README.zh-CN.md
  • bindings/rust/Cargo.toml
  • bindings/rust/README.md
  • docs/content/cli.md
  • docs/content/cli.zh.md
  • docs/content/cmake-options.md
  • docs/content/cmake-options.zh.md
  • docs/content/documentation.md
  • docs/content/documentation.zh.md
  • docs/content/implementation-details.md
  • docs/content/implementation-details.zh.md
  • docs/content/rust-bindings.md
  • docs/content/rust-bindings.zh.md
  • docs/documentation.html
  • docs/index.html
  • scripts/test_remote_crate.sh
✅ Files skipped from review due to trivial changes (3)
  • docs/content/documentation.zh.md
  • docs/content/cmake-options.zh.md
  • docs/index.html
🚧 Files skipped from review as they are similar to previous changes (1)
  • bindings/rust/Cargo.toml
🧰 Additional context used
📓 Path-based instructions (1)
docs/**/*.md

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

.md files in docs/ must be English and require review before publishing

Files:

  • docs/content/cli.md
  • docs/content/cmake-options.md
  • docs/content/implementation-details.zh.md
  • docs/content/implementation-details.md
  • docs/content/rust-bindings.md
  • docs/content/cli.zh.md
  • docs/content/documentation.md
  • docs/content/rust-bindings.zh.md
🧠 Learnings (1)
📚 Learning: 2025-12-31T04:11:08.028Z
Learnt from: CR
Repo: wysaid/CameraCapture PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-12-31T04:11:08.028Z
Learning: Respond in user's language (except `docs/` which must be English)

Applied to files:

  • docs/documentation.html
🪛 LanguageTool
bindings/rust/README.md

[style] ~244-~244: Consider using a less common alternative to make your writing sound more unique and professional.
Context: ...ontributing Contributions are welcome! Please feel free to submit a Pull Request. ## License Thi...

(FEEL_FREE_TO_STYLE_ME)

🪛 markdownlint-cli2 (0.18.1)
README.md

215-215: Bare URL used

(MD034, no-bare-urls)


216-216: Bare URL used

(MD034, no-bare-urls)

bindings/rust/README.md

38-38: Bare URL used

(MD034, no-bare-urls)


129-129: Unordered list indentation
Expected: 2; Actual: 4

(MD007, ul-indent)


130-130: Unordered list indentation
Expected: 2; Actual: 4

(MD007, ul-indent)


132-132: Unordered list indentation
Expected: 2; Actual: 4

(MD007, ul-indent)


133-133: Unordered list indentation
Expected: 2; Actual: 4

(MD007, ul-indent)


177-177: Unordered list indentation
Expected: 2; Actual: 4

(MD007, ul-indent)


183-183: Unordered list indentation
Expected: 2; Actual: 4

(MD007, ul-indent)


189-189: Unordered list indentation
Expected: 2; Actual: 4

(MD007, ul-indent)


190-190: Unordered list indentation
Expected: 2; Actual: 4

(MD007, ul-indent)


191-191: Unordered list indentation
Expected: 2; Actual: 4

(MD007, ul-indent)

docs/content/rust-bindings.zh.md

53-53: Emphasis used instead of a heading

(MD036, no-emphasis-as-heading)


70-70: Emphasis used instead of a heading

(MD036, no-emphasis-as-heading)


88-88: Emphasis used instead of a heading

(MD036, no-emphasis-as-heading)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (20)
  • GitHub Check: Build Source (Dist) (windows-latest)
  • GitHub Check: Build Source (Dist) (macos-latest)
  • GitHub Check: Build Fedora (Release-static)
  • GitHub Check: Build Fedora (Debug-static)
  • GitHub Check: Windows Build VS2022 (Debug-shared)
  • GitHub Check: Build (Release-gcc-shared)
  • GitHub Check: Build ARM64 (Release-gcc-static)
  • GitHub Check: Windows Build MinGW (Release-shared)
  • GitHub Check: Build (Debug-clang-static)
  • GitHub Check: Windows Build MinGW (Release-static)
  • GitHub Check: Windows Build MinGW (Debug-static)
  • GitHub Check: Windows Build VS2022 (Release-shared)
  • GitHub Check: Windows Build VS2022 (Release-static)
  • GitHub Check: Windows Build MinGW (Debug-shared)
  • GitHub Check: Build ARM64 (Release-gcc-shared)
  • GitHub Check: Build (Release-clang-shared)
  • GitHub Check: macOS Build (Debug-shared)
  • GitHub Check: Windows VS2022 Release - No File Playback
  • GitHub Check: macOS Build (Debug-static)
  • GitHub Check: macOS Build (Release-shared)
🔇 Additional comments (20)
docs/content/implementation-details.md (1)

408-408: LGTM! Good addition to the documentation navigation.

The Rust Bindings link is appropriately placed in the "Need Help?" section and uses the 🦀 emoji consistently with the Rust theme. The link format matches the existing documentation structure.

docs/content/cmake-options.md (1)

411-411: LGTM! Appropriate documentation cross-reference.

The Rust Bindings link is well-placed in the Related Documentation section and follows the same format as other documentation links.

docs/documentation.html (3)

18-20: LGTM! Appropriate syntax highlighting support.

Adding Rust and TOML language support to highlight.js is necessary for the new Rust bindings documentation, which will include Rust code examples and Cargo.toml configuration snippets.


41-41: LGTM! Consistent navigation additions.

The Rust Bindings links are properly integrated into both the navigation dropdown and footer documentation section, following the existing bilingual pattern with English and Chinese labels.

Also applies to: 84-84


224-229: Excellent security improvement!

The sanitization of the baseDocName parameter effectively prevents path traversal attacks by restricting input to alphanumeric characters and hyphens only, with a safe fallback to 'documentation' if the sanitized value is empty. This is a best practice for handling user-controlled input in URL parameters.

docs/content/cli.zh.md (1)

485-485: LGTM! Appropriate Chinese documentation link.

The Rust Bindings link is correctly added to the Chinese version of the CLI documentation with proper Chinese text. The link references the Chinese version of the Rust bindings documentation (rust-bindings.zh.md), maintaining the bilingual documentation structure.

docs/content/cli.md (1)

521-521: LGTM! Appropriate documentation cross-reference.

The Rust Bindings link is well-placed in the "See Also" section with a clear and accurate description. The format is consistent with other documentation references in the same section.

README.zh-CN.md (2)

6-6: Rust CI badge placement is consistent with platform CI badges.

The badge URL and workflow reference follow the same pattern as existing platform CI badges, and the positioning among build status indicators is appropriate.


174-193: Rust Bindings section is well-structured and provides clear guidance.

The section logically follows the C++ and C interface examples, includes essential metadata (crate name, docs.rs link, source path), and provides practical quick-start guidance (cargo add, Cargo.toml aliasing, crate naming clarification). Chinese phrasing is clear and professional.

docs/content/rust-bindings.md (1)

1-93: Comprehensive and well-structured Rust bindings documentation.

The documentation clearly introduces the crate, provides practical dependency examples (including async feature), includes a syntactically correct minimal example, and thoroughly documents feature flags and build modes. Platform support is accurately described, and related documentation links are properly positioned.

docs/content/documentation.md (1)

13-13: Documentation index correctly links to both C Interface and Rust Bindings.

The wording update appropriately expands the language bindings reference to include Rust bindings alongside the C interface, with correct relative links to both documentation files.

docs/content/implementation-details.zh.md (1)

303-303: Link insertion aligns with new Rust bindings—clarify documentation guideline scope.

The added link to Rust bindings documentation is appropriate, but the coding guideline specifies that files in docs/ must be English. This file is explicitly a Chinese translation (.zh.md). Please clarify whether the English-only guideline is intended to apply to translated documentation files or only to primary English documentation. If translation files are in scope, this file should not be modified; if not, this change is approved.

Per coding guidelines: "docs/**/*.md: .md files in docs/ must be English and require review before publishing"

README.md (1)

6-6: Rust integration content looks solid.

The new Rust badge, updated description, and Rust Bindings section integrate well into the README. The quick-install instructions and feature documentation (async support) are clear. Once the bare URLs are fixed, this section is ready.

Also applies to: 14-14, 211-230

bindings/rust/README.md (1)

1-250: Comprehensive Rust bindings documentation—structure and content are excellent.

The README provides clear guidance on quick-start, examples, building, platform support, and thread safety. The sync and async usage examples are well-balanced, and the section on feature modes (build-source vs. static-link) and ASan handling shows thoughtful integration planning. Once the minor linting issues (bare URL and indentation) are resolved, this documentation is production-ready.

docs/content/rust-bindings.zh.md (1)

1-92: Documentation language policy requires clarification.

The coding guidelines state that .md files in docs/ must be English; however, this file is entirely in Chinese. Notably, the repository already contains 7 localized Chinese documentation files (.zh.md) paired with English equivalents, suggesting an established localization strategy that the current guideline doesn't explicitly permit.

Either translate this to English or update the coding guidelines to clarify whether locale-specific variants (.zh.md) are acceptable. If localized documentation is intended, the guideline should be revised to explicitly allow parallel translations.

scripts/test_remote_crate.sh (5)

1-18: LGTM!

The script header and setup follow Bash best practices with proper error handling (set -euo pipefail) and portable shebang. Directory resolution is correct.


20-35: LGTM!

Logging functions are well-implemented with proper color coding and stderr redirection for warnings and errors.


37-46: LGTM!

Disabling automatic cleanup by default is a good choice for preserving test artifacts (captured images, etc.) for inspection. The comment clearly documents this behavior.


397-397: LGTM!

Standard pattern for executing the main function with all script arguments.


51-63: No action needed. The grep -oE combination is supported on both GNU and BSD grep (macOS default). The version extraction pattern works correctly across both implementations.

Likely an incorrect or invalid review comment.

Copy link

@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: 7

🤖 Fix all issues with AI agents
In @bindings/rust/build_and_test.sh:
- Line 97: The build line uses nproc which isn’t portable on macOS/BSD; add a
reusable CPU detection (e.g., a function or top-level variable like JOBS
computed via nproc || sysctl -n hw.ncpu || fallback to 4) and replace the raw
nproc usage in the cmake --build invocation with -j"$JOBS" (also update the
earlier occurrence around line 30 to use the same JOBS variable).
- Around line 205-206: Remove the duplicate echo of the ccap dependency line:
keep a single line that prints echo '  ccap = { package = "ccap-rs", path =
"'$RUST_DIR'" }' and delete the repeated identical echo so the script outputs
the dependency only once.
- Around line 35-36: The script assumes the built CLI is named "ccap"
unconditionally when setting cli_bin; on Windows the binary is "ccap.exe" so the
Windows path must be chosen or detected. Update the assignment for cli_bin (the
variable set to "$PROJECT_ROOT/build/Debug/ccap") to either detect/append the
.exe extension when running on Windows (e.g., via OSTYPE/`uname` or a
file-exists check) or prefer a file-existence check that tries
"$PROJECT_ROOT/build/Debug/ccap" and then "$PROJECT_ROOT/build/Debug/ccap.exe"
and assigns whichever exists; adjust the cli_bin logic used later to rely on
this corrected value.

In @bindings/rust/src/provider.rs:
- Around line 210-235: open_device currently destroys and recreates self.handle
when a device name is supplied but does not clean up the per-instance frame
callback, which can leak resources; before destroying or replacing the provider
handle in open_device (same place where it calls
sys::ccap_provider_destroy(self.handle)), call the existing cleanup_callback()
method to free any previously-registered callback and reset self.callback_ptr
(same cleanup done in open_with_index), then proceed to destroy/create the
handle and set is_opened; ensure set_new_frame_callback will be able to register
a fresh callback afterwards.
- Around line 379-444: The set_error_callback implementation on Provider
currently installs a single global callback via GLOBAL_ERROR_CALLBACK, which
makes callbacks shared between all Provider instances and persist after
instances are dropped; change the API to make this explicit by converting the
instance method Provider::set_error_callback into an associated/static function
(e.g., Provider::set_global_error_callback) and update all call sites and
examples to use the new name, or alternatively add a prominent doc comment on
Provider::set_error_callback clarifying it is global; ensure you update
references to GLOBAL_ERROR_CALLBACK and the wrapper error_callback_wrapper to
match the new static/associated usage so callers understand the callback scope.
- Line 92: Update the comment next to the is_opened field to remove "likely" and
state the documented behavior: change the comment for is_opened to indicate the
device is opened by the C API, e.g. "Device opened by
ccap_provider_create_with_index/ccap_provider_create_with_device" so it reflects
the documented behavior of those functions (affects the struct initialization
where is_opened: true is set).
🧹 Nitpick comments (4)
bindings/rust/build_and_test.sh (1)

110-110: Consider making cargo clean optional for faster iterative testing.

While clean builds are good for CI, they slow down local development iterations. Consider making this conditional (e.g., skip if SKIP_CLEAN env var is set).

💡 Optional enhancement
 # Clean previous build
-cargo clean
+if [ "${SKIP_CLEAN:-}" != "1" ]; then
+    cargo clean
+fi

Then developers can run: SKIP_CLEAN=1 ./build_and_test.sh

bindings/rust/README.md (1)

98-102: Consider fixing nested list indentation for consistency.

Markdownlint reports that nested list items use 4-space indentation instead of the expected 2 spaces. While this doesn't affect functionality, standardizing to 2-space indentation improves consistency with common Markdown conventions.

♻️ Proposed indentation fixes
 - **Distribution mode (default):** `build-source`
-    - Builds the native C/C++ implementation via the `cc` crate.
-    - Intended for crates.io users.
+  - Builds the native C/C++ implementation via the `cc` crate.
+  - Intended for crates.io users.
 - **Development mode:** `static-link`
-    - Links against a pre-built native library from a CameraCapture checkout (e.g. `build/Debug/libccap.a`).
-    - Intended for developing this repository.
+  - Links against a pre-built native library from a CameraCapture checkout (e.g. `build/Debug/libccap.a`).
+  - Intended for developing this repository.

Apply the same 2-space indentation pattern to lines 146, 152, and 157-159.

Also applies to: 146-146, 152-152, 157-159

bindings/rust/RUST_INTEGRATION_SUMMARY.md (1)

29-48: Add language specifier to fenced code block.

The project structure code block (lines 29-48) lacks a language specifier. Adding text or plaintext improves rendering and accessibility.

♻️ Proposed fix
-```
+```text
 bindings/rust/
 ├── Cargo.toml              # package configuration
bindings/rust/src/provider.rs (1)

115-153: get_devices() may be inefficient due to opening each device.

The implementation creates a temporary Provider for each discovered device (line 138) to retrieve detailed information. If devices are slow to open or if there are many devices, this could cause noticeable delays.

Consider adding a lightweight device enumeration method that doesn't require opening each device, or at minimum document the performance characteristics:

📝 Suggested documentation addition
     /// Get available camera devices
+    ///
+    /// Note: This method opens each device to retrieve detailed information,
+    /// which may be slow if there are many devices. For a faster name-only
+    /// enumeration, use `list_devices()`.
     pub fn get_devices() -> Result<Vec<DeviceInfo>> {
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 6fce988 and 3cff3a1.

⛔ Files ignored due to path filters (1)
  • bindings/rust/Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (8)
  • bindings/rust/Cargo.toml
  • bindings/rust/README.md
  • bindings/rust/RUST_INTEGRATION_SUMMARY.md
  • bindings/rust/build_and_test.sh
  • bindings/rust/src/lib.rs
  • bindings/rust/src/provider.rs
  • src/ccap_imp_windows.cpp
  • src/ccap_imp_windows.h
✅ Files skipped from review due to trivial changes (2)
  • src/ccap_imp_windows.h
  • src/ccap_imp_windows.cpp
🚧 Files skipped from review as they are similar to previous changes (1)
  • bindings/rust/Cargo.toml
🧰 Additional context used
🧬 Code graph analysis (1)
bindings/rust/src/lib.rs (2)
include/ccap_utils.h (1)
  • LogLevel (80-140)
bindings/rust/src/provider.rs (1)
  • version (337-348)
🪛 LanguageTool
bindings/rust/README.md

[style] ~211-~211: Consider using a less common alternative to make your writing sound more unique and professional.
Context: ...ontributing Contributions are welcome! Please feel free to submit a Pull Request. ## License Thi...

(FEEL_FREE_TO_STYLE_ME)

🪛 markdownlint-cli2 (0.18.1)
bindings/rust/README.md

34-34: Bare URL used

(MD034, no-bare-urls)


98-98: Unordered list indentation
Expected: 2; Actual: 4

(MD007, ul-indent)


99-99: Unordered list indentation
Expected: 2; Actual: 4

(MD007, ul-indent)


101-101: Unordered list indentation
Expected: 2; Actual: 4

(MD007, ul-indent)


102-102: Unordered list indentation
Expected: 2; Actual: 4

(MD007, ul-indent)


146-146: Unordered list indentation
Expected: 2; Actual: 4

(MD007, ul-indent)


152-152: Unordered list indentation
Expected: 2; Actual: 4

(MD007, ul-indent)


157-157: Unordered list indentation
Expected: 2; Actual: 4

(MD007, ul-indent)


158-158: Unordered list indentation
Expected: 2; Actual: 4

(MD007, ul-indent)


159-159: Unordered list indentation
Expected: 2; Actual: 4

(MD007, ul-indent)

bindings/rust/RUST_INTEGRATION_SUMMARY.md

29-29: Fenced code blocks should have a language specified

(MD040, fenced-code-language)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (20)
  • GitHub Check: Build (Release-gcc-static)
  • GitHub Check: Build Fedora (Debug-shared)
  • GitHub Check: Build Fedora (Release-shared)
  • GitHub Check: Build (Debug-gcc-shared)
  • GitHub Check: Build (Release-gcc-shared)
  • GitHub Check: Build Fedora (Release-static)
  • GitHub Check: Build Fedora (Debug-static)
  • GitHub Check: Build ARM64 (Release-gcc-shared)
  • GitHub Check: Build ARM64 (Debug-gcc-shared)
  • GitHub Check: Build ARM64 (Debug-gcc-static)
  • GitHub Check: Build ARM64 (Release-gcc-static)
  • GitHub Check: Windows Build MinGW (Debug-static)
  • GitHub Check: Windows Build VS2022 (Release-static)
  • GitHub Check: Windows Build MinGW (Debug-shared)
  • GitHub Check: Windows Build MinGW (Release-static)
  • GitHub Check: Windows Build MinGW (Release-shared)
  • GitHub Check: Build Source (Dist) (windows-latest)
  • GitHub Check: macOS Build (Debug-static)
  • GitHub Check: macOS Build (Release-static)
  • GitHub Check: Windows Build VS2022 (Release-shared)
🔇 Additional comments (12)
bindings/rust/build_and_test.sh (6)

42-55: LGTM!

The parsing logic correctly handles both the success case (extracting device count from "Found N camera") and the no-devices case, with a sentinel value of -1 for unparseable output.


57-70: LGTM!

The Rust output parser correctly mirrors the CLI parser's approach, handling success, no-devices, and unparseable cases consistently.


72-82: LGTM!

The path setup uses the standard BASH_SOURCE idiom and correctly derives the project root and Rust directory paths.


117-120: LGTM!

Test execution is straightforward and appropriate.


122-125: LGTM!

Building examples ensures they compile correctly.


127-193: Well-structured device discovery validation.

The comparison logic is thorough and correctly handles multiple edge cases:

  • CLI failure
  • Unparseable output
  • Headless environments (no cameras)
  • Count mismatches

The use of set +e / set -e around discovery commands to capture exit codes while preserving strict error handling is appropriate.

bindings/rust/README.md (1)

1-220: Documentation is comprehensive and well-structured.

The README provides excellent coverage of features, installation, usage examples, build modes, platform support, and API documentation. The thread safety warnings are particularly valuable.

bindings/rust/RUST_INTEGRATION_SUMMARY.md (1)

1-178: Implementation summary is clear and well-organized.

The document effectively communicates the three-step implementation approach, project structure, core features, and test results.

bindings/rust/src/provider.rs (3)

511-564: LGTM: Frame callback implementation is memory-safe.

The double-boxing pattern correctly ensures a stable thin pointer for the C FFI callback, and cleanup is properly handled on both success and failure paths.


595-607: LGTM: Drop implementation correctly cleans up resources.

The Drop implementation properly cleans up the frame callback before destroying the C handle, preventing resource leaks.

Note: The global error callback is intentionally not cleaned up here (it's global and may be used by other instances), which is consistent with the global design.


49-63: Send implementation for Provider is correctly justified.

The unsafe impl Send for Provider is sound. Verification of the C++ implementation confirms:

  • No thread-local storage exists in the Provider class (thread-local allocators in ccap_convert.cpp are separate conversion utilities)
  • No thread ID caching found
  • Move semantics are properly supported with no thread-specific state

The safety documentation accurately reflects the actual guarantees: moving the Provider between threads is safe because Send only permits ownership transfer, not concurrent access. The C++ documentation correctly warns that concurrent access requires external synchronization, which is a separate concern from move safety.

bindings/rust/src/lib.rs (1)

1-37: LGTM: Clean and idiomatic crate structure.

The library organization is well-designed:

  • Clear separation between low-level FFI (sys) and high-level API
  • Appropriate lint configuration with relaxed rules for FFI bindings
  • Comprehensive re-exports for ergonomic usage
  • Good use of the OUT_DIR pattern for generated bindings

Copy link

@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

🤖 Fix all issues with AI agents
In @bindings/rust/build.rs:
- Around line 389-423: The build script only registers core sources for
cargo:rerun-if-changed but omits platform- and SIMD-specific sources, so changes
to files like ccap_imp_apple.mm, ccap_convert_apple.cpp, ccap_imp_linux.cpp,
ccap_imp_windows.cpp, ccap_convert_avx2.cpp, and ccap_convert_neon.cpp won't
trigger rebuilds; fix by adding conditional blocks that, when build_from_source
is true, emit println!("cargo:rerun-if-changed={}/src/<file>",
ccap_root.display()) for each platform-specific and SIMD file using
cfg(target_os = "...") and cfg(any(target_arch = "...")) guards, mirroring the
existing pattern around build_from_source found in build.rs so all
platform-specific sources are tracked.
🧹 Nitpick comments (2)
bindings/rust/build.rs (2)

99-99: Consider removing the unused _is_packaged binding.

The variable is assigned but never used in the code. If it's not needed for future logic, remove it to improve clarity.

♻️ Proposed fix
-    let (ccap_root, _is_packaged) = if build_from_source {
+    let ccap_root = if build_from_source {
         // 1) Vendored sources under ./native (ideal for crates.io)
         if manifest_path.join("native").exists() {
-            (manifest_path.join("native"), true)
+            manifest_path.join("native")
         } else if let Some(root) = find_ccap_root_from(&manifest_path) {
             // 2) Search parent dirs for CameraCapture repo root (works for git checkout
             //    and for `cargo publish --dry-run` which builds from target/package)
-            (root, false)
+            root
         } else if let Ok(root) = env::var("CCAP_SOURCE_DIR") {
             // 3) Allow override via CCAP_SOURCE_DIR
             let root = PathBuf::from(root);
             if looks_like_ccap_root(&root) {
-                (root, false)
+                root
             } else {
                 panic!(
                     "CCAP_SOURCE_DIR is set but does not look like CameraCapture root: {}",
                     root.display()
                 );
             }
         } else {
             // Keep a placeholder; if build-source is enabled we'll error with a clear message.
-            (manifest_path.clone(), false)
+            manifest_path.clone()
         }
     } else {
         // Dev/static-link mode: even if ./native exists, we still prefer the repo root so we can
         // link against pre-built build/Debug|Release artifacts.
         if let Some(root) = find_ccap_root_from(&manifest_path) {
-            (root, false)
+            root
         } else if let Ok(root) = env::var("CCAP_SOURCE_DIR") {
             let root = PathBuf::from(root);
             if looks_like_ccap_root(&root) {
-                (root, false)
+                root
             } else {
                 panic!(
                     "CCAP_SOURCE_DIR is set but does not look like CameraCapture root: {}",
                     root.display()
                 );
             }
         } else if static_link {
             panic!(
                 "static-link feature is enabled, but CameraCapture repo root was not found.\n\
\
Tried (in order):\n\
  - searching parent directories for include/ccap_c.h and src/ccap_core.cpp\n\
  - CCAP_SOURCE_DIR environment variable\n\
\
Please set CCAP_SOURCE_DIR to a CameraCapture checkout (with build/Debug|Release built)."
             );
         } else {
             // Fallback placeholder.
-            (manifest_path.clone(), false)
+            manifest_path.clone()
         }
     };

153-263: LGTM! Consider adding source file validation.

The build-from-source logic correctly handles platform-specific sources, SIMD variants, and compiler flags. The NEON file is always compiled for symbol availability, which is correct.

One optional improvement: you could validate that critical source files exist before calling build.compile() to provide clearer error messages if files are missing.

♻️ Optional defensive validation

Before line 168, you could add:

// Validate critical source files exist
let required_sources = [
    "src/ccap_core.cpp",
    "src/ccap_utils.cpp",
    "src/ccap_convert.cpp",
];
for src in &required_sources {
    if !ccap_root.join(src).exists() {
        panic!("Required source file not found: {}", ccap_root.join(src).display());
    }
}
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 3cff3a1 and 35ecf18.

📒 Files selected for processing (1)
  • bindings/rust/build.rs
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: Windows Build MinGW (Debug-static)
  • GitHub Check: Windows Build MinGW (Release-shared)
  • GitHub Check: Windows Build MinGW (Debug-shared)
🔇 Additional comments (7)
bindings/rust/build.rs (7)

6-14: LGTM!

The byte-pattern search logic is correct and efficiently uses a sliding window approach.


16-54: LGTM!

The clang resource directory discovery logic correctly prioritizes PATH clang and falls back to xcrun on macOS.


56-73: LGTM!

The root detection logic is sound and the 16-level parent traversal accommodates deep packaging structures like target/package/<crate>-<ver>.


274-318: LGTM! ASan detection and runtime linking is well-implemented.

The logic correctly:

  • Detects ASan instrumentation in prebuilt archives by scanning for ___asan_init (macOS Mach-O) and __asan_init (Linux ELF).
  • Links the ASan runtime explicitly, which is necessary since rustc uses -nodefaultlibs.
  • Finds and adds rpath for the clang ASan runtime on macOS.
  • Provides an opt-out mechanism via CCAP_RUST_NO_ASAN_LINK.

345-373: LGTM!

Platform-specific linking configuration is correct for all supported platforms (macOS frameworks, Windows Media Foundation, Linux C++ stdlib).


425-445: LGTM!

The bindgen configuration correctly generates FFI bindings with appropriate type derivations and symbol allowlisting.


336-340: Clarify the source of MSVC library naming behavior.

The comment states "we always link to the Release version (ccap.lib)" but this behavior is actually enforced by CMake's DEBUG_POSTFIX "d" configuration (CMakeLists.txt lines 174-180), not by the Rust code itself. The unconditional cargo:rustc-link-lib=static=ccap request happens to resolve to the Release version only because CMake names debug builds with a "d" suffix.

Either add a comment explaining that CMake handles this, or remove the misleading note if the linking behavior is already correct as-is.

Copy link

@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: 4

🤖 Fix all issues with AI agents
In @bindings/rust/build_and_test.sh:
- Around line 7-24: The jobs_count function can return without printing if a
detected command (e.g., sysctl) fails silently; update the function so each
detection captures the command output into a variable and only echo+return when
that variable is non-empty (for example, replace the sysctl branch with
capturing sysctl -n hw.ncpu into a var and if it is non-empty then echo it and
return); apply the same safe-capture pattern to nproc and getconf branches so
the function never returns without producing a value, falling back to echo 4
only if all attempts yield no output.

In @scripts/test_remote_crate.sh:
- Around line 367-372: The current build step redirects stderr into stdout
(`cargo build --quiet 2>&1`), which can swallow useful error output; update the
`log`/`error` block so failures surface the build output: either remove the
stderr redirection so Cargo prints errors to the console, or capture the command
output into a variable when running the build and, on failure, call the existing
`error` helper with that captured output (e.g., capture output from `cargo build
--quiet` and pass it into `error` before returning 1). Ensure you update the
invocation around the `cargo build` call in this script to include the captured
output in the `error` message.
- Around line 51-63: The version-extraction regex used when VERSION=="latest"
doesn't capture semver build metadata; update the grep pattern used to populate
latest_version (the command assigning latest_version via cargo search ... | grep
-oE '...') to allow an optional build metadata suffix by replacing
`[0-9]+\.[0-9]+\.[0-9]+(-[a-zA-Z0-9.]+)?` with a pattern like
`[0-9]+\.[0-9]+\.[0-9]+(-[0-9A-Za-z.]+)?(\+[0-9A-Za-z.]+)?` so both pre-release
and +build metadata (e.g., 1.0.0+build.123) are matched; keep the rest of the
logic setting VERSION and logging intact.
🧹 Nitpick comments (5)
bindings/rust/README.md (2)

29-34: Consider providing a concrete version example instead of <latest> placeholder.

The <latest> placeholder requires users to look up the current version. Consider providing a concrete example version or a command to fetch the latest version automatically.

Suggested improvement
 [dependencies]
-ccap = { package = "ccap-rs", version = "<latest>" }
+ccap = { package = "ccap-rs", version = "1.0" }  # Check crates.io for latest

Or add a script-based approach:

# Get current version and add to Cargo.toml
cargo add ccap-rs --rename ccap

200-203: Minor style suggestion.

Per static analysis, "Please feel free to submit" could be simplified.

Optional wording improvement
-Contributions are welcome! Please feel free to submit a Pull Request.
+Contributions are welcome! Submit a Pull Request to get started.
bindings/rust/build_and_test.sh (2)

26-65: Redundant cd after popd operations.

Line 63 uses cd "$original_dir" but the pushd/popd pairs (lines 42-52) should already restore the working directory. This cd is redundant but harmless.

Optional cleanup
-    cd "$original_dir"
+    # pushd/popd already restored the directory; this cd is optional
     echo "$cli_bin"

134-136: cargo clean before every build may slow iterative development.

Running cargo clean unconditionally clears the entire target directory, forcing a full rebuild every time. This is appropriate for CI but may be slow for local development.

Consider making clean optional
-# Clean previous build
-cargo clean
+# Clean previous build (optional: set CCAP_SKIP_CLEAN=1 to skip)
+if [[ "${CCAP_SKIP_CLEAN:-0}" != "1" ]]; then
+    cargo clean
+fi
bindings/rust/src/provider.rs (1)

345-349: Minor inconsistency: stop_capture doesn't check is_opened.

Unlike start_capture() (line 333-334), this function doesn't verify the device is opened before calling the C API. While probably safe (likely a no-op), adding a check would make the API more consistent and predictable.

♻️ Suggested consistency improvement
 pub fn stop_capture(&mut self) -> Result<()> {
+    if !self.is_opened {
+        return Err(CcapError::DeviceNotOpened);
+    }
     unsafe { sys::ccap_provider_stop(self.handle) };
     Ok(())
 }
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 6b2c5a7 and 2fa366e.

📒 Files selected for processing (5)
  • README.md
  • bindings/rust/README.md
  • bindings/rust/build_and_test.sh
  • bindings/rust/src/provider.rs
  • scripts/test_remote_crate.sh
🧰 Additional context used
🪛 LanguageTool
bindings/rust/README.md

[style] ~202-~202: Consider using a less common alternative to make your writing sound more unique and professional.
Context: ...ontributing Contributions are welcome! Please feel free to submit a Pull Request. ## License Thi...

(FEEL_FREE_TO_STYLE_ME)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (20)
  • GitHub Check: Windows Build VS2022 (Debug-static)
  • GitHub Check: Windows Build MinGW (Debug-shared)
  • GitHub Check: Windows Build VS2022 (Debug-shared)
  • GitHub Check: Build Fedora (Debug-static)
  • GitHub Check: Static Link (Dev) (ubuntu-latest)
  • GitHub Check: Static Link (Dev) (macos-latest)
  • GitHub Check: Static Link (Dev) (windows-latest)
  • GitHub Check: Build Source (Dist) (ubuntu-latest)
  • GitHub Check: Build Source (Dist) (windows-latest)
  • GitHub Check: macOS Release - No File Playback
  • GitHub Check: Build Fedora (Release-static)
  • GitHub Check: Build Fedora (Release-shared)
  • GitHub Check: macOS Build (Release-shared)
  • GitHub Check: Build (Release-clang-shared)
  • GitHub Check: Build (Release-clang-static)
  • GitHub Check: Build (Debug-clang-shared)
  • GitHub Check: Build ARM64 (Release-gcc-shared)
  • GitHub Check: Build (Release-gcc-shared)
  • GitHub Check: Build ARM64 (Release-gcc-static)
  • GitHub Check: Build (Release-gcc-static)
🔇 Additional comments (21)
bindings/rust/README.md (3)

1-16: LGTM - Well-structured README header.

The badges, feature list, and project description provide a clear overview of the Rust bindings. The features accurately reflect the capabilities documented in the PR objectives.


36-72: LGTM - Clear and practical usage example.

The basic usage example demonstrates the core workflow (device discovery, opening, frame capture) with proper error handling using the Result type.


176-184: Good thread safety documentation with appropriate caveats.

The thread safety section clearly documents that VideoFrame is Send, Provider is Send but not thread-safe, and includes a practical recommendation for multi-threaded access. The note about C++ layer thread-safety being based on code inspection is appropriately cautious.

README.md (3)

6-6: LGTM - Rust CI badge addition.

The Rust CI badge is correctly placed alongside the other build badges, providing visibility into the Rust bindings build status.


211-231: LGTM - Well-integrated Rust Bindings section.

The new section is appropriately placed after the Pure C Interface section and provides clear quick-install instructions with links to crates.io and docs.rs.

Note: Same suggestion as in bindings/rust/README.md applies here—consider using a concrete version example instead of <latest>.


14-14: LGTM - Updated project description.

The description now accurately reflects the availability of Rust bindings alongside C++ and C interfaces.

bindings/rust/build_and_test.sh (2)

178-217: LGTM - Comprehensive device discovery validation logic.

The decision logic handles various edge cases well:

  • CLI discovery failure
  • Parse failures for both CLI and Rust outputs
  • Rust failure when CLI sees devices
  • Headless environment detection (no cameras)
  • Device count mismatch detection

This provides good validation that the Rust bindings correctly discover devices.


67-95: LGTM - Robust output parsing functions.

The regex patterns correctly handle the expected output formats:

  • CLI: Found N camera... or No camera devices found
  • Rust: ## Found N video capture device... or Failed to find any video capture device

Returning -1 for unparseable output allows the caller to distinguish between "zero devices" and "parse failure".

scripts/test_remote_crate.sh (4)

1-46: LGTM - Well-structured script initialization.

Good practices:

  • set -euo pipefail for strict error handling
  • Clear logging functions with color coding
  • Cleanup function defined (intentionally disabled to preserve test artifacts)

81-110: Embedded Python script for Cargo.toml manipulation is clever but complex.

The Python script correctly handles:

  • Finding and modifying the [dependencies] section
  • Replacing existing ccap-rs entries
  • Creating [dependencies] section if missing

This is a reasonable approach given the complexity of TOML manipulation in pure Bash.


141-365: LGTM - Comprehensive test code generation.

The generated Rust test code covers:

  1. Provider creation and device listing
  2. Camera capture with frame saving (when devices available)
  3. Pixel format conversion (NV12 → RGB24/BGR24) with size validation
  4. PixelFormat enum variant availability
  5. Video file playback (Windows/macOS only, with #[cfg] guard)

Error handling uses early exits appropriately for critical failures while allowing non-critical operations to continue with warnings.


246-258: Test data dimensions are hardcoded and minimal.

The 16x16 test image is sufficient for basic conversion validation. The comment on line 256 correctly notes that stride equals width (no padding), which is important for the conversion functions.

bindings/rust/src/provider.rs (9)

8-20: LGTM: Safe global callback storage pattern.

The SendSyncPtr wrapper with safety justification and Mutex-protected global storage correctly implements thread-safe callback management.


170-186: LGTM: Proper bounds checking.

The bounds checks on lines 172 and 179 before accessing supportedPixelFormats and supportedResolutions arrays prevent out-of-bounds access.


290-305: LGTM: Excellent atomicity handling with rollback.

The resolution setter prevents partial updates by saving the old values and performing best-effort rollback if the height update fails. This is good defensive programming that maintains device state consistency.


214-223: LGTM: Proper cleanup order before handle destruction.

Lines 217-219 correctly stop capture and remove callbacks before destroying the C API handle, preventing use-after-free errors.


549-631: LGTM: Correct callback memory management.

The double-boxing pattern (lines 578-580), cleanup on failure (lines 595-597), and consistent type aliases ensure memory safety. The cleanup_callback() helper properly reconstructs and drops the boxed callback.


477-492: LGTM: Defensive cleanup in error callback clearing.

Lines 483-485 correctly clear the C-side callback even when no Rust callback is stored in GLOBAL_ERROR_CALLBACK. This defensive approach handles edge cases where the state might be inconsistent.


633-645: LGTM: Proper Drop cleanup order.

Cleaning up the callback (line 636) before destroying the handle (lines 638-643) prevents potential use-after-free if the C API invokes callbacks during handle destruction.


22-63: Documentation correctly warns about thread-safety limitations.

The extensive documentation (lines 22-48) and safety comments (lines 55-62) clearly state that while Provider is Send, the underlying C++ is NOT thread-safe and requires external synchronization.

Potential footgun: Users might assume Send implies safety for concurrent access. Consider adding a compile-time assertion or runtime check in debug builds to detect concurrent access, though this may not be feasible.

As per coding guidelines or learnings, does the project have a pattern for detecting or preventing concurrent access to non-thread-safe Send types?


80-113: Replace "likely" with definitive language in comments.

Lines 92 and 110 state "C API likely opens device automatically"—this is unnecessarily uncertain. The C API constructors (ccap_provider_create_with_device and ccap_provider_create_with_index) invoke the C++ Provider constructors which explicitly call open() internally, confirming the assumption is correct. Update the comments to remove "likely" and state the actual behavior definitively (e.g., "C API opens device automatically").

Comment on lines 502 to 537
pub fn open_with_index(&mut self, device_index: i32, auto_start: bool) -> Result<()> {
// Destroy old handle if exists
if !self.handle.is_null() {
unsafe {
sys::ccap_provider_destroy(self.handle);
}
}

// Clean up old callback if exists
self.cleanup_callback();

// Create a new provider with the specified device index
self.handle = unsafe { sys::ccap_provider_create_with_index(device_index, ptr::null()) };

if self.handle.is_null() {
return Err(CcapError::InvalidDevice(format!(
"device index {}",
device_index
)));
}

self.is_opened = false;
self.open()?;
if auto_start {
self.start_capture()?;
}
Ok(())
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# First, find the with_device implementation to understand the pattern
rg -n "fn with_device" bindings/rust/src/ -A 20

Repository: wysaid/CameraCapture

Length of output: 2711


🏁 Script executed:

#!/bin/bash
# Search for all uses of ccap_provider_create_with_index and ccap_provider_open_by_index
rg -n "ccap_provider_create_with_index|ccap_provider_open_by_index" bindings/rust/src/

Repository: wysaid/CameraCapture

Length of output: 449


🏁 Script executed:

#!/bin/bash
# Look for any documentation or comments about the C API
fd -e c -e h . | xargs rg -l "ccap_provider_create_with_index|ccap_provider_open_by_index" 2>/dev/null | head -5

Repository: wysaid/CameraCapture

Length of output: 236


🏁 Script executed:

#!/bin/bash
# Check if there are any tests or examples
fd -e rs . bindings/rust | xargs rg -l "open_with_index\|with_device" 2>/dev/null

Repository: wysaid/CameraCapture

Length of output: 48


🏁 Script executed:

#!/bin/bash
# Examine the C API header to understand function semantics
cat -n include/ccap_c.h | grep -A 10 -B 2 "ccap_provider_create_with_index\|ccap_provider_open_by_index"

Repository: wysaid/CameraCapture

Length of output: 1091


🏁 Script executed:

#!/bin/bash
# Check C examples to see the correct usage pattern
cat -n examples/desktop/1-minimal_example_c.c

Repository: wysaid/CameraCapture

Length of output: 3053


🏁 Script executed:

#!/bin/bash
# Also check another C example
cat -n examples/desktop/2-capture_grab_c.c | head -100

Repository: wysaid/CameraCapture

Length of output: 4052


🏁 Script executed:

#!/bin/bash
# Examine the open() method to understand the -1 device index
sed -n '195,210p' bindings/rust/src/provider.rs

Repository: wysaid/CameraCapture

Length of output: 492


🏁 Script executed:

#!/bin/bash
# Get more context around the open() method
sed -n '185,215p' bindings/rust/src/provider.rs

Repository: wysaid/CameraCapture

Length of output: 988


🏁 Script executed:

#!/bin/bash
# Check C API documentation for open_by_index parameter meaning
rg -A 20 "ccap_provider_open_by_index" include/ccap_c.h

Repository: wysaid/CameraCapture

Length of output: 852


🏁 Script executed:

#!/bin/bash
# Search for usages of with_device to see if it works or fails
rg -n "with_device" bindings/rust/src/ -B 2 -A 5

Repository: wysaid/CameraCapture

Length of output: 2979


🏁 Script executed:

#!/bin/bash
# Check if there are any Rust tests or examples
find . -name "*.rs" -path "*/examples/*" -o -name "*test*.rs" | head -10

Repository: wysaid/CameraCapture

Length of output: 277


🏁 Script executed:

#!/bin/bash
# Look for any C implementation or documentation about -1 deviceIndex
rg -B 5 -A 15 "deviceIndex.*-1|== -1" include/ examples/ --type=c

Repository: wysaid/CameraCapture

Length of output: 18118


🏁 Script executed:

#!/bin/bash
# Check Rust integration tests
head -200 bindings/rust/tests/integration_tests.rs

Repository: wysaid/CameraCapture

Length of output: 2658


🏁 Script executed:

#!/bin/bash
# Check minimal example
cat -n bindings/rust/examples/minimal_example.rs | head -150

Repository: wysaid/CameraCapture

Length of output: 2326


🏁 Script executed:

#!/bin/bash
# Check the start() and is_started() implementation
rg -n "fn start\|fn is_started" bindings/rust/src/provider.rs -A 10

Repository: wysaid/CameraCapture

Length of output: 46


🏁 Script executed:

#!/bin/bash
# Check if there's any documentation about the expected flow
rg -B 5 -A 10 "with_device|open_with_index" bindings/rust/src/provider.rs | head -150

Repository: wysaid/CameraCapture

Length of output: 3114


🏁 Script executed:

#!/bin/bash
# Search for all calls to start() to understand the complete flow
rg -n "fn start\|fn start_capture" bindings/rust/src/provider.rs -A 8

Repository: wysaid/CameraCapture

Length of output: 46


🏁 Script executed:

#!/bin/bash
# Check if is_opened affects start behavior
rg -n "is_opened\|is_started" bindings/rust/src/provider.rs -B 2 -A 5

Repository: wysaid/CameraCapture

Length of output: 46


🏁 Script executed:

#!/bin/bash
# Read the full provider.rs to understand all methods
wc -l bindings/rust/src/provider.rs

Repository: wysaid/CameraCapture

Length of output: 97


🏁 Script executed:

#!/bin/bash
# Get a broader view of method names
grep -n "pub fn" bindings/rust/src/provider.rs | head -40

Repository: wysaid/CameraCapture

Length of output: 2052


🏁 Script executed:

#!/bin/bash
# Look for the actual implementation of start_capture
sed -n '400,600p' bindings/rust/src/provider.rs

Repository: wysaid/CameraCapture

Length of output: 7010


🏁 Script executed:

#!/bin/bash
# Look at start_capture and open implementations
sed -n '196,210p' bindings/rust/src/provider.rs

Repository: wysaid/CameraCapture

Length of output: 461


🏁 Script executed:

#!/bin/bash
# Look at start_capture implementation
sed -n '332,346p' bindings/rust/src/provider.rs

Repository: wysaid/CameraCapture

Length of output: 470


🏁 Script executed:

#!/bin/bash
# Also check start() method
sed -n '253,262p' bindings/rust/src/provider.rs

Repository: wysaid/CameraCapture

Length of output: 307


🏁 Script executed:

#!/bin/bash
# Let me verify the actual test to see if with_device actually works
grep -A 30 "test_provider_with_index" bindings/rust/tests/integration_tests.rs

Repository: wysaid/CameraCapture

Length of output: 1154


🏁 Script executed:

#!/bin/bash
# Check if there are any other usages of with_device in the codebase
rg -n "\.with_device\(" bindings/rust/ -B 2 -A 5

Repository: wysaid/CameraCapture

Length of output: 46


Critical inconsistency in device opening logic—with_device() and with_device_name() incorrectly assume the device is opened automatically.

The issue extends beyond open_with_index(). Two methods have the same bug:

with_device() (line 81-92):

  • Creates provider with ccap_provider_create_with_index
  • Sets is_opened = true with comment "C API likely opens device automatically"
  • Does NOT call open()

with_device_name() (line 98-112):

  • Creates provider with ccap_provider_create_with_device
  • Sets is_opened = true with same incorrect assumption
  • Does NOT call open()

open_with_index() (line 502-529):

  • Creates provider with ccap_provider_create_with_index
  • Sets is_opened = false
  • Calls open() which invokes ccap_provider_open_by_index(self.handle, -1, false)

Evidence of the bug:
The minimal example (bindings/rust/examples/minimal_example.rs, lines 23-24) explicitly calls open() after with_device():

let mut provider = Provider::with_device(camera_index_i32)?;
provider.open()?;

This proves the C API does not automatically open the device on creation. Both with_device() and with_device_name() falsely claim is_opened = true without actually opening the device.

Consequences:

  • Code relying only on with_device() without calling open() has incorrect internal state
  • start_capture() checks is_opened but the device may not be truly opened
  • The inconsistency creates a semantic bug where the API claims success but the device is not operational

Fix both with_device() and with_device_name() to set is_opened = false and document that open() must be called explicitly, or redesign them to call open() internally.

Copy link

@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

🤖 Fix all issues with AI agents
In @docs/content/rust-bindings.zh.md:
- Around line 5-6: Lines containing "Crate:" and "API 文档:" use bare URLs;
replace each bare URL with a markdown link (e.g., change
'Crate:https://crates.io/crates/ccap-rs' to 'Crate:[ccap-rs on
crates.io](https://crates.io/crates/ccap-rs)' and 'API
文档:https://docs.rs/ccap-rs' to 'API 文档:[ccap-rs API
文档](https://docs.rs/ccap-rs)') so the URLs are rendered as proper markdown
links.
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 2fa366e and 8cf56cd.

📒 Files selected for processing (2)
  • docs/content/rust-bindings.md
  • docs/content/rust-bindings.zh.md
🚧 Files skipped from review as they are similar to previous changes (1)
  • docs/content/rust-bindings.md
🧰 Additional context used
📓 Path-based instructions (1)
docs/**/*.md

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

.md files in docs/ must be English and require review before publishing

Files:

  • docs/content/rust-bindings.zh.md
🪛 markdownlint-cli2 (0.18.1)
docs/content/rust-bindings.zh.md

5-5: Bare URL used

(MD034, no-bare-urls)


6-6: Bare URL used

(MD034, no-bare-urls)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: Build (Release-clang-static)
  • GitHub Check: Build (Release-clang-shared)
  • GitHub Check: Windows Build MinGW (Release-shared)
🔇 Additional comments (1)
docs/content/rust-bindings.zh.md (1)

1-89: No action required — this file follows the established project localization pattern.

The repository intentionally maintains parallel Chinese (.zh.md) versions of all documentation files in docs/content/. This is supported by the documentation system itself (docs/documentation.html contains explicit language routing logic to serve .zh.md files for Chinese language selection), and all .zh.md variants are actively cross-linked and referenced throughout the codebase, including in README.zh-CN.md. The file is correctly placed and follows the project's documented localization strategy.

Likely an incorrect or invalid review comment.

Copy link

@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: 3

🤖 Fix all issues with AI agents
In @bindings/rust/build.rs:
- Around line 466-473: The rerun trigger for ccap_convert_avx2.cpp is
conditionally emitted behind #[cfg(any(target_arch = "x86", target_arch =
"x86_64"))] while the AVX2 source is compiled unconditionally for hasAVX2()
symbols; fix by emitting the
println!("cargo:rerun-if-changed={}/src/ccap_convert_avx2.cpp",
ccap_root.display()) unconditionally in build.rs (or move it into the same
unconditional block that builds the AVX2 file) so changes to
ccap_convert_avx2.cpp always cause a rebuild.

In @bindings/rust/src/frame.rs:
- Around line 140-151: The data() method currently builds a &[u8] from a raw C
pointer tied to a temporary CcapVideoFrameInfo, which is unsafe because the
slice may outlive the underlying frame memory; change the API to return an owned
Vec<u8> (e.g., pub fn data(&self) -> crate::error::Result<Vec<u8>>) and inside
data() copy the bytes from the raw pointer into a Vec (create the slice
from_raw_parts then .to_vec()) before returning; update callers to accept
Vec<u8> and keep the same error handling (return Err(CcapError::FrameGrabFailed)
on failure).
- Around line 86-138: The info() and data() methods expose slices that can
outlive or point to driver-owned hardware buffers that may be reused, creating
dangling-pointer/undefined-behavior risks; fix by making frame data owned or
explicitly detached: update VideoFrameInfo (and VideoFrame::data) so the
returned buffers are owned copies (Vec<u8> or owned container) created inside
info() by copying from info.data[i] with the computed plane sizes (plane0_size,
plane1_size, plane2_size), change VideoFrameInfo.data_planes to hold owned
Vec<u8> (or Option<Vec<u8>>) instead of borrowed &[u8], and adjust data() to
return references tied to &self or return owned data as well; alternatively, if
you prefer zero-copy, expose and bind the C++ detach() method to Rust and call
it before creating slices—use the function names info(), data(), VideoFrameInfo,
and the C FFI call sys::ccap_video_frame_get_info and add a binding for detach
to ensure safety.
🧹 Nitpick comments (2)
bindings/rust/build.rs (1)

155-164: Remove trailing whitespace in panic message.

Line 163 has a trailing space in the panic message string literal.

✨ Suggested fix
                 "build-source feature is enabled, but CameraCapture sources were not found.\n\
 \
 Tried (in order):\n\
   - ./native (vendored) under the crate root\n\
   - searching parent directories for include/ccap_c.h and src/ccap_core.cpp\n\
   - CCAP_SOURCE_DIR environment variable\n\
 \
-Please vendor the sources into bindings/rust/native/, or set CCAP_SOURCE_DIR to a CameraCapture checkout." 
+Please vendor the sources into bindings/rust/native/, or set CCAP_SOURCE_DIR to a CameraCapture checkout."
             );
bindings/rust/src/frame.rs (1)

153-178: Consider returning Result from convenience methods.

The convenience methods (width(), height(), etc.) silently return default values (0, Unknown) when info() fails, potentially hiding issues like null frame pointers or invalid state. While convenient for some use cases, this could make debugging harder.

Consider either:

  1. Returning Result<u32>, Result<PixelFormat>, etc., to surface errors
  2. Adding try_width(), try_height() variants that return Results
  3. Documenting prominently that 0/Unknown indicates an error state
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between c2e32d7 and ec77592.

📒 Files selected for processing (3)
  • bindings/rust/build.rs
  • bindings/rust/src/convert.rs
  • bindings/rust/src/frame.rs
🧰 Additional context used
🧬 Code graph analysis (2)
bindings/rust/src/convert.rs (1)
bindings/rust/src/types.rs (2)
  • from_c_enum (62-64)
  • from_c_enum (197-207)
bindings/rust/src/frame.rs (3)
include/ccap_def.h (1)
  • DeviceInfo (425-442)
bindings/rust/src/types.rs (7)
  • from_c_enum (62-64)
  • from_c_enum (197-207)
  • from (35-52)
  • from (87-103)
  • from (116-126)
  • from (154-167)
  • from (220-225)
bindings/rust/src/error.rs (1)
  • from (85-111)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (20)
  • GitHub Check: Windows Build MinGW (Debug-static)
  • GitHub Check: Windows Build VS2022 (Debug-static)
  • GitHub Check: Windows Build MinGW (Release-shared)
  • GitHub Check: Windows Build VS2022 (Debug-shared)
  • GitHub Check: Windows Build MinGW (Debug-shared)
  • GitHub Check: Windows Build VS2022 (Release-static)
  • GitHub Check: Windows VS2022 Release - No File Playback
  • GitHub Check: Static Link (Dev) (macos-latest)
  • GitHub Check: Static Link (Dev) (windows-latest)
  • GitHub Check: Static Link (Dev) (ubuntu-latest)
  • GitHub Check: Build (Release-gcc-shared)
  • GitHub Check: Build Fedora (Release-static)
  • GitHub Check: Build ARM64 (Debug-gcc-shared)
  • GitHub Check: Build ARM64 (Release-gcc-static)
  • GitHub Check: Build (Debug-clang-static)
  • GitHub Check: Build (Release-clang-static)
  • GitHub Check: Build (Release-gcc-static)
  • GitHub Check: Build (Release-clang-shared)
  • GitHub Check: Build Source (Dist) (macos-latest)
  • GitHub Check: Build Fedora (Release-shared)
🔇 Additional comments (13)
bindings/rust/build.rs (4)

6-14: LGTM: Well-defensive file pattern matching.

The function correctly handles edge cases (read failure, empty needle) and uses an efficient sliding window approach.


284-323: ASan detection heuristic is pragmatic for dev builds.

The byte-pattern matching for ASan symbols (___asan_init/__asan_init) is a reasonable heuristic, though it could theoretically produce false positives/negatives if symbol names change across compiler versions. The CCAP_RUST_NO_ASAN_LINK escape hatch provides users with a workaround if needed.


221-246: Excellent: Unconditional AVX2 compilation with conditional flags.

The approach of always building the AVX2 object file but only adding SIMD flags on x86/x86_64 ensures that hasAVX2() symbols are available on all platforms while avoiding compiler errors on non-x86 architectures. The MSVC vs non-MSVC flag handling is also correct.


483-495: Well-scoped bindgen configuration.

The allowlist approach (ccap_.*, Ccap.*, CCAP_.*) appropriately restricts bindings to only the camera capture API, avoiding unnecessary code generation. The derive directives enable useful traits.

bindings/rust/src/convert.rs (5)

10-20: LGTM: Clear and informative validation error messages.

The helper provides precise feedback including actual vs. required buffer sizes, which aids debugging.


387-461: Excellent: Comprehensive round-trip validation.

The RGB↔BGR round-trip test with per-pixel verification (including coordinates in assertion messages) provides strong correctness guarantees and excellent debugging support.


463-499: Appropriate YUV conversion tolerance.

The tolerance range (100-156 for neutral gray from Y=128, U=128, V=128) correctly accounts for YUV→RGB conversion rounding and different color space standards.


560-579: Good: Validation error path coverage.

Testing that undersized buffers produce clear InvalidParameter errors with "too small" messages confirms the error handling works as expected.


55-86: C conversion functions return void and cannot report errors, so no return value checking is possible. The current approach of validating input buffer sizes before calling the C FFI is appropriate for this API design. No action needed.

bindings/rust/src/frame.rs (4)

24-35: Excellent: Defensive bounds checking prevents buffer overruns.

Using .min() to clamp array accesses (lines 25, 31) protects against C API inconsistencies where count fields might exceed array bounds.


52-65: LGTM: Clear ownership semantics.

The distinction between owned (from_c_ptr) and borrowed (from_c_ptr_ref) frames is well-designed for handling both grabbed frames and callback scenarios. The owns_frame flag ensures proper resource management.


181-189: LGTM: Correct conditional resource cleanup.

The Drop implementation properly respects the owns_frame flag, ensuring callback-borrowed frames aren't double-freed while owned frames are properly released.


191-222: The unsafe impl Send for VideoFrame is correctly implemented and verified. The C++ implementation confirms all safety assumptions:

  1. Independent frames: ccap_provider_grab() creates a new std::shared_ptr<VideoFrame> via std::move(), ensuring independence from the Provider
  2. Thread-safe reference counting: std::shared_ptr uses atomic operations for its control block, making it thread-safe across threads
  3. Thread-safe release: ccap_video_frame_release() simply deletes the shared_ptr, which is an atomic-safe operation
  4. Immutable frame data: VideoFrame contains only immutable fields (pointers, integers, enum values, and a thread-safe shared_ptr for the allocator)

The inline documentation is accurate and well-reasoned. No changes needed.

Comment on lines +466 to +473
// SIMD-specific sources
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
{
println!(
"cargo:rerun-if-changed={}/src/ccap_convert_avx2.cpp",
ccap_root.display()
);
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Inconsistent rerun trigger for AVX2 source.

The AVX2 source file is unconditionally compiled (lines 221-246, comment states "Always build AVX2 file for hasAVX2() symbols"), but the rerun-if-changed directive is gated by #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]. This means changes to ccap_convert_avx2.cpp on non-x86 platforms won't trigger a rebuild even though the file is compiled.

🔧 Suggested fix
-        // SIMD-specific sources
-        #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
-        {
-            println!(
-                "cargo:rerun-if-changed={}/src/ccap_convert_avx2.cpp",
-                ccap_root.display()
-            );
-        }
+        // SIMD-specific sources
+        // AVX2 is always built (provides symbols on all platforms)
+        println!(
+            "cargo:rerun-if-changed={}/src/ccap_convert_avx2.cpp",
+            ccap_root.display()
+        );
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// SIMD-specific sources
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
{
println!(
"cargo:rerun-if-changed={}/src/ccap_convert_avx2.cpp",
ccap_root.display()
);
}
// SIMD-specific sources
// AVX2 is always built (provides symbols on all platforms)
println!(
"cargo:rerun-if-changed={}/src/ccap_convert_avx2.cpp",
ccap_root.display()
);
🤖 Prompt for AI Agents
In @bindings/rust/build.rs around lines 466 - 473, The rerun trigger for
ccap_convert_avx2.cpp is conditionally emitted behind #[cfg(any(target_arch =
"x86", target_arch = "x86_64"))] while the AVX2 source is compiled
unconditionally for hasAVX2() symbols; fix by emitting the
println!("cargo:rerun-if-changed={}/src/ccap_convert_avx2.cpp",
ccap_root.display()) unconditionally in build.rs (or move it into the same
unconditional block that builds the AVX2 file) so changes to
ccap_convert_avx2.cpp always cause a rebuild.

Comment on lines +86 to +138
/// Get frame information
pub fn info<'a>(&'a self) -> crate::error::Result<VideoFrameInfo<'a>> {
let mut info = sys::CcapVideoFrameInfo::default();

let success = unsafe { sys::ccap_video_frame_get_info(self.frame, &mut info) };

if success {
// Calculate proper plane sizes based on pixel format
// For plane 0 (Y or main): stride * height
// For chroma planes (UV): stride * height/2 for most formats
let plane0_size = (info.stride[0] as usize) * (info.height as usize);
let plane1_size = if info.stride[1] > 0 {
(info.stride[1] as usize) * ((info.height as usize + 1) / 2)
} else {
0
};
let plane2_size = if info.stride[2] > 0 {
(info.stride[2] as usize) * ((info.height as usize + 1) / 2)
} else {
0
};

Ok(VideoFrameInfo {
width: info.width,
height: info.height,
pixel_format: PixelFormat::from(info.pixelFormat),
size_in_bytes: info.sizeInBytes,
timestamp: info.timestamp,
frame_index: info.frameIndex,
orientation: FrameOrientation::from(info.orientation),
data_planes: [
if info.data[0].is_null() {
None
} else {
Some(unsafe { std::slice::from_raw_parts(info.data[0], plane0_size) })
},
if info.data[1].is_null() {
None
} else {
Some(unsafe { std::slice::from_raw_parts(info.data[1], plane1_size) })
},
if info.data[2].is_null() {
None
} else {
Some(unsafe { std::slice::from_raw_parts(info.data[2], plane2_size) })
},
],
strides: [info.stride[0], info.stride[1], info.stride[2]],
})
} else {
Err(CcapError::FrameGrabFailed)
}
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

# Find C/C++ header files and implementations
find . -type f \( -name "*.h" -o -name "*.hpp" -o -name "*.c" -o -name "*.cpp" \) | head -20

Repository: wysaid/CameraCapture

Length of output: 726


🏁 Script executed:

# Search for ccap_video_frame_get_info definition
rg -i "ccap_video_frame_get_info" --type-list | head -5

Repository: wysaid/CameraCapture

Length of output: 453


🏁 Script executed:

# Search in common locations for header/source files
rg -n "ccap_video_frame_get_info" -A 20 2>/dev/null | head -100

Repository: wysaid/CameraCapture

Length of output: 5645


🏁 Script executed:

# Look for CcapVideoFrameInfo struct definition
rg -n "CcapVideoFrameInfo" -B 2 -A 15 2>/dev/null | head -150

Repository: wysaid/CameraCapture

Length of output: 8736


🏁 Script executed:

# Find the implementation of ccap_video_frame_get_info
rg -n "ccap_video_frame_get_info" --type cpp --type c -B 2 -A 20 2>/dev/null | head -200

Repository: wysaid/CameraCapture

Length of output: 14619


🏁 Script executed:

# Look for CcapVideoFrame struct definition and implementation
rg -n "class VideoFrame" -A 30 --type cpp 2>/dev/null | head -150

Repository: wysaid/CameraCapture

Length of output: 46


🏁 Script executed:

# Find header files with CcapVideoFrame or VideoFrame definitions
fd -e h -e hpp | xargs rg -l "CcapVideoFrame\|class VideoFrame" 2>/dev/null

Repository: wysaid/CameraCapture

Length of output: 48


🏁 Script executed:

# Check the include directory for full struct definitions
cat include/ccap_c.h | head -200

Repository: wysaid/CameraCapture

Length of output: 8914


🏁 Script executed:

# Find VideoFrame class definition
rg -n "class VideoFrame" -A 50 --type cpp 2>/dev/null | head -200

Repository: wysaid/CameraCapture

Length of output: 46


🏁 Script executed:

# Look for VideoFrame struct/class and data member declarations
rg -n "data\[3\]" -B 5 -A 2 --type cpp --type h 2>/dev/null | head -150

Repository: wysaid/CameraCapture

Length of output: 1202


🏁 Script executed:

# Find the Drop/release implementation for VideoFrame in Rust bindings
rg -n "impl Drop" -A 10 bindings/rust/src/ 2>/dev/null

Repository: wysaid/CameraCapture

Length of output: 1318


🏁 Script executed:

# Check if there are any allocation/deallocation patterns in the C code
rg -n "ccap_video_frame_release" -B 5 -A 10 src/ccap_c.cpp 2>/dev/null | head -100

Repository: wysaid/CameraCapture

Length of output: 554


🏁 Script executed:

# Find VideoFrame C++ class definition to understand data member management
rg -n "class VideoFrame" -A 80 include/ccap_def.h 2>/dev/null | head -200

Repository: wysaid/CameraCapture

Length of output: 46


🏁 Script executed:

# Check how data is allocated/deallocated in VideoFrame
rg -n "data\[0\] =" -B 3 -A 3 src/ --type cpp 2>/dev/null | head -100

Repository: wysaid/CameraCapture

Length of output: 6336


🏁 Script executed:

# Verify the data() method return type more carefully
cat bindings/rust/src/frame.rs | sed -n '140,152p'

Repository: wysaid/CameraCapture

Length of output: 527


🏁 Script executed:

# Check if VideoFrame can be used concurrently or if frame data can be invalidated
rg -n "grab\|release" include/ccap_c.h -A 2 -B 2 2>/dev/null | grep -A 3 -B 3 "grab\|release"

Repository: wysaid/CameraCapture

Length of output: 46


🏁 Script executed:

# Check the full VideoFrame class definition to understand lifetime guarantees
rg -n "struct VideoFrame\|class VideoFrame" -A 100 include/ccap_def.h 2>/dev/null | head -250

Repository: wysaid/CameraCapture

Length of output: 46


🏁 Script executed:

# Verify the allocator class to understand data ownership
rg -n "class.*Allocator\|struct.*Allocator" -A 30 include/ --type h 2>/dev/null | head -150

Repository: wysaid/CameraCapture

Length of output: 46


🏁 Script executed:

# Check if there are any threading or concurrent access concerns
rg -n "shared_ptr.*VideoFrame\|make_shared" src/ccap_c.cpp -B 3 -A 3 2>/dev/null

Repository: wysaid/CameraCapture

Length of output: 46


🏁 Script executed:

# Find VideoFrame definition with broader search
rg -n "VideoFrame" include/ccap_def.h -A 60 | head -300

Repository: wysaid/CameraCapture

Length of output: 4703


🏁 Script executed:

# Check what allocator is used in VideoFrame
rg -n "allocator" include/ccap_def.h -B 2 -A 2 | head -100

Repository: wysaid/CameraCapture

Length of output: 1594


🏁 Script executed:

# Check the frame.rs more carefully for the data() method issues
cat bindings/rust/src/frame.rs | sed -n '1,70p'

Repository: wysaid/CameraCapture

Length of output: 2280


🏁 Script executed:

# Examine the VideoFrameInfo struct and how lifetimes are bound
cat bindings/rust/src/frame.rs | sed -n '1,50p'

Repository: wysaid/CameraCapture

Length of output: 1674


🏁 Script executed:

# Check if detach() is exposed in Rust bindings
rg -n "detach" bindings/rust/src/ 2>/dev/null

Repository: wysaid/CameraCapture

Length of output: 295


🏁 Script executed:

# Check the VideoFrameInfo struct definition in Rust to see lifetime constraints
rg -n "struct VideoFrameInfo\|pub struct VideoFrameInfo" -A 20 bindings/rust/src/ 2>/dev/null

Repository: wysaid/CameraCapture

Length of output: 46


🏁 Script executed:

# Verify the complete data() method implementation one more time
sed -n '140,151p' bindings/rust/src/frame.rs

Repository: wysaid/CameraCapture

Length of output: 526


Fix critical lifetime safety issues in info() and data() methods.

The data() method (lines 140-151) is fundamentally unsafe: it returns &[u8] with no lifetime binding to &self, allowing the slice to outlive the VideoFrame. If the frame is dropped, the slice becomes a dangling pointer.

The info() method (lines 86-138) has a related but subtler issue. While it correctly binds slice lifetimes to &'a self, this assumes the underlying data pointers remain valid for the frame's entire lifetime. However, the C++ implementation reveals that data[i] can point to hardware buffers managed by the camera driver. According to the C++ documentation (ccap_def.h:407-413), these hardware buffers can be reused by the driver even while the VideoFrame object exists, unless explicitly detached. The Rust bindings expose no way to call the detach() method, leaving slices created by info() potentially pointing to reused memory.

The safe fix requires either:

  1. Binding the slice lifetime in data() to &self (make it return &'_ [u8] or wrap in a struct with lifetime)
  2. Copying frame data at construction time to guarantee stability, or
  3. Exposing the C++ detach() method to Rust callers to explicitly copy hardware buffers to owned memory
🤖 Prompt for AI Agents
In @bindings/rust/src/frame.rs around lines 86 - 138, The info() and data()
methods expose slices that can outlive or point to driver-owned hardware buffers
that may be reused, creating dangling-pointer/undefined-behavior risks; fix by
making frame data owned or explicitly detached: update VideoFrameInfo (and
VideoFrame::data) so the returned buffers are owned copies (Vec<u8> or owned
container) created inside info() by copying from info.data[i] with the computed
plane sizes (plane0_size, plane1_size, plane2_size), change
VideoFrameInfo.data_planes to hold owned Vec<u8> (or Option<Vec<u8>>) instead of
borrowed &[u8], and adjust data() to return references tied to &self or return
owned data as well; alternatively, if you prefer zero-copy, expose and bind the
C++ detach() method to Rust and call it before creating slices—use the function
names info(), data(), VideoFrameInfo, and the C FFI call
sys::ccap_video_frame_get_info and add a binding for detach to ensure safety.

Comment on lines +140 to +151
/// Get all frame data as a slice
pub fn data(&self) -> crate::error::Result<&[u8]> {
let mut info = sys::CcapVideoFrameInfo::default();

let success = unsafe { sys::ccap_video_frame_get_info(self.frame, &mut info) };

if success && !info.data[0].is_null() {
Ok(unsafe { std::slice::from_raw_parts(info.data[0], info.sizeInBytes as usize) })
} else {
Err(CcapError::FrameGrabFailed)
}
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Same lifetime concern applies to data() method.

The data() method creates a slice from a C pointer (line 147) with lifetime tied to &self, sharing the same safety concern as info(). The returned slice must remain valid for the VideoFrame's entire lifetime.

🤖 Prompt for AI Agents
In @bindings/rust/src/frame.rs around lines 140 - 151, The data() method
currently builds a &[u8] from a raw C pointer tied to a temporary
CcapVideoFrameInfo, which is unsafe because the slice may outlive the underlying
frame memory; change the API to return an owned Vec<u8> (e.g., pub fn
data(&self) -> crate::error::Result<Vec<u8>>) and inside data() copy the bytes
from the raw pointer into a Vec (create the slice from_raw_parts then .to_vec())
before returning; update callers to accept Vec<u8> and keep the same error
handling (return Err(CcapError::FrameGrabFailed) on failure).

wysaid and others added 15 commits January 12, 2026 04:00
Critical fixes:
- Add GitHub Actions workflow permissions (contents: read)
- Fix memory leak in Provider callback management
  - Add callback_ptr field to track callback lifecycle
  - Implement cleanup_callback() method
  - Clean up callbacks in Drop implementation
- Fix open_device ignoring device_name parameter
- Fix pixel format conversion type errors in set/get operations
- Fix VideoFrame data plane size calculations
  - Plane 0: stride * height
  - Chroma planes: stride * (height + 1) / 2

Important fixes:
- Remove strict device count assertion in tests
- Add bounds checking for DeviceInfo arrays
- Fix README example code
  - Use open_device() instead of open()
  - Use grab_frame(timeout) instead of grab_frame_blocking()

Minor fixes:
- Add ConversionFailed error variant
- Clean up unused imports in utils.rs
- Document AsyncProvider's reserved _frame_sender field
- Fix shell script quoting in build_and_test.sh
- Fix remove_new_frame_callback error type

All critical and major issues from PR review have been addressed.
- Remove non-existent functions: ccap_convert_frame, ccap_convert_is_backend_available, ccap_convert_get_backend_name
- Remove non-existent conversion functions: MJPEG, YV12, yuyv422
- Fix NV12/I420/YUYV conversion functions to match C API signatures (add stride parameters)
- Use correct constant CCAP_CONVERT_FLAG_DEFAULT instead of non-existent NONE
- Remove unused ConversionFailed error variant
- Fix unused_variables warning in build.rs
- All conversion functions now match actual C API in include/ccap_convert_c.h
Problem Analysis:
- Build Source (Dist) mode on macOS/Windows was failing with undefined symbols
- Root cause: ccap_file_reader_apple.mm and ccap_file_reader_windows.cpp were missing
- These files are required when CCAP_ENABLE_FILE_PLAYBACK is enabled (default ON in CMake)
- The FileReaderApple/Windows classes are referenced in ccap_imp_apple.mm and ccap_imp_windows.cpp

Changes:
- Add ccap_file_reader_apple.mm to macOS platform sources
- Add ccap_file_reader_windows.cpp to Windows platform sources
- Define CCAP_ENABLE_FILE_PLAYBACK=1 to enable file playback functionality

Verification:
- Linux build-source mode: ✅ cargo build --no-default-features --features build-source
- Linux tests: ✅ cargo test --no-default-features --features build-source
- All 11 tests passed

This fix ensures build-source mode includes all required source files
for the file playback feature, resolving linker errors on macOS and Windows.
…ty and test robustness

- Upgrade Rust crate version from 1.1.0 to 1.5.0 to match main project
- Enhance build_and_test.sh with CLI-assisted device discovery validation:
  - Auto-build ccap CLI if not found (with CCAP_BUILD_CLI=ON)
  - Compare Rust print_camera and CLI --list-devices device counts
  - Report mismatches or inconsistencies; skip tests gracefully if no cameras
  - Fix: Use print_camera instead of non-existent list_cameras example
  - Fix: Directory restoration after CLI construction
  - Update: Usage examples list to match actual examples
- Strengthen build_and_test.sh library rebuild:
  - Always rebuild when libccap.a missing, even if build dir exists
  - Use cmake --build instead of make for better portability
  - Check CMakeCache.txt to skip redundant reconfiguration
- Extend scripts/update_version.sh to sync Rust crate version:
  - Update Cargo.toml version when running version script
  - Update Cargo.lock ccap entry via Python regex
  - Ensures future version bumps include Rust bindings
- Add <limits> header to src/ccap_convert_frame.cpp:
  - Prevent uint32_t sizeInBytes overflow (return false if allocator->size() > UINT32_MAX)
  - Safety check for inplaceConvertFrame post-conversion
- Rename package to 'ccap-rs' for crates.io, keep lib name 'ccap' for imports
- Change default features from 'static-link' to 'build-source' for crates.io compatibility
- Update build.rs with robust source root discovery supporting packaged (vendor) mode
- Implement publish_rust_crate.sh with multi-mode workflow:
  * dry-run: verify-only with auto-prepare (no upload)
  * preview: pre-release publish (non-main default)
  * stable: stable release (main branch default)
  * expert: as-is publish (requires explicit overrides)
- Auto-vendor native sources (include/ + src/) to bindings/rust/native/
- Auto-fix Cargo.toml defaults to 'build-source' for dist-mode friendliness
- Isolate static-link tests to truly validate pre-built library workflow
- Translate RUST_INTEGRATION_SUMMARY.md to English
- Update version update script to handle 'ccap-rs' package name
- Branch-aware versioning: non-main = '-test.<timestamp>.<sha>' pre-release
Drop the incomplete frame_stream API and remove unused deps. Mark VideoFrame as Send (not Sync) and update docs/examples accordingly.
…s, and address PR #44 review feedback

Rust bindings improvements:
- Provider::open_device: clean up callbacks before destroying handle (prevents UAF/leaks)
- set_resolution: add rollback logic to avoid partial state updates if height set fails
- Global error callback: clarify process-global semantics with alias APIs
  - Add set_global_error_callback() and clear_global_error_callback() aliases
  - Always clear C-side callback in clear_error_callback()

Build and test script improvements:
- build_and_test.sh: cross-platform CPU core detection (nproc → sysctl/getconf fallback)
- build_and_test.sh: robust CLI binary discovery (handle .exe on Windows)
- build_and_test.sh: remove duplicate echo output
- test_remote_crate.sh: fix cargo metadata version detection (remove --no-deps)
- test_remote_crate.sh: improve cargo update error handling message

Documentation fixes:
- Wrap bare URLs in angle brackets (MD034)
- Fix list indentation and table formatting (MD007/MD060)
- Add blank lines around list features (MD032)

Verified with:
- cargo fmt (format all code)
- cargo test (Rust bindings pass)
- markdownlint (no errors remaining)
@wysaid wysaid merged commit 4e5553b into main Jan 11, 2026
44 of 45 checks passed
@wysaid wysaid deleted the feature/rust branch January 11, 2026 20:04
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