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

    • Full Rust bindings: synchronous Provider API, optional async API, DeviceInfo/VideoFrame types, color-conversion utilities, utilities for saving/dumping frames, and runnable examples.
  • Documentation

    • Detailed Rust README and integration summary with quick-starts, usage examples, platform notes and build guidance.
  • Chores

    • Cross-platform CI workflow for build/test, build-system option to enable Rust bindings, editor tasks, helper script, and ignore rules.
  • Tests

    • New unit and integration tests covering creation, listing, conversions and error handling.

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

wysaid and others added 6 commits December 28, 2025 04:16
Co-authored-by: wysaid <1430725+wysaid@users.noreply.github.com>
…SCode tasks

Co-authored-by: wysaid <1430725+wysaid@users.noreply.github.com>
- Fixed async.rs compilation errors:
  - Use open_device() instead of open() with parameters
  - Fix async stop() method return type
  - Add missing InternalError variant to error enum
  - Fix mutable borrow in spawn_blocking

- Clean up warnings:
  - Remove unused imports (CString, std::ptr) from frame.rs
  - Fix unused variable by prefixing with underscore
  - Remove unused tokio_test import from async.rs
  - Fix unused Result warning in minimal_example.rs

- Add comprehensive documentation:
  - Document all enum variants for PixelFormat, FrameOrientation, PropertyName, ColorConversionBackend
  - Document all struct fields in Resolution, DeviceInfo, VideoFrameInfo
  - Document all missing methods and associated functions
  - Add doc comment for InternalError variant and Unknown error code field

- Add GitHub Actions workflow (.github/workflows/rust.yml):
  - Cross-platform testing (Ubuntu, Windows, macOS)
  - Rust formatting and clippy checks
  - Build C library as prerequisite
  - Test rust bindings and examples
  - Comprehensive CI pipeline for rust-specific checks

All compilation errors resolved, warnings cleaned up, and comprehensive CI workflow added as requested.

Co-authored-by: wysaid <1430725+wysaid@users.noreply.github.com>
Copilot AI review requested due to automatic review settings December 27, 2025 20:53
@coderabbitai
Copy link

coderabbitai bot commented Dec 27, 2025

Note

Other AI code review bot(s) detected

CodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review.

Walkthrough

Adds a complete Rust bindings crate for the ccap camera library: build integration (CMake/Cargo/build.rs), bindgen FFI, synchronous Provider API, optional async wrapper, conversion/utils modules, examples, tests, CI workflow, and developer tasks/docs.

Changes

Cohort / File(s) Summary
CI / Editor tasks
/.github/workflows/rust.yml, .vscode/tasks.json
New GitHub Actions workflow with Dev (static-link) and Dist (build-source) jobs across OS matrix; VS Code tasks to build/run Rust bindings and examples.
CMake integration
CMakeLists.txt
Adds CCAP_BUILD_RUST option; detects Cargo and exposes ccap-rust / ccap-rust-test targets (warns and disables when Cargo missing).
Crate manifest & build glue
bindings/rust/Cargo.toml, bindings/rust/build.rs, bindings/rust/build_and_test.sh, bindings/rust/.gitignore, bindings/rust/wrapper.h
New Cargo.toml with features (static-link, build-source, async); build.rs builds or links ccap and runs bindgen; helper build/test script and wrapper.h includes; gitignore updated.
Library root & re-exports
bindings/rust/src/lib.rs
New crate root exposing generated sys module and re-exporting public API (Convert, CcapError/Result, frame/provider/types/utils) and conditional async module.
Core synchronous API
bindings/rust/src/error.rs, bindings/rust/src/types.rs, bindings/rust/src/frame.rs, bindings/rust/src/provider.rs
Adds CcapError/Result, PixelFormat/Resolution/FrameOrientation/PropertyName mappings, DeviceInfo/VideoFrame wrappers, and a synchronous Provider with discovery, lifecycle, property management, capture, callbacks, and Drop cleanup.
Async API
bindings/rust/src/async.rs
Feature-gated AsyncProvider that wraps the sync Provider, provides async lifecycle/device discovery/grab APIs, spawn_blocking for grabs, channel-backed frame stream, and basic async tests.
Conversion & utilities
bindings/rust/src/convert.rs, bindings/rust/src/utils.rs
Convert wrappers for color conversions and backend queries; Utils helpers for pixel-format string conversions, dumping/saving frames, camera selection, and log-level control.
Examples & tests
bindings/rust/examples/*, bindings/rust/tests/integration_tests.rs
Four examples (print_camera, minimal_example, capture_grab, capture_callback) and integration tests tolerant of no-device environments.
Docs & integration summary
bindings/rust/README.md, bindings/rust/RUST_INTEGRATION_SUMMARY.md
README and integration summary documenting design, build instructions, API, examples, and tests.

Sequence Diagram(s)

sequenceDiagram
    autonumber
    participant App as Client App
    participant Async as AsyncProvider (Rust)
    participant Sync as Provider (Rust)
    participant C as ccap C library
    participant Dev as Device/Driver

    Note over App,Async: async feature enabled (optional)
    App->>Async: new()/open()/start()
    Async->>Sync: lock & delegate open/start
    Sync->>C: ccap_open / ccap_start_capture
    C->>Dev: initialize stream
    Dev-->>C: emit frame (C pointer)
    C-->>Sync: deliver frame pointer
    Sync->>Async: return VideoFrame / send via channel
    Async-->>App: yield VideoFrame (await or stream)
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Possibly related PRs

Suggested labels

enhancement

Suggested reviewers

  • LeeGoDamn
  • Lozg2

Poem

🐰 I tunneled from C to Rust with care,
Bindgen stitched fur and examples hop in air,
Async and sync frames skitter, converted bright,
Tests nibble bugs beneath the moonlit night,
Hooray — crates compiled and cameras take flight! 🥕

Pre-merge checks and finishing touches

✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'feat(rust): Add high-performance Rust bindings with SIMD acceleration' clearly and concisely summarizes the main change—adding Rust bindings with performance optimization. It is specific, directly related to the primary changeset focus, and uses conventional commit formatting.
Docstring Coverage ✅ Passed Docstring coverage is 96.36% which is sufficient. The required threshold is 80.00%.
✨ 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

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 25 to 32
if [ ! -d "build" ]; then
mkdir -p build/Debug
cd build/Debug
cmake ../.. -DCMAKE_BUILD_TYPE=Debug
make -j$(nproc 2>/dev/null || echo 4)
else
echo "Build directory exists, assuming library is built"
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 | 🟠 Major

Verify library existence, not just build directory.

The script assumes the C library is built if the build directory exists, but doesn't verify the library file actually exists in that case. If someone manually creates build/ or a build fails partway, this will skip rebuilding even though the library is missing.

🔎 Proposed fix
-    if [ ! -d "build" ]; then
+    if true; then  # Always try to build if library not found
         mkdir -p build/Debug
         cd build/Debug
         cmake ../.. -DCMAKE_BUILD_TYPE=Debug
         make -j$(nproc 2>/dev/null || echo 4)
-    else
-        echo "Build directory exists, assuming library is built"
     fi

Or better, remove the else branch entirely since the outer check already verified the library doesn't exist.

Committable suggestion skipped: line range outside the PR's diff.

🧰 Tools
🪛 Shellcheck (0.11.0)

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

(SC2046)

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:

bindgen latest stable version MSRV minimum supported rust version

💡 Result:

Latest stable bindgen: 0.72.1. Minimum supported Rust version (MSRV): 1.70.0. [1][2]

Sources:

  • bindgen (docs.rs) — release & MSRV info. [1]
  • rust-bindgen GitHub README — MSRV statement. [2]

[1] https://docs.rs/crate/bindgen/latest
[2] https://github.com/rust-lang/rust-bindgen


bindgen 0.68 is outdated; consider upgrading with MSRV implications.

The current version (0.68) is several releases behind the latest stable (0.72.1), which offers bug fixes and improvements. However, the latest bindgen requires Rust 1.70.0 MSRV, while the project specifies 1.65+. If keeping Rust 1.65 compatibility is important, stay with 0.68 or an intermediate version. Otherwise, upgrade to 0.72.1 and update the rust-version requirement to 1.70 or higher.

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 419
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 328
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.

let (ccap_root, is_packaged) = if manifest_path.join("native").exists() {
(manifest_path.join("native"), true)
} else {
(manifest_path.parent().unwrap().parent().unwrap().to_path_buf(), false)
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

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()

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In bindings/rust/build.rs around line 15, the expression using
.parent().unwrap().parent().unwrap() is fragile and can panic if the manifest
path lacks two parent directories; replace the chained unwraps with safe
handling: retrieve the second ancestor via manifest_path.parent().and_then(|p|
p.parent()) or manifest_path.ancestors().nth(2), map it to a PathBuf on success,
and on failure return a clear error (or call panic!/eprint! and exit) with a
descriptive message indicating the expected repository layout and the
problematic manifest_path; ensure the code does not use plain unwrap() so it
fails with an actionable error instead of an obscure panic.

Comment on lines +117 to +119
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");
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

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 is_packaged == true and 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=wrapper.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());

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In bindings/rust/build.rs around lines 117 to 119, the cargo:rerun-if-changed
directives use hardcoded relative paths (../../include/...) which break when the
crate is packaged; update these three println! invocations to build the
include-file paths from the existing ccap_root variable (or PathBuf formed from
it) so they point to ccap_root.join("include/<file>") and print the resulting
path (e.g., path.display()) instead of the hardcoded ../../ paths; ensure the
code uses the correct string/Display output so the paths exist both in repo and
packaged (is_packaged) modes.

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.

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
- Run cargo fmt to fix all code formatting issues
- Separate SIMD file compilation in build.rs with proper flags:
  - ccap_convert_avx2.cpp with -mavx2 -mfma for x86/x86_64
  - ccap_convert_neon.cpp for aarch64
- This fixes GitHub Actions CI failures:
  - Formatting check failure
  - Build-source mode compilation failure
- Add #[allow(clippy::too_many_arguments)] to functions with 8 parameters
- Change Into<T> implementation to From<T> (clippy::from_over_into)
- Remove unnecessary double cast (u32 as u32 as f64 -> u32 as f64)

All clippy checks now pass with -D warnings.
Compress multi-line call to single line as cargo fmt prefers.
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 +66
) -> 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.

Comment on lines 341 to 362
let callback = Arc::new(Mutex::new(callback));

unsafe extern "C" fn error_callback_wrapper(
error_code: sys::CcapErrorCode,
description: *const c_char,
user_data: *mut std::ffi::c_void,
) {
if user_data.is_null() || description.is_null() {
return;
}

let callback = &*(user_data as *const Arc<Mutex<dyn Fn(u32, &str) + Send + Sync>>);
let desc_cstr = std::ffi::CStr::from_ptr(description);
if let Ok(desc_str) = desc_cstr.to_str() {
if let Ok(cb) = callback.lock() {
cb(error_code, desc_str);
}
}
}

// Store the callback to prevent it from being dropped
let callback_ptr = Box::into_raw(Box::new(callback));
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:

cat -n bindings/rust/src/provider.rs | sed -n '330,370p'

Repository: wysaid/CameraCapture

Length of output: 1658


🏁 Script executed:

# Check if tests exercise the error callback
rg -n "set_error_callback|error_callback" bindings/rust --type rust -A 3 -B 1

Repository: wysaid/CameraCapture

Length of output: 2937


🏁 Script executed:

# Look for the complete context around the callback setup
rg -n "Box::into_raw" bindings/rust --type rust -B 10 -A 5

Repository: wysaid/CameraCapture

Length of output: 2449


Fix unsound pointer cast in error callback setup.

Line 352 unsafely casts a pointer to Arc<Mutex<F>> (concrete type) as a pointer to Arc<Mutex<dyn Fn>> (trait object). These pointer types have incompatible layouts: the trait object pointer is a fat pointer requiring two words (data + vtable), but the concrete pointer is a single word. This is undefined behavior.

Use the trait object type from the start:

-        let callback = Arc::new(Mutex::new(callback));
+        let callback: Arc<Mutex<dyn Fn(u32, &str) + Send + Sync>> = Arc::new(Mutex::new(callback));

This ensures Arc wraps the trait object directly, making the cast at line 352 valid and safe.

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In bindings/rust/src/provider.rs around lines 341 to 362, the code unsafely
casts a pointer to Arc<Mutex<F>> into a pointer to Arc<Mutex<dyn Fn(u32, &str) +
Send + Sync>>, which is UB because trait objects are fat pointers; instead
construct and store the trait object type from the start: change the local
callback variable to be an Arc<Mutex<dyn Fn(u32, &str) + Send + Sync>> (by
boxing/cloning the closure into that trait object), then
Box::into_raw(Box::new(callback_trait_object)) so the raw pointer has the
correct fat-pointer layout, and update any uses of the pointer and the unsafe
wrapper to treat user_data as *const Arc<Mutex<dyn Fn(u32, &str) + Send +
Sync>>; remove the earlier incorrect cast. Ensure all clones/ownership
adjustments still prevent drop and that locking and invocation remain unchanged.

@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.

Comment on lines +53 to +63
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,
)
};
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

Missing input buffer size validation—potential out-of-bounds read.

The function passes src_data.as_ptr() to the C FFI without validating that src_data.len() >= src_stride * height. If the caller provides an undersized buffer, the C code will read beyond the slice bounds, causing undefined behavior.

Add size validation before the unsafe block:

🔎 Proposed fix
     pub fn yuyv_to_rgb24(
         src_data: &[u8],
         src_stride: usize,
         width: u32,
         height: u32,
     ) -> Result<Vec<u8>> {
+        let expected_src_size = src_stride * height as usize;
+        if src_data.len() < expected_src_size {
+            return Err(CcapError::InvalidParameter(format!(
+                "source buffer too small: {} < {}",
+                src_data.len(),
+                expected_src_size
+            )));
+        }
+
         let dst_stride = (width * 3) as usize;

Committable suggestion skipped: line range outside the PR's diff.

Comment on lines +145 to +172
pub fn nv12_to_rgb24(
y_data: &[u8],
y_stride: usize,
uv_data: &[u8],
uv_stride: usize,
width: u32,
height: u32,
) -> 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_nv12_to_rgb24(
y_data.as_ptr(),
y_stride as c_int,
uv_data.as_ptr(),
uv_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.

⚠️ Potential issue | 🟠 Major

Same input validation issue applies to planar format conversions.

Similar to yuyv_to_rgb24, this function and i420_to_* variants pass buffer pointers to C FFI without validating sizes. For NV12:

  • y_data needs at least y_stride * height bytes
  • uv_data needs at least uv_stride * (height / 2) bytes

Add validation for all input planes before the unsafe block to prevent out-of-bounds reads.

🤖 Prompt for AI Agents
In bindings/rust/src/convert.rs around lines 145 to 172, the NV12 conversion
calls the C FFI without validating input buffer sizes; add checks before the
unsafe block to ensure y_data.len() >= y_stride * height and uv_data.len() >=
uv_stride * (height / 2) (use height as usize and integer division for the UV
plane), and return an Err with a descriptive message if either check fails so we
never pass invalid pointers to the FFI. Ensure the same style/Result error type
used elsewhere in the file is used for the returned error.

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.

Comment on lines +420 to +429
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);
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

Same UB: concrete Box<F> cast to Box<dyn Fn...> trait object.

Line 428 creates Box::new(callback) where callback is the concrete type F. Line 420 then casts user_data to *const Box<dyn Fn(&VideoFrame) -> bool + Send + Sync>. These have incompatible layouts (thin vs fat pointer).

🔎 Proposed fix
         // Store the callback to prevent it from being dropped
-        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);
🤖 Prompt for AI Agents
In bindings/rust/src/provider.rs around lines 420 to 429, the code boxes the
concrete closure type F then later casts the raw pointer to a Box<dyn
Fn(&VideoFrame) -> bool + Send + Sync>, causing undefined behavior due to
incompatible layouts; fix by boxing the closure as a trait object up front
(i.e., create a Box<dyn Fn(&VideoFrame)->bool + Send + Sync> from the closure,
ensuring F: Fn(&VideoFrame)->bool + Send + Sync + 'static so coercion succeeds),
store/convert that boxed trait object with Box::into_raw, and keep the raw
pointer cast and user_data access matching the Box<dyn ...> type so the layouts
agree and the callback isn't dropped.

- Remove unused PixelFormat import in capture_grab.rs
- Simplify device_index selection logic in capture_callback.rs
- Remove unnecessary type cast (device_index as i32)
- Add cache: false to Rust toolchain setup to avoid Cargo.toml lookup errors

Fixes clippy warnings:
- unused-imports
- if-same-then-else
- unnecessary-cast
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.

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.
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.

Problem Analysis:
- Windows builds were failing with type mismatch errors in error.rs and provider.rs
- Root cause: CcapErrorCode constants have platform-dependent types
  - Linux/macOS: c_uint (u32) via GCC/Clang
  - Windows: c_int (i32) via MSVC
- Original code assumed u32 and cast i32 to u32, causing type errors on Windows

Error Details:
- error.rs:97: expected u32, found i32 for CcapErrorCode constants
- provider.rs:353: error callback expected u32 but received i32

Changes:
1. error.rs: Convert i32 to CcapErrorCode type before matching
   - Use 'let code_u = code as CcapErrorCode' to handle platform differences
   - This ensures constants match regardless of underlying type

2. provider.rs: Change error callback signature from Fn(u32, &str) to Fn(i32, &str)
   - Use i32 as the common error code type across all platforms
   - Cast sys::CcapErrorCode to i32 when calling the callback

Verification:
- Linux: ✅ cargo build --features static-link
- Linux: ✅ cargo clippy --all-targets --features static-link -- -D warnings
- Linux: ✅ cargo build --no-default-features --features build-source
- Expected to fix Windows CI failures

This ensures the Rust bindings work correctly on all platforms despite
C enum type differences between compilers.
- Fix CMakeLists.txt circular dependency (remove ccap <- ccap-rust dep)
- Add Windows Media Foundation libraries for video playback support
- Fix error type in set_new_frame_callback (use InvalidParameter)
- Document global error callback memory management behavior

Fixes:
- Windows build failures in CI
- Circular dependency preventing CMake configuration
- Incorrect error type returned from callback registration
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)

Comment on lines +476 to +481
if let Some(callback_ptr) = self.callback_ptr.take() {
unsafe {
let _ = Box::from_raw(
callback_ptr as *mut Box<dyn Fn(&VideoFrame) -> bool + Send + Sync>,
);
}
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

Cleanup cast must match the stored type.

Line 479 casts callback_ptr as *mut Box<dyn Fn(&VideoFrame) -> bool + Send + Sync>. This is only correct if set_new_frame_callback stores a Box<dyn Fn...> (as suggested in the fix for that function). If the current code in set_new_frame_callback is used (which stores Box<F>), then this cast also exhibits undefined behavior.

Once the fix to set_new_frame_callback (lines 429-438) is applied, this cleanup will be correct.

Based on learnings from past reviews.

🤖 Prompt for AI Agents
In bindings/rust/src/provider.rs around lines 476 to 481, the Box::from_raw cast
used to drop the stored callback must match the exact type that was stored by
set_new_frame_callback; update the cleanup to cast to the same pointer type that
set_new_frame_callback stores (if you changed set_new_frame_callback to store
Box<dyn Fn(&VideoFrame) -> bool + Send + Sync> keep the current cast, otherwise
change the cast to the concrete Box<F> type previously used), and ensure
set_new_frame_callback and this drop path use the same boxed type so the
from_raw call is safe and defined.

- Fix formatting issues (trailing spaces in doc comments)
- Fix Windows MSVC linker errors by:
  * Always compiling ccap_convert_neon.cpp (provides hasNEON() symbol)
  * Using MSVC-compatible flags (/arch:AVX2) for AVX2 builds
  * Removing -mavx2/-mfma flags that are GCC/Clang specific

The hasNEON() function is needed on all platforms (returns false on non-ARM).
Previously it was only compiled on aarch64, causing undefined symbol errors
on Windows x86_64.
Add robust path conversion in utils.rs that safely handles Windows paths
containing Unicode characters. The new path_to_cstring() helper function:
- Uses to_string_lossy() on Windows to prevent UTF-8 conversion panics
- Maintains standard to_str() conversion on Unix-like systems
- Updates dump_frame_to_file(), dump_frame_to_directory(), and
  save_rgb_data_as_bmp() functions to use the new helper

This resolves Windows path handling issues where Unicode characters in
file paths would cause CString conversion failures.
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.

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