-
Notifications
You must be signed in to change notification settings - Fork 4
feat(rust): Add high-performance Rust bindings with SIMD acceleration #44
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
|
Caution Review failedThe pull request is closed. WalkthroughIntroduces comprehensive Rust bindings for the ccap video capture library as a new Cargo crate (ccap-rs) at bindings/rust, including a Provider API for camera operations, color conversion utilities, error handling, build system integration, CI workflow, examples, and documentation. Changes
Sequence Diagram(s)sequenceDiagram
actor User
participant RustApp as Rust Application
participant Provider
participant FFI as FFI Bindings
participant CLib as ccap C Library
participant Camera as Camera Device
User->>RustApp: Create provider
RustApp->>Provider: Provider::new()
Provider->>FFI: ccap_provider_create()
FFI->>CLib: Call native create
CLib->>Camera: Enumerate devices
CLib-->>FFI: Return provider handle
User->>RustApp: Select and open device
RustApp->>Provider: open_device()
Provider->>FFI: ccap_provider_open()
FFI->>CLib: Open device
CLib->>Camera: Access device
CLib-->>FFI: Success
User->>RustApp: Configure and start
RustApp->>Provider: set_resolution()/start()
Provider->>FFI: ccap_provider_set_property()/ccap_provider_start()
FFI->>CLib: Configure camera
CLib->>Camera: Apply settings and start streaming
loop Capture Frames
User->>RustApp: grab_frame()
RustApp->>Provider: grab_frame(timeout)
Provider->>FFI: ccap_provider_grab_frame()
FFI->>CLib: Grab from camera
CLib->>Camera: Retrieve frame
CLib-->>FFI: Return VideoFrame
FFI-->>Provider: Wrapped VideoFrame
Provider-->>RustApp: Result<Option<VideoFrame>>
RustApp->>User: Frame data (with safe accessors)
end
User->>RustApp: Stop capture
RustApp->>Provider: stop()
Provider->>FFI: ccap_provider_stop()
FFI->>CLib: Stop streaming
CLib->>Camera: Release resources
RustApp->>Provider: Drop
Provider->>FFI: ccap_provider_destroy()
Estimated code review effort🎯 4 (Complex) | ⏱️ ~50 minutes Possibly related PRs
Suggested labels
Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 3✅ Passed checks (3 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing touches
🧪 Generate unit tests (beta)
📜 Recent review detailsConfiguration used: Organization UI Review profile: CHILL Plan: Pro ⛔ Files ignored due to path filters (1)
📒 Files selected for processing (42)
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. Comment |
There was a problem hiding this 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
--releasemode, 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 insrc/async.rsto determine the minimum required set..vscode/tasks.json (1)
1227-1326: Consider adding dependency on C library build.The Rust binding tasks execute
cargocommands without ensuring the underlying C++ccaplibrary is built first. Since the Rust bindings link against the C++ library (viabuild.rs), these tasks may fail if the library hasn't been built.Consider adding
dependsOnfor 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 returnsOk(()), 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 bothNone(line 34) andErr(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
0regardless of the number of devices, making theifstatement 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, butFileOperationFailed(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,
InvalidDeviceandInvalidParameterare 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
Nonevariant in an error enum is unusual in Rust - typically success is represented byOk(())rather thanOk(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:
- Renaming to reflect actual behavior, or
- 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:
- Line 199:
unwrap()onflush()will panic on I/O errors- 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.
- Line 88: The
#[cfg(feature = "async")]is redundant since this is already inside a#[cfg(feature = "async")]impl block- Lines 91-92:
expect()will panic ifframe_stream()is called twice. Consider returningOption<impl Stream>orResultinstead.🔎 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: Preferimpl From<PixelFormat> for sys::CcapPixelFormatoverimpl Into.The Rust API guidelines recommend implementing
Fromrather thanInto, asFromautomatically provides anIntoimplementation. This is also what Clippy warns about withclippy::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.
No validation that
src_data.len()is sufficient for the given dimensions. For YUYV422, expected size iswidth * height * 2. Passing undersized buffers to C could cause buffer overreads.
width * height * 3could 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.
CStringis imported but never used in this file (onlyCStris used).🔎 Proposed fix
-use std::ffi::{CStr, CString}; +use std::ffi::CStr;
📜 Review details
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (1)
bindings/rust/Cargo.lockis excluded by!**/*.lock
📒 Files selected for processing (23)
.github/workflows/rust.yml.vscode/tasks.jsonCMakeLists.txtbindings/rust/.gitignorebindings/rust/Cargo.tomlbindings/rust/README.mdbindings/rust/RUST_INTEGRATION_SUMMARY.mdbindings/rust/build.rsbindings/rust/build_and_test.shbindings/rust/examples/capture_callback.rsbindings/rust/examples/capture_grab.rsbindings/rust/examples/minimal_example.rsbindings/rust/examples/print_camera.rsbindings/rust/src/async.rsbindings/rust/src/convert.rsbindings/rust/src/error.rsbindings/rust/src/frame.rsbindings/rust/src/lib.rsbindings/rust/src/provider.rsbindings/rust/src/types.rsbindings/rust/src/utils.rsbindings/rust/tests/integration_tests.rsbindings/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
PixelFormatensures 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: DerivingDefaulton opaque handle types is not a practical risk given the API design.The C headers define
CcapProviderandCcapVideoFrameas forward-declared opaque types. Bindgen does generateDefaultderives 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 (
Providerstruct) that never use.default()- The low-level
sysmodule containing raw opaque types is marked for advanced users only- Concrete types that actually call
.default()in the code (CcapDeviceNamesList,CcapDeviceInfo) are safe data containersNo action required; the current design prevents unsafe usage of
Defaulton 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
LogLevelenum properly mirrors the C API levels and theto_c_enumconversion is correct.bindings/rust/src/async.rs (1)
72-86: LGTM!The
grab_frame_timeoutcorrectly usesspawn_blockingto offload the blocking FFI call, andblocking_lock()is the right choice insidespawn_blocking. The timeout-to-millis conversion and error propagation are handled properly.bindings/rust/src/types.rs (1)
162-196: LGTM!The
ColorConversionBackendenum correctly maps to and from C API values. The fallback toCpufor 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_callbackimplementation correctly handles the callback type throughBox::into_rawand reconstruction. The memory leak is acceptable for callback registration that lives for the provider's lifetime, though addingDropcleanup or a dedicated unregister mechanism would be ideal for long-running applications.
439-448: LGTM!The
Dropimplementation correctly releases the C handle and nullifies the pointer to prevent double-free.
bindings/rust/src/frame.rs
Outdated
| // Make VideoFrame Send + Sync if the underlying C library supports it | ||
| unsafe impl Send for VideoFrame {} | ||
| unsafe impl Sync for VideoFrame {} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🏁 Script executed:
cd bindings/rust && head -200 src/frame.rs | tail -50Repository: wysaid/CameraCapture
Length of output: 1445
🏁 Script executed:
cat -n bindings/rust/src/frame.rs | sed -n '1,50p'Repository: wysaid/CameraCapture
Length of output: 1945
🏁 Script executed:
# Check what C library this binding is for
git ls-files | grep -E "(Cargo.toml|build\.rs)" | head -5Repository: wysaid/CameraCapture
Length of output: 111
🌐 Web query:
ccap camera capture library thread safety documentation
💡 Result:
I found the library's primary docs on its GitHub repo (ccap / CameraCapture). The README + API reference describe Provider/VideoFrame APIs (provider.open/start/stop/grab, new-frame callbacks, C API equivalents) but do not include an explicit "thread-safety" section or per-function thread-safety guarantees. See the repo README/API reference for the details to inspect (Provider::grab, setNewFrameCallback, ccap_provider_grab, etc.). [1]
If you need definitive thread-safety guarantees I recommend:
- Inspecting the relevant headers/source for locking or std::mutex usage in Provider and C API implementations, and
- Opening an issue or asking the maintainer on the repo for explicit thread-safety guidance.
Source: GitHub — wysaid/CameraCapture (ccap) README/API. [1]
References
[1] wysaid/CameraCapture — README & API (ccap).
🏁 Script executed:
cat bindings/rust/Cargo.tomlRepository: wysaid/CameraCapture
Length of output: 1198
🏁 Script executed:
# Search for other unsafe impl Send/Sync patterns and safety documentation
rg "unsafe impl (Send|Sync)" bindings/rust/src/ -A 2Repository: wysaid/CameraCapture
Length of output: 424
🏁 Script executed:
# Check if there are any safety comments or documentation about thread safety
rg -i "thread|safe|send|sync" bindings/rust/src/frame.rs -B 2 -A 2Repository: wysaid/CameraCapture
Length of output: 1869
🏁 Script executed:
# Look at the C library source to understand frame lifecycle
cat bindings/rust/build.rsRepository: wysaid/CameraCapture
Length of output: 3011
Verify and document thread-safety assumptions for Send+Sync implementations.
The unsafe impl Send + Sync for VideoFrame assumes the underlying C library is thread-safe, but the ccap library's public documentation does not include explicit thread-safety guarantees. VideoFrame holds a raw *mut CcapVideoFrame pointer, which requires verified thread-safety to safely implement these traits. Either provide concrete evidence of the C library's thread-safety (with a detailed safety comment citing the source), or make these trait bounds conditional on a feature flag until thread-safety is confirmed with the library maintainers. The same issue applies to unsafe impl Send for Provider.
🤖 Prompt for AI Agents
In bindings/rust/src/frame.rs around lines 158-160, the unsafe impls making
VideoFrame Send+Sync (and similarly Provider) assume the underlying C ccap
library is thread-safe but lack justification; either add a detailed safety
comment above each impl documenting concrete evidence (link to ccap docs or
maintainer statement, describe invariants that make raw *mut CcapVideoFrame safe
across threads) and explain why no interior mutability/races occur, OR
remove/guard the impls behind a feature flag (e.g., "ccap_threadsafe") and add a
clear TODO to enable only after confirmation from library maintainers; update
Cargo.toml features accordingly and ensure tests/builds reflect the conditional
implementation.
There was a problem hiding this 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 |
bindings/rust/src/utils.rs
Outdated
| use crate::types::PixelFormat; | ||
| use crate::frame::VideoFrame; | ||
| use crate::sys; | ||
| use std::ffi::{ CString}; |
Copilot
AI
Dec 27, 2025
There was a problem hiding this comment.
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.
| use std::ffi::{ CString}; |
| 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) | ||
| } | ||
| } |
Copilot
AI
Dec 27, 2025
There was a problem hiding this comment.
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.
bindings/rust/src/provider.rs
Outdated
| if success { | ||
| Ok(()) | ||
| } else { | ||
| Err(CcapError::CaptureStartFailed) |
Copilot
AI
Dec 27, 2025
There was a problem hiding this comment.
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.
| Err(CcapError::CaptureStartFailed) | |
| Err(CcapError::CaptureStopFailed) |
| 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) | ||
| } | ||
| } | ||
| } |
Copilot
AI
Dec 27, 2025
There was a problem hiding this comment.
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.
bindings/rust/README.md
Outdated
| println!("Camera opened successfully!"); | ||
|
|
||
| // Capture a frame | ||
| if let Some(frame) = provider.grab_frame_blocking()? { |
Copilot
AI
Dec 27, 2025
There was a problem hiding this comment.
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.
| if let Some(frame) = provider.grab_frame_blocking()? { | |
| if let Some(frame) = provider.grab_frame()? { |
bindings/rust/src/provider.rs
Outdated
|
|
||
| /// Set pixel format | ||
| pub fn set_pixel_format(&mut self, format: PixelFormat) -> Result<()> { | ||
| self.set_property(PropertyName::PixelFormatOutput, format as u32 as f64) |
Copilot
AI
Dec 27, 2025
There was a problem hiding this comment.
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.
| self.set_property(PropertyName::PixelFormatOutput, format as u32 as f64) | |
| self.set_property( | |
| PropertyName::PixelFormatOutput, | |
| format.to_c_enum() as u32 as f64, | |
| ) |
bindings/rust/src/provider.rs
Outdated
| /// 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)) |
Copilot
AI
Dec 27, 2025
There was a problem hiding this comment.
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.
| Ok(PixelFormat::from(format_val)) | |
| Ok(PixelFormat::from_c_enum(format_val as sys::CcapPixelFormat)) |
| ``` | ||
|
|
||
| ## Building | ||
|
|
Copilot
AI
Dec 27, 2025
There was a problem hiding this comment.
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).
bindings/rust/src/async.rs
Outdated
| pub struct AsyncProvider { | ||
| provider: Arc<Mutex<SyncProvider>>, | ||
| frame_receiver: Option<mpsc::UnboundedReceiver<VideoFrame>>, | ||
| _frame_sender: mpsc::UnboundedSender<VideoFrame>, |
Copilot
AI
Dec 27, 2025
There was a problem hiding this comment.
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.
bindings/rust/src/utils.rs
Outdated
| let selected_index = input.trim().parse::<usize>() | ||
| .unwrap_or(0); |
Copilot
AI
Dec 27, 2025
There was a problem hiding this comment.
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.
| 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 | |
| } | |
| }; |
There was a problem hiding this 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 any1.xrelease, 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-linkandbuild-sourceappear to be mutually exclusive by design (one for development, one for distribution), but nothing prevents both from being enabled. Whilebuild.rschecksCARGO_FEATURE_BUILD_SOURCEfirst 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
📒 Files selected for processing (2)
bindings/rust/Cargo.tomlbindings/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) andcanUseAVX2()runtime checks before execution.NEON: Platform-specific
hasNEON_()with compile-time gate (#if ENABLE_NEON_IMPfor 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_IMPandcanUseNEON()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.
There was a problem hiding this 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@v1action 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
📒 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/Debugto match build.rs expectations for Makefiles- Windows builds in
buildwhich createsbuild/Debug/ccap.libfor 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-linkflag 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 cmakeIf 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-sourceto 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.
There was a problem hiding this 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 "calledOption::unwrap()on aNonevalue" 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-changeddirectives (lines 166-168) use hardcoded../../include/*paths that assume repository layout. When the crate is packaged for crates.io (ccap_rootpoints tonative/), 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
OnceLockto avoid repeated leaks on multiple calls. The current implementation will leak memory each timeset_error_callbackis called. While acceptable for single-call usage, consider theOnceLockpattern 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
DisplayandErrorimplementations (lines 64-89) work correctly but could be simplified using thethiserrorcrate 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 toi32. 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 i32could overflow on 64-bit systems. Consider usingi32::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 entireimpl AsyncProviderblock 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 viainfo(). For performance-critical code paths where multiple properties are needed, users should callinfo()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, theSendimpl forProviderassumes 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
📒 Files selected for processing (17)
.github/workflows/rust.ymlbindings/rust/README.mdbindings/rust/build.rsbindings/rust/build_and_test.shbindings/rust/examples/capture_callback.rsbindings/rust/examples/capture_grab.rsbindings/rust/examples/minimal_example.rsbindings/rust/examples/print_camera.rsbindings/rust/src/async.rsbindings/rust/src/convert.rsbindings/rust/src/error.rsbindings/rust/src/frame.rsbindings/rust/src/lib.rsbindings/rust/src/provider.rsbindings/rust/src/types.rsbindings/rust/src/utils.rsbindings/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 ofsrc/async.rsAsyncProvider::grab_frame()matches the implementation at line 91, which defaults to a 0ms timeoutNo 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_blockingwithblocking_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) / 2for 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_callbackmethod properly frees the boxed callback, andDropensures resources are released. This addresses the previous memory leak concerns for frame callbacks.Also applies to: 480-491
| ) -> 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) | ||
| } |
There was a problem hiding this comment.
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 -20Repository: wysaid/CameraCapture
Length of output: 878
🏁 Script executed:
fd -e h -e rs "convert" | grep -E "\.(h|rs)$" | head -20Repository: 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 -100Repository: wysaid/CameraCapture
Length of output: 3532
🏁 Script executed:
cat include/ccap_convert_c.h | head -100Repository: wysaid/CameraCapture
Length of output: 3536
🏁 Script executed:
cat -n bindings/rust/src/convert.rs | head -250Repository: wysaid/CameraCapture
Length of output: 8617
🏁 Script executed:
cat include/ccap_convert_c.h | tail -150Repository: 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.hRepository: 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.
|
@coderabbitai review |
✅ Actions performedReview triggered.
|
There was a problem hiding this 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:Resultwrapper is misleading—C FFI functions returnvoid.All conversion functions (including this one) return
Result<Vec<u8>>but the underlying C functions likeccap_convert_yuyv_to_rgb24returnvoidwith no error indication. TheOk(dst_data)on line 65 always succeeds, making theResulttype 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 betweenccapandccap-rusttargets.Line 394 declares
ccap-rustwithDEPENDS ccap, meaningccap-rustdepends onccap. Line 405 then addsadd_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 bothccapandccap-rustif 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 castsuser_datato*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--releasemode regardless of CMake build type.When CMake is configured for Debug, the C++
ccaplibrary will have a debug postfix (ccapdon MSVC per line 178). However,cargo build --releasehere 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 usinglet _ = 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
📒 Files selected for processing (6)
.vscode/tasks.jsonCMakeLists.txtbindings/rust/src/convert.rsbindings/rust/src/provider.rsbindings/rust/src/types.rsbindings/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
LogLevelenum provides a clean Rust abstraction over the C API's bitmask-based log levels. Theto_c_enummethod correctly maps to the underlying C constants.bindings/rust/src/provider.rs (3)
161-186: LGTM!The
open_devicemethod now properly handles thedevice_nameparameter by recreating the provider with the specified device, addressing the previous review feedback.
319-323: LGTM!The
pixel_formatmethod now correctly usesPixelFormat::from_c_enumfor the type conversion, addressing the previous review feedback.
477-488: LGTM!The
Dropimplementation properly cleans up the callback before destroying the handle, preventing resource leaks.
| callback_ptr: Option<*mut std::ffi::c_void>, | ||
| } | ||
|
|
||
| unsafe impl Send for Provider {} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 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 fRepository: 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 -nRepository: 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 -10Repository: 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 -200Repository: 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 3Repository: 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 2Repository: 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 -100Repository: 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.hRepository: 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.rsRepository: 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.
There was a problem hiding this 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: falseRemoving
cache: falseenables 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-linkfeature enabled. Conditional compilation blocks under thebuild-sourcefeature won't be linted, potentially missing issues in distribution mode.🔎 Suggested addition
Add a clippy check in the
build-sourcejob 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-linkjob:cache: falsedisables 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
📒 Files selected for processing (3)
.github/workflows/rust.ymlbindings/rust/examples/capture_callback.rsbindings/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: readpermission addresses the security concern flagged by CodeQL.
27-135: Workflow structure effectively tests both build modes.The separation into
static-link(development) andbuild-source(distribution) jobs provides good coverage for both usage scenarios of the Rust bindings.
115-119: Thebuild-sourcejob correctly omits cmake as it compiles C++ directly with thecccrate.The build.rs script uses
cc::Build::new()to compile C++ sources directly without requiring CMake. It includes only the project's standard headers frominclude/andsrc/directories, not cmake-generated files. Thebuild-essentialandpkg-configpackages are sufficient since system compilers (g++, clang, MSVC) are pre-installed on GitHub runners.
There was a problem hiding this 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: Chainedunwrap()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-changeddirectives use hardcoded relative paths (../../include/*) that assume repository layout. When the crate is packaged (withccap_rootpointing tonative/), 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: UpdateCargoCallbacksto use the new constructor API.Starting in bindgen 0.68,
CargoCallbacksrequires 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
📒 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.
There was a problem hiding this 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 thatSendis safe for non-thread-safe C API.Previous reviews flagged this
unsafe impl Sendbecause the underlying C++ Provider is documented as "not thread-safe." However,Sendonly allows transferring ownership between threads (not concurrent access, which would requireSync). If the C API can safely be used from any single thread (just not concurrently), thenSendis 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 asBox<Arc<Mutex<F>>>, but line 358 reconstructs it as*const Arc<Mutex<dyn Fn...>>(trait object).The problem:
Arc<Mutex<F>>andArc<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)wherecallbackis the concrete typeF, but line 429 reconstructs it as*const Box<dyn Fn(&VideoFrame) -> bool + Send + Sync>.The problem:
Box<F>is a thin pointer, whileBox<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
-mavx2and-mfmaare 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 withccap_rootpointing tonative/.🔎 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
📒 Files selected for processing (3)
CMakeLists.txtbindings/rust/build.rsbindings/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)
There was a problem hiding this 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_cstringhelper 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_fileanddump_frame_to_directoryshare 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
📒 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_argumentssuppression is appropriate. The implementation correctly wraps the C API with proper error handling.
8-9: LGTM!The
Utilsstruct as a namespace for utility functions is a standard Rust pattern, andsave_frame_as_bmpappropriately delegates todump_frame_to_filefor 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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
♻️ Duplicate comments (2)
bindings/rust/build.rs (2)
16-21: Chained unwrap() is fragile and could panic.This issue was previously flagged but not yet addressed. The chained
.parent().unwrap().parent().unwrap()will panic if the manifest directory structure is unexpected. This creates a fragile assumption about repository layout.🔎 Proposed fix with better error handling
( - manifest_path - .parent() - .unwrap() - .parent() - .unwrap() - .to_path_buf(), + manifest_path + .parent() + .and_then(|p| p.parent()) + .expect("Cargo manifest must be at least 2 directories deep (bindings/rust/)") + .to_path_buf(), false, )
194-196: Hardcoded paths in rerun triggers break in packaged mode.This issue was previously flagged but not yet addressed. The
rerun-if-changeddirectives use hardcoded relative paths (../../include/*) that assume repository layout. When the crate is packaged withccap_rootpointing tonative/, these paths won't exist, causing the rerun triggers to fail silently.🔎 Proposed fix using ccap_root
println!("cargo:rerun-if-changed=wrapper.h"); - println!("cargo:rerun-if-changed=../../include/ccap_c.h"); - println!("cargo:rerun-if-changed=../../include/ccap_utils_c.h"); - println!("cargo:rerun-if-changed=../../include/ccap_convert_c.h"); + println!("cargo:rerun-if-changed={}/include/ccap_c.h", ccap_root.display()); + println!("cargo:rerun-if-changed={}/include/ccap_utils_c.h", ccap_root.display()); + println!("cargo:rerun-if-changed={}/include/ccap_convert_c.h", ccap_root.display());
📜 Review details
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (2)
bindings/rust/build.rssrc/ccap_convert_frame.cpp
🧰 Additional context used
🧬 Code graph analysis (1)
bindings/rust/build.rs (8)
bindings/rust/examples/capture_callback.rs (1)
main(6-108)bindings/rust/examples/capture_grab.rs (1)
main(4-73)bindings/rust/examples/minimal_example.rs (1)
main(3-55)bindings/rust/examples/print_camera.rs (1)
main(62-83)bindings/rust/src/error.rs (1)
from(92-118)bindings/rust/src/types.rs (5)
from(35-52)from(87-103)from(116-126)from(154-167)from(220-225)bindings/rust/src/provider.rs (1)
new(18-29)bindings/rust/src/async.rs (1)
new(30-39)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (20)
- GitHub Check: Static Link (Dev) (windows-latest)
- GitHub Check: Build (Debug-gcc-shared)
- GitHub Check: Windows Build MinGW (Release-shared)
- GitHub Check: Build (Release-clang-static)
- GitHub Check: Build Fedora (Release-shared)
- GitHub Check: Build ARM64 (Release-gcc-static)
- GitHub Check: Build Fedora (Release-static)
- GitHub Check: Build (Release-clang-shared)
- GitHub Check: Windows Build VS2022 (Debug-static)
- GitHub Check: Windows Build MinGW (Release-static)
- GitHub Check: Windows VS2022 Release - No File Playback
- GitHub Check: Windows Build VS2022 (Release-shared)
- GitHub Check: Windows Build VS2022 (Release-static)
- GitHub Check: Windows Build MinGW (Debug-static)
- GitHub Check: Windows Build VS2022 (Debug-shared)
- GitHub Check: Windows Build MinGW (Debug-shared)
- GitHub Check: Build Source (Dist) (macos-latest)
- GitHub Check: Static Link (Dev) (ubuntu-latest)
- GitHub Check: Static Link (Dev) (macos-latest)
- GitHub Check: Static Link (Dev) (windows-latest)
| assert(frame->pixelFormat == toFormat); | ||
| assert(frame->allocator != nullptr && frame->data[0] == frame->allocator->data()); | ||
| frame->sizeInBytes = frame->allocator->size(); | ||
| frame->sizeInBytes = static_cast<uint32_t>(frame->allocator->size()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Add bounds check to prevent silent truncation.
The explicit cast to uint32_t fixes the Windows compiler warning but introduces a potential silent truncation risk. If frame->allocator->size() exceeds UINT32_MAX (4GB), the value will be truncated, leading to an incorrect sizeInBytes. While video frames rarely exceed 4GB, high-resolution formats or future use cases could trigger this. Silent truncation in size fields can cause memory safety issues if sizeInBytes is used for bounds checking downstream.
🔎 Proposed fix: Add assertion to catch overflow
- frame->sizeInBytes = static_cast<uint32_t>(frame->allocator->size());
+ auto allocatorSize = frame->allocator->size();
+ assert(allocatorSize <= UINT32_MAX && "Frame size exceeds uint32_t maximum");
+ frame->sizeInBytes = static_cast<uint32_t>(allocatorSize);📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| frame->sizeInBytes = static_cast<uint32_t>(frame->allocator->size()); | |
| auto allocatorSize = frame->allocator->size(); | |
| assert(allocatorSize <= UINT32_MAX && "Frame size exceeds uint32_t maximum"); | |
| frame->sizeInBytes = static_cast<uint32_t>(allocatorSize); |
🤖 Prompt for AI Agents
In src/ccap_convert_frame.cpp around line 245, the assignment casts
frame->allocator->size() to uint32_t which can silently truncate values >
UINT32_MAX; add an explicit bounds check: obtain the allocator size in a size_t,
compare it to std::numeric_limits<uint32_t>::max(), and if it exceeds the limit,
handle the overflow deterministically (e.g., log an error and return/propagate a
failure, or assert in debug builds) instead of performing the cast; only perform
the static_cast when the check passes so sizeInBytes remains correct and
truncation cannot occur.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 2
🧹 Nitpick comments (2)
.github/workflows/rust.yml (2)
30-106: Consider adding timeout for job reliability.No timeout is specified for this job. Consider adding
timeout-minutesto prevent runaway builds from consuming excessive CI minutes if a step hangs.🔎 Suggested addition
static-link: name: Static Link (Dev) + timeout-minutes: 30 strategy: matrix:
111-116: Consider adding timeout for job reliability.Similar to the static-link job, consider adding
timeout-minutesto prevent hanging builds from consuming excessive CI minutes.🔎 Suggested addition
build-source: name: Build Source (Dist) + timeout-minutes: 30 strategy: matrix:
📜 Review details
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
.github/workflows/rust.yml
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (16)
- GitHub Check: Build ARM64 (Release-gcc-shared)
- GitHub Check: Build (Debug-gcc-static)
- GitHub Check: Build (Release-gcc-static)
- GitHub Check: Build Source (Dist) (windows-latest)
- GitHub Check: Build Fedora (Release-shared)
- GitHub Check: Build (Release-clang-shared)
- GitHub Check: Build (Debug-clang-static)
- GitHub Check: Build ARM64 (Release-gcc-static)
- GitHub Check: Build Fedora (Debug-static)
- GitHub Check: Build (Release-clang-static)
- GitHub Check: Build (Release-gcc-shared)
- GitHub Check: Build Fedora (Release-static)
- GitHub Check: Windows Build MinGW (Release-static)
- GitHub Check: Windows Build VS2026 (Release-static)
- GitHub Check: Windows Build VS2022 (Release-static)
- GitHub Check: Windows Build VS2022 (Release-shared)
🔇 Additional comments (2)
.github/workflows/rust.yml (2)
1-26: LGTM! Workflow triggers and permissions are well-configured.The workflow correctly triggers on relevant path changes and implements least-privilege permissions with
contents: read. The path filters appropriately scope CI runs to changes affecting Rust bindings or the underlying C++ library.
111-142: No cmake installation needed for build-source job.The build-source feature uses the
cccrate for direct C++ compilation (build.rs lines 29-104), which does not require cmake. The static-link job installs cmake because it needs to build a pre-built library using cmake first. The build-source job bypasses the cmake build step entirely and compiles sources directly via cargo.Likely an incorrect or invalid review comment.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
♻️ Duplicate comments (1)
.github/workflows/rust.yml (1)
135-139: Remove ineffectiverefreshenvcommand.The
refreshenvcommand on line 139 won't have the intended effect in GitHub Actions since each step runs in a separate shell session. Environment variables set by the choco installations will automatically be available in subsequent steps without this command. This same issue was addressed in the static-link job but remains here.🔎 Proposed fix
- name: Install system dependencies (Windows) if: matrix.os == 'windows-latest' run: | choco install llvm -y - refreshenv
📜 Review details
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (2)
.github/workflows/rust.ymlbindings/rust/tests/integration_tests.rs
🧰 Additional context used
🧬 Code graph analysis (1)
bindings/rust/tests/integration_tests.rs (3)
bindings/rust/src/provider.rs (3)
is_opened(209-211)version(288-299)with_device(32-46)bindings/rust/src/lib.rs (1)
version(38-40)bindings/rust/src/types.rs (2)
from_c_enum(62-64)from_c_enum(197-207)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (17)
- GitHub Check: Windows Build VS2022 (Release-shared)
- GitHub Check: Windows Build MinGW (Debug-shared)
- GitHub Check: Windows Build VS2022 (Debug-static)
- GitHub Check: Windows Build MinGW (Debug-static)
- GitHub Check: Windows Build VS2026 (Debug-static)
- GitHub Check: Build ARM64 (Release-gcc-shared)
- GitHub Check: Build (Release-gcc-shared)
- GitHub Check: Build Fedora (Release-static)
- GitHub Check: Windows Build MinGW (Release-static)
- GitHub Check: Build ARM64 (Release-gcc-static)
- GitHub Check: Windows Build VS2022 (Release-static)
- GitHub Check: Build (Release-clang-static)
- GitHub Check: Windows VS2022 Release - No File Playback
- GitHub Check: macOS Build (Debug-static)
- GitHub Check: Build Source (Dist) (windows-latest)
- GitHub Check: Static Link (Dev) (macos-latest)
- GitHub Check: Static Link (Dev) (windows-latest)
🔇 Additional comments (2)
.github/workflows/rust.yml (1)
1-164: Well-structured CI workflow for dual build modes.The workflow appropriately separates static linking (development mode) from source building (distribution mode), testing both across all major platforms. The platform-specific dependency installation, LIBCLANG_PATH configuration, and build steps are correctly implemented to support the Rust bindings' build.rs expectations.
bindings/rust/tests/integration_tests.rs (1)
1-90: Comprehensive integration test coverage.The test suite provides good coverage of the ccap bindings' core functionality with appropriate separation between camera-dependent and camera-independent tests. The use of the
CCAP_SKIP_CAMERA_TESTSenvironment variable correctly allows CI to run without physical cameras, and the tests include validation of both success and error paths.
| if skip_camera_tests() { | ||
| eprintln!("Skipping device_operations_without_camera due to CCAP_SKIP_CAMERA_TESTS"); | ||
| return; | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Remove skip guard from camera-independent test.
The test test_device_operations_without_camera is designed to verify that basic operations (provider creation, device listing, version retrieval) work regardless of camera presence, as indicated by the comment on line 81. However, the skip guard on lines 77-80 prevents it from running when CCAP_SKIP_CAMERA_TESTS is set, which is the case in CI (line 23 of rust.yml).
This defeats the purpose of the test—validating camera-independent operations should always run in CI to catch regressions in basic functionality.
🔎 Proposed fix
#[test]
fn test_device_operations_without_camera() {
- if skip_camera_tests() {
- eprintln!("Skipping device_operations_without_camera due to CCAP_SKIP_CAMERA_TESTS");
- return;
- }
// Test that operations work regardless of camera presence
let provider = Provider::new().expect("Failed to create provider");🤖 Prompt for AI Agents
In bindings/rust/tests/integration_tests.rs around lines 77 to 80, the test is
incorrectly gated by a CCAP_SKIP_CAMERA_TESTS skip guard which prevents
test_device_operations_without_camera from running in CI; remove the if
skip_camera_tests() { eprintln!(...); return; } block (and any associated early
return) so the test always executes, leaving any camera-dependent tests separate
and unchanged.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 0
🧹 Nitpick comments (2)
scripts/update_version.sh (2)
94-95: Consider a more targeted pattern for Cargo.toml version.The current sed pattern matches any line starting with
version =, which could inadvertently match dependency version specifications if the file structure is unusual. While this works in typical cases (where the package version appears first), you could make it more robust by:
- Using a multi-line sed/awk command to scope the replacement to the
[package]section, or- Using
cargodirectly:cargo metadata --format-version 1 --no-depsto validate, or leveraging tools liketomlq/cargo-edit.Example: More targeted awk-based approach
-# 5. Update Rust crate version (Cargo.toml) -update_file "s/^version = \".*\"$/version = \"$NEW_VERSION\"/" "$PROJECT_ROOT/bindings/rust/Cargo.toml" "Rust crate version (Cargo.toml)" +# 5. Update Rust crate version (Cargo.toml) +CARGO_TOML="$PROJECT_ROOT/bindings/rust/Cargo.toml" +if [ -f "$CARGO_TOML" ]; then + awk -v ver="$NEW_VERSION" ' + /^\[package\]/ { in_package=1 } + /^\[/ && !/^\[package\]/ { in_package=0 } + in_package && /^version = / { $0 = "version = \"" ver "\"" } + { print } + ' "$CARGO_TOML" > "$CARGO_TOML.tmp" && mv "$CARGO_TOML.tmp" "$CARGO_TOML" + echo "✅ Updated Rust crate version (Cargo.toml)" +else + echo "⚠️ $CARGO_TOML not found, skipping Rust crate version" +fi
97-116: Prefer using Cargo to regenerate the lockfile instead of manual editing.Manually editing
Cargo.lock(an auto-generated file) can lead to inconsistencies and is not standard Rust practice. When Cargo rebuilds or updates dependencies, it may overwrite your manual changes or produce unexpected results.Recommended approach:
- Update
Cargo.toml(which you already do in Step 5)- Run
cargo update -p ccap(orcargo check) to let Cargo regenerateCargo.lockwith the correct versionThis ensures consistency with Cargo's dependency resolution and follows Rust ecosystem conventions.
Proposed alternative using Cargo
-# 6. Update Rust lockfile entry for ccap (Cargo.lock) -RUST_LOCKFILE="$PROJECT_ROOT/bindings/rust/Cargo.lock" -if [ -f "$RUST_LOCKFILE" ]; then - python3 - "$RUST_LOCKFILE" "$NEW_VERSION" <<'PY' -import pathlib, re, sys - -path = pathlib.Path(sys.argv[1]) -new_ver = sys.argv[2] -text = path.read_text() -pattern = r'(?m)(^name = "ccap"\nversion = ")[^"]+("\n)' -new_text, count = re.subn(pattern, rf"\1{new_ver}\2", text, count=1) -if count: - path.write_text(new_text) - print(f"✅ Updated Rust lockfile ccap version to {new_ver}") -else: - print(f"⚠️ ccap entry not found in {path}, lockfile not modified") -PY -else - echo "⚠️ $RUST_LOCKFILE not found, skipping Rust lockfile update" -fi +# 6. Regenerate Rust lockfile using Cargo +RUST_DIR="$PROJECT_ROOT/bindings/rust" +if [ -d "$RUST_DIR" ] && [ -f "$RUST_DIR/Cargo.toml" ]; then + if command -v cargo &> /dev/null; then + (cd "$RUST_DIR" && cargo update -p ccap 2>&1) && \ + echo "✅ Regenerated Rust lockfile (Cargo.lock)" || \ + echo "⚠️ Failed to update Rust lockfile, please run 'cargo update -p ccap' manually" + else + echo "⚠️ cargo not found, skipping Rust lockfile update (run 'cargo update -p ccap' manually)" + fi +else + echo "⚠️ Rust bindings directory not found, skipping Rust lockfile update" +fi
📜 Review details
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (1)
bindings/rust/Cargo.lockis excluded by!**/*.lock
📒 Files selected for processing (3)
bindings/rust/Cargo.tomlbindings/rust/build_and_test.shscripts/update_version.sh
🚧 Files skipped from review as they are similar to previous changes (2)
- bindings/rust/build_and_test.sh
- bindings/rust/Cargo.toml
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-12-31T04:11:08.021Z
Learnt from: CR
Repo: wysaid/CameraCapture PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-12-31T04:11:08.021Z
Learning: Run `./scripts/update_version.sh <version>` to update version files
Applied to files:
scripts/update_version.sh
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (18)
- GitHub Check: Build Source (Dist) (macos-latest)
- GitHub Check: Static Link (Dev) (macos-latest)
- GitHub Check: Build Source (Dist) (windows-latest)
- GitHub Check: Build ARM64 (Release-gcc-shared)
- GitHub Check: Build (Release-gcc-shared)
- GitHub Check: Build (Release-clang-static)
- GitHub Check: Build ARM64 (Release-gcc-static)
- GitHub Check: Build Fedora (Debug-shared)
- GitHub Check: Build (Release-clang-shared)
- GitHub Check: Build Fedora (Release-shared)
- GitHub Check: Windows Build MinGW (Release-static)
- GitHub Check: Windows Build VS2022 (Release-static)
- GitHub Check: Windows Build VS2022 (Debug-static)
- GitHub Check: Windows Build VS2022 (Debug-shared)
- GitHub Check: Windows VS2022 Release - No File Playback
- GitHub Check: Windows Build VS2022 (Release-shared)
- GitHub Check: Windows Build MinGW (Debug-shared)
- GitHub Check: Windows Build MinGW (Debug-static)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
🧹 Nitpick comments (1)
bindings/rust/README.md (1)
187-187: Minor: Improve phrasing for professional tone.The phrase "Please feel free to submit a Pull Request" is somewhat informal. Consider a more direct alternative like "Contributions and pull requests are welcome" for a more professional tone.
📜 Review details
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (2)
bindings/rust/README.mdbindings/rust/build.rs
🧰 Additional context used
🧬 Code graph analysis (1)
bindings/rust/build.rs (2)
bindings/rust/src/error.rs (1)
from(92-118)bindings/rust/src/types.rs (5)
from(35-52)from(87-103)from(116-126)from(154-167)from(220-225)
🪛 LanguageTool
bindings/rust/README.md
[style] ~187-~187: Consider using a less common alternative to make your writing sound more unique and professional.
Context: ...ontributing Contributions are welcome! Please feel free to submit a Pull Request. ## License Thi...
(FEEL_FREE_TO_STYLE_ME)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (20)
- GitHub Check: Build (Debug-gcc-shared)
- GitHub Check: Build (Debug-gcc-static)
- GitHub Check: Build (Release-clang-static)
- GitHub Check: Windows Build VS2022 (Release-static)
- GitHub Check: Build Fedora (Debug-shared)
- GitHub Check: Build Fedora (Debug-static)
- GitHub Check: Build Fedora (Release-static)
- GitHub Check: Build ARM64 (Debug-gcc-static)
- GitHub Check: Windows Build VS2026 (Release-static)
- GitHub Check: Build ARM64 (Release-gcc-static)
- GitHub Check: Windows Build VS2022 (Debug-shared)
- GitHub Check: Build Source (Dist) (windows-latest)
- GitHub Check: Windows Build MinGW (Release-static)
- GitHub Check: Windows Build VS2022 (Release-shared)
- GitHub Check: Build Source (Dist) (macos-latest)
- GitHub Check: Windows Build MinGW (Debug-shared)
- GitHub Check: Windows Build MinGW (Release-shared)
- GitHub Check: Windows Build MinGW (Debug-static)
- GitHub Check: Static Link (Dev) (windows-latest)
- GitHub Check: Static Link (Dev) (macos-latest)
🔇 Additional comments (9)
bindings/rust/README.md (2)
68-90: Verify API consistency between sync and async examples.The async example shows different method names/signatures compared to the sync version:
- Line 79 calls
provider.open(Some(&devices[0]), true)but the sync example (line 49) usesprovider.open_device(). Clarify ifAsyncProviderhas different method names or if this is an error.- Line 82 calls
provider.grab_frame()without a timeout parameter, whereas the sync version requires a timeout (line 53:grab_frame(3000)). Confirm whether the async API supports timeout differently or if the example is incomplete.Also verify that the async example documentation aligns with the actual
AsyncProviderAPI.
92-111: Verify that referenced examples exist and are executable.The Examples section lists several example invocations (print_camera, minimal_example, capture_grab, capture_callback) but does not reference the corresponding example file names. Ensure that:
- All referenced example names match the actual file names in
bindings/rust/examples/- Each example can be executed with the provided
cargo runcommands- If the async example in line 110 requires specific feature flags, they are correctly specified
bindings/rust/build.rs (7)
1-23: LGTM! Previous issues resolved.The ccap_root detection logic correctly handles both packaged (crates.io) and repository modes. The previously flagged issues have been fixed:
is_packagedis now used on line 194- Safe navigation with
.and_then()and.expect()replaces the chained.unwrap()
25-81: LGTM!The build-from-source configuration is well-structured with appropriate platform-specific sources and compiler flags. The C++17 standard and file playback support are correctly enabled.
83-103: LGTM! MSVC AVX2 issue resolved.The AVX2 build now correctly detects MSVC and applies
/arch:AVX2instead of GCC-style flags. This resolves the previously flagged critical issue.
105-123: LGTM!Building the NEON file unconditionally to provide the
hasNEON()symbol across all platforms is a sound design. The comment clearly explains the behavior.
161-189: LGTM!Platform-specific linking configuration is appropriate for macOS (frameworks), Linux (stdc++), and Windows (COM and Media Foundation libraries).
191-207: LGTM! Rerun triggers now use ccap_root.The previously flagged issue with hardcoded
../../include/*paths has been resolved. The rerun triggers now correctly useccap_root.display()and are only added in repository mode (!is_packaged).
209-229: LGTM!The bindgen configuration is standard and appropriate. The allowlists correctly target the C API surface, and the derives are suitable for FFI types.
| // Note: On MSVC, we always link to the Release version (ccap.lib) | ||
| // to avoid CRT mismatch issues, since Rust uses the release CRT | ||
| // even in debug builds by default | ||
| println!("cargo:rustc-link-lib=static=ccap"); | ||
|
|
||
| println!("cargo:warning=Linking against pre-built ccap library (dev mode)..."); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🏁 Script executed:
rg -i -C 3 "msvc.*crt|crt.*mismatch" --type rustRepository: wysaid/CameraCapture
Length of output: 503
🏁 Script executed:
# Also search for MSVC-related discussions more broadly
rg -i "msvc" bindings/rust/build.rs -C 5Repository: wysaid/CameraCapture
Length of output: 1049
🌐 Web query:
Rust MSVC CRT linking behavior in debug builds - does Rust use release CRT by default
💡 Result:
Short answer: Yes — on MSVC targets Rust links the (dynamic) release CRT by default even for Debug builds; it does not automatically switch to the MSVC debug CRT (MDd/msvcrtd). [1][2]
Details / notes:
- The Rust toolchain exposes CRT linkage via the crt-static target feature (dynamic by default), but that controls static vs dynamic, not debug vs release. Rust’s default dynamic CRT corresponds to the normal/release MSVC runtime (msvcrt / vcruntime*.dll). [1][3]
- This has been a longstanding limitation/issue (see rust-lang/rust issue #39016). Workarounds include passing explicit linker args (/nodefaultlib:/defaultlib or -Clink-arg), using crates like static_vcruntime, or aligning the C/C++ side to use the release CRT. [2][3][4]
References:
[1] Rust Reference — Linkage (CRT / crt-static).
[2] rust-lang/rust issue #39016 (always links non-debug Windows runtime).
[3] static_vcruntime crate documentation (options for VCRuntime/UCRT).
[4] Blog post describing MSVC debug/Release CRT mismatch with Rust (example).
🌐 Web query:
How to link C++ library with different CRT versions in Rust on Windows MSVC
💡 Result:
Short answer: you must ensure the Rust binary and the C++ library use compatible CRT linkage (same MSVC CRT type/version) or isolate them (DLL boundary, C ABI), otherwise you get ODR/heap/pointer issues. Options:
-
Build the C++ library with the same CRT as Rust:
- Default Rust MSVC links dynamically (/MD). Compile the C++ lib with /MD (dynamic CRT) using the same toolset (same MSVC version). [1][2]
-
Make Rust use the static VC runtime (so it matches a /MT C++ lib) or statically link vcruntime:
- Use RUSTFLAGS -C target-feature=+crt-static to make Rust link the CRT statically (/MT). Note this also statically links UCRT unless you use the helper below. [1][6]
- Or use the static_vcruntime crate to statically link the VC runtime while still using the dynamic UCRT (common compromise). Add static_vcruntime in build-dependencies and call metabuild() in build.rs; optionally set the target rustflags in .cargo/config.toml. [2][3][7]
-
Isolate differing CRTs across process/DLL boundaries:
- Build the C++ code as a DLL and expose a plain C ABI (extern "C") boundary. Each DLL can use its own CRT safely if ownership of CRT-managed resources never crosses the ABI (no crossing of malloc/free, FILE*, STL objects, C++ exceptions, FILE handles, std::string, etc.). Prefer returning simple PODs and use explicit allocation/deallocation functions. [common Windows/MSVC practice; see 1]
-
Link-time tweaks (advanced / fragile):
- You can force or suppress default libraries with MSVC link flags (/DEFAULTLIB:, /NODEFAULTLIB:) via rustc link-args, but mixing CRTs manually is error-prone and not recommended for production. Use only if you understand the consequences. [2][7]
Practical checklist
- Pick a strategy (match CRT or DLL boundary).
- Build C++ with the matching MSVC toolset and flags (/MD vs /MT).
- If changing Rust: set RUSTFLAGS or .cargo/config.toml (-C target-feature=+crt-static) or add static_vcruntime. [1][2][3]
- Expose C ABI for cross-language calls; avoid sharing CRT-allocated resources. Test for memory/heap/CRT symbol issues.
References
- Rust reference on CRT/linkage and crt-static. [1]
- static_vcruntime crate docs (how to statically link vcruntime). [2][3]
- Cargo/gcc crate docs (msvc static_crt and build-time flags) and guidance about /MT vs /MD. [5]
If you want, tell me:
- whether your Rust toolchain is x86_64-pc-windows-msvc and currently using dynamic or static CRT, and
- how the C++ library is built (/MD or /MT, DLL or static lib),
and I’ll give exact Cargo config and build flags.
🏁 Script executed:
sed -n '120,165p' bindings/rust/build.rsRepository: wysaid/CameraCapture
Length of output: 1608
Fix MSVC CRT mismatch: search path order contradicts comment and creates runtime risk.
The comment correctly states that Rust uses the release CRT by default on MSVC even in debug builds, but the code contradicts this. The search paths are added as build/{build_type} first, then Debug, then Release. When PROFILE=debug, the linker will find and link the Debug version of ccap first if it exists.
On MSVC, linking a Debug ccap library (built with /MDd) against a Rust binary using the release CRT (/MD) will cause heap and memory management errors at runtime.
To match the comment's intent, either reorder the search paths to always prioritize Release on MSVC targets, or update the comment to reflect the actual behavior.
🤖 Prompt for AI Agents
bindings/rust/build.rs around lines 153 to 158: the comment says MSVC should
always link the Release CRT but the search-path order currently allows a Debug
ccap to be found first; change the logic so that for MSVC targets the Release
build directory is added to cargo:rustc-link-search before any
Debug/build/{build_type} paths (i.e., prioritize build/Release before
build/Debug or build/{PROFILE}), so the linker always resolves to the Release
ccap on MSVC, and keep the comment consistent with this behavior.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 8
🤖 Fix all issues with AI agents
In @bindings/rust/Cargo.toml:
- Line 28: Update the bindgen dependency version in Cargo.toml from "0.68" to
"0.72.1" (or a newer stable release); locate the bindgen entry in Cargo.toml and
change its version string to "0.72.1", then run cargo update/build to ensure
compatibility and resolve any new warnings or API changes.
In @bindings/rust/RUST_INTEGRATION_SUMMARY.md:
- Around line 151-152: The docs and manifest disagree on the minimum Rust
version: RUST_INTEGRATION_SUMMARY.md lists "Rust 1.70+" while Cargo.toml sets
rust-version = "1.65"; choose one and make them consistent by either updating
Cargo.toml's rust-version field to "1.70" (if you want to require 1.70) or
changing the "### Build Requirements" line in RUST_INTEGRATION_SUMMARY.md to
"Rust 1.65+" (if you want to support 1.65); ensure both the rust-version key in
Cargo.toml and the Build Requirements header text match the chosen minimum
version.
- Around line 96-122: The example uses non-existent/incorrect APIs; update it to
use Provider::new() to create the provider, call Provider::find_device_names()
or Provider::get_devices() to enumerate devices, call
Provider::with_device(device_index: i32) with an integer index (not a device
ref), call provider.set_resolution(width: u32, height: u32) with two separate
parameters (not a Resolution struct), and handle provider.grab_frame(timeout_ms)
which returns Result<Option<VideoFrame>> by matching like if let Some(frame) =
provider.grab_frame(1000)? { ... } (also adjust any start/open calls to the
correct methods used in the README).
In @scripts/publish_rust_crate.sh:
- Around line 652-657: The script currently checks for a non-existent standalone
command `cargo-clippy` using require_cmd, causing clippy detection to fail;
update the check in the block that uses require_cmd so it verifies clippy by
running `cargo clippy --version` (or otherwise invoking `cargo clippy` with
--version) instead of `cargo-clippy`, so the subsequent `cargo clippy
--all-targets -- -D warnings` branch runs when clippy is actually available.
- Around line 239-246: The install_rustup function currently pipes curl to sh
which is insecure; change it to first detect existing Rust (check rustc or
cargo) and skip if present, otherwise download the rustup install script to a
temporary file (using curl -f -o) and verify it (e.g., checksum or signature) or
at minimum prompt/require explicit consent before executing; then source
"${HOME}/.cargo/env" after a successful install; update any failure paths
(require_cmd, die) to report the temporary file path and verification result so
the install flow for install_rustup is safe and auditable.
- Around line 318-324: The problem is that cratesio_has_exact_crate() passes a
regex-anchored string ("^${crate}$") to cargo search, but cargo search does
simple substring matching and treats ^/$ as literal, so it returns wrong
results; fix by querying cargo search with the plain crate name (e.g.,
"${crate}" or "${crate}" with --limit) and keep the existing grep -E "^${crate}
= \"" to perform the exact-match check; update the cargo search invocation
inside cratesio_has_exact_crate to remove the ^ and $ anchors so the downstream
grep can correctly detect exact crate names.
- Around line 326-332: cratesio_latest_version is vulnerable to unescaped regex
interpolation of ${crate}; change the cargo search call to use the literal crate
string (cargo search "$crate" 2>/dev/null || true) and avoid embedding ${crate}
into the sed regex by instead finding the exact line with a fixed literal match
and then extracting the version, e.g. pipe out into grep -F "${crate} =" | head
-n1 and then extract the quoted version with a safe sed/cut (e.g. sed -E
's/^[^"]*"([^"]+)".*$/\1/'); update the function cratesio_latest_version
accordingly so no shell regex/meta-characters from crate can alter matching.
🧹 Nitpick comments (4)
bindings/rust/Cargo.toml (1)
23-23: Consider specifying only required tokio features instead of"full".The
"full"feature enables all tokio components including net, fs, process, signal, etc. For camera capture bindings, you likely only need a subset likert,rt-multi-thread,sync, andmacros. This reduces compile times and binary size for users enabling the async feature.Suggested minimal tokio features
-tokio = { version = "1.0", features = ["full"], optional = true } +tokio = { version = "1.0", features = ["rt", "rt-multi-thread", "sync", "macros"], optional = true }bindings/rust/build.rs (2)
194-197: Empty cfg block could be more explicit.The empty
#[cfg(target_arch = "aarch64")]block is technically correct since NEON is always available on aarch64, but it may confuse readers. Consider adding a flag or a more explicit comment.Suggested improvement
// Only add NEON flags on aarch64 #[cfg(target_arch = "aarch64")] { - // NEON is always available on aarch64, no special flags needed + // NEON is always available on aarch64, no special compiler flags needed. + // The C++ source handles ENABLE_NEON_IMP based on architecture detection. }
287-290: For bindgen 0.70+ compatibility, migrateCargoCallbacksto the new API.The project currently uses bindgen 0.68 where
Box::new(bindgen::CargoCallbacks)works as-is. However, when upgrading to bindgen 0.70+, update toBox::new(bindgen::CargoCallbacks::new()), which enables rerun-on-input-header-files by default. If the old behavior is needed, call.rerun_on_header_files(false)afternew().bindings/rust/README.md (1)
96-111: Example commands referencecapture_callbackwith async but async example is different.Line 110 suggests running
capture_callbackwith async feature, but the async usage example on lines 68-90 shows a different pattern usingAsyncProvider. Consider clarifying which example demonstrates async functionality.
📜 Review details
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (1)
bindings/rust/Cargo.lockis excluded by!**/*.lock
📒 Files selected for processing (7)
bindings/rust/Cargo.tomlbindings/rust/README.mdbindings/rust/RUST_INTEGRATION_SUMMARY.mdbindings/rust/build.rsbindings/rust/build_and_test.shscripts/publish_rust_crate.shscripts/update_version.sh
🚧 Files skipped from review as they are similar to previous changes (1)
- bindings/rust/build_and_test.sh
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-12-31T04:11:08.028Z
Learnt from: CR
Repo: wysaid/CameraCapture PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-12-31T04:11:08.028Z
Learning: Run `./scripts/update_version.sh <version>` to update version files
Applied to files:
scripts/update_version.sh
🧬 Code graph analysis (1)
scripts/publish_rust_crate.sh (1)
bindings/rust/src/error.rs (1)
fmt(65-86)
🪛 LanguageTool
bindings/rust/README.md
[style] ~187-~187: Consider using a less common alternative to make your writing sound more unique and professional.
Context: ...ontributing Contributions are welcome! Please feel free to submit a Pull Request. ## License Thi...
(FEEL_FREE_TO_STYLE_ME)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (20)
- GitHub Check: Build (Release-gcc-static)
- GitHub Check: Build (Release-gcc-shared)
- GitHub Check: Build (Debug-gcc-static)
- GitHub Check: Windows Build MinGW (Release-static)
- GitHub Check: Windows Build VS2026 (Debug-shared)
- GitHub Check: Windows Build VS2022 (Debug-shared)
- GitHub Check: Build Fedora (Debug-shared)
- GitHub Check: Build (Release-clang-static)
- GitHub Check: Build ARM64 (Release-gcc-shared)
- GitHub Check: Windows Build MinGW (Release-shared)
- GitHub Check: Build Fedora (Release-shared)
- GitHub Check: Windows Build MinGW (Debug-shared)
- GitHub Check: Build ARM64 (Release-gcc-static)
- GitHub Check: Build Fedora (Release-static)
- GitHub Check: Windows Build MinGW (Debug-static)
- GitHub Check: macOS Release - No File Playback
- GitHub Check: Windows Build VS2022 (Release-static)
- GitHub Check: Windows VS2022 Release - No File Playback
- GitHub Check: macOS Build (Debug-static)
- GitHub Check: Static Link (Dev) (windows-latest)
🔇 Additional comments (3)
scripts/update_version.sh (1)
106-117: Loop exits after first pattern match, potentially leaving one package entry outdated.The
breakon line 117 causes the loop to exit after the first successful substitution. If bothccap-rsandccapentries exist in the lockfile (e.g., during a transition period), only one will be updated. If both should be updated, remove thebreak:for pattern in patterns: new_text, n = re.subn(pattern, rf"\1{new_ver}\2", new_text, count=1) if n: count += n - breakbindings/rust/build.rs (1)
1-6: LGTM - Well-structured build script with robust source discovery.The build script handles both vendored/packaged and development modes cleanly. The hierarchical source discovery (native/ → parent dirs → CCAP_SOURCE_DIR) aligns well with the publish workflow in
publish_rust_crate.sh.bindings/rust/README.md (1)
1-7: LGTM - Comprehensive and well-structured documentation.The README provides clear installation instructions, usage examples for both sync and async patterns, API documentation, and platform support information. The badges and structure follow crates.io conventions well.
| futures = { version = "0.3", optional = true } | ||
|
|
||
| [build-dependencies] | ||
| bindgen = "0.68" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🌐 Web query:
latest version bindgen crate crates.io 2025 2026
💡 Result:
The latest bindgen crate version on crates.io as of January 11, 2026 is 0.72.1. (docs.rs)
Citations:
Update bindgen to the latest stable version.
bindgen = "0.68" is outdated. The latest version is 0.72.1, which includes improved Rust/Clang compatibility and bug fixes. Update to bindgen = "0.72.1" or later.
🤖 Prompt for AI Agents
In @bindings/rust/Cargo.toml at line 28, Update the bindgen dependency version
in Cargo.toml from "0.68" to "0.72.1" (or a newer stable release); locate the
bindgen entry in Cargo.toml and change its version string to "0.72.1", then run
cargo update/build to ensure compatibility and resolve any new warnings or API
changes.
| ### Build Requirements | ||
| - Rust 1.70+ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Inconsistent Rust version requirement.
This document states "Rust 1.70+" but Cargo.toml specifies rust-version = "1.65". Please align the documentation with the actual minimum supported version.
Suggested fix
### Build Requirements
-- Rust 1.70+
+- Rust 1.65+
- CMake 3.14+📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| ### Build Requirements | |
| - Rust 1.70+ | |
| ### Build Requirements | |
| - Rust 1.65+ | |
| - CMake 3.14+ |
🤖 Prompt for AI Agents
In @bindings/rust/RUST_INTEGRATION_SUMMARY.md around lines 151 - 152, The docs
and manifest disagree on the minimum Rust version: RUST_INTEGRATION_SUMMARY.md
lists "Rust 1.70+" while Cargo.toml sets rust-version = "1.65"; choose one and
make them consistent by either updating Cargo.toml's rust-version field to
"1.70" (if you want to require 1.70) or changing the "### Build Requirements"
line in RUST_INTEGRATION_SUMMARY.md to "Rust 1.65+" (if you want to support
1.65); ensure both the rust-version key in Cargo.toml and the Build Requirements
header text match the chosen minimum version.
| install_rustup() { | ||
| require_cmd curl || die "curl not found; please install curl first to auto-install Rust toolchain" | ||
|
|
||
| log "Installing Rust toolchain via rustup..." | ||
| curl https://sh.rustup.rs -sSf | sh -s -- -y | ||
| # shellcheck disable=SC1090 | ||
| source "${HOME}/.cargo/env" | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Piping curl to shell is a security risk.
The curl ... | sh pattern downloads and executes arbitrary code. While this is the official rustup installation method, it's risky in automated scripts. Consider adding a verification step or providing an alternative for environments where Rust should already be installed.
Suggested safer approach
install_rustup() {
require_cmd curl || die "curl not found; please install curl first to auto-install Rust toolchain"
+ warn "This will download and execute the rustup installer from https://sh.rustup.rs"
+ warn "For security-conscious environments, install Rust manually: https://www.rust-lang.org/tools/install"
+ if ! prompt_yn "Proceed with automatic installation?" "Y"; then
+ die "Please install Rust manually and re-run this script."
+ fi
+
log "Installing Rust toolchain via rustup..."
curl https://sh.rustup.rs -sSf | sh -s -- -y📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| install_rustup() { | |
| require_cmd curl || die "curl not found; please install curl first to auto-install Rust toolchain" | |
| log "Installing Rust toolchain via rustup..." | |
| curl https://sh.rustup.rs -sSf | sh -s -- -y | |
| # shellcheck disable=SC1090 | |
| source "${HOME}/.cargo/env" | |
| } | |
| install_rustup() { | |
| require_cmd curl || die "curl not found; please install curl first to auto-install Rust toolchain" | |
| warn "This will download and execute the rustup installer from https://sh.rustup.rs" | |
| warn "For security-conscious environments, install Rust manually: https://www.rust-lang.org/tools/install" | |
| if ! prompt_yn "Proceed with automatic installation?" "Y"; then | |
| die "Please install Rust manually and re-run this script." | |
| fi | |
| log "Installing Rust toolchain via rustup..." | |
| curl https://sh.rustup.rs -sSf | sh -s -- -y | |
| # shellcheck disable=SC1090 | |
| source "${HOME}/.cargo/env" | |
| } |
🤖 Prompt for AI Agents
In @scripts/publish_rust_crate.sh around lines 239 - 246, The install_rustup
function currently pipes curl to sh which is insecure; change it to first detect
existing Rust (check rustc or cargo) and skip if present, otherwise download the
rustup install script to a temporary file (using curl -f -o) and verify it
(e.g., checksum or signature) or at minimum prompt/require explicit consent
before executing; then source "${HOME}/.cargo/env" after a successful install;
update any failure paths (require_cmd, die) to report the temporary file path
and verification result so the install flow for install_rustup is safe and
auditable.
| cratesio_has_exact_crate() { | ||
| local crate="$1" | ||
| # cargo search output begins with: <name> = "<ver>" ... | ||
| local out | ||
| out="$(cargo search "^${crate}$" --limit 10 2>/dev/null || true)" | ||
| echo "${out}" | grep -E "^${crate} = \"" >/dev/null 2>&1 | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
cargo search does not support regex anchors.
The ^${crate}$ pattern uses regex anchors, but cargo search performs a simple substring match and doesn't interpret regex. The ^ and $ are treated as literal characters, causing the search to likely return no results.
Suggested fix
cratesio_has_exact_crate() {
local crate="$1"
# cargo search output begins with: <name> = "<ver>" ...
local out
- out="$(cargo search "^${crate}$" --limit 10 2>/dev/null || true)"
- echo "${out}" | grep -E "^${crate} = \"" >/dev/null 2>&1
+ out="$(cargo search "${crate}" --limit 20 2>/dev/null || true)"
+ # Match exact crate name at line start
+ echo "${out}" | grep -E "^${crate} = \"" >/dev/null 2>&1
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| cratesio_has_exact_crate() { | |
| local crate="$1" | |
| # cargo search output begins with: <name> = "<ver>" ... | |
| local out | |
| out="$(cargo search "^${crate}$" --limit 10 2>/dev/null || true)" | |
| echo "${out}" | grep -E "^${crate} = \"" >/dev/null 2>&1 | |
| } | |
| cratesio_has_exact_crate() { | |
| local crate="$1" | |
| # cargo search output begins with: <name> = "<ver>" ... | |
| local out | |
| out="$(cargo search "${crate}" --limit 20 2>/dev/null || true)" | |
| # Match exact crate name at line start | |
| echo "${out}" | grep -E "^${crate} = \"" >/dev/null 2>&1 | |
| } |
🤖 Prompt for AI Agents
In @scripts/publish_rust_crate.sh around lines 318 - 324, The problem is that
cratesio_has_exact_crate() passes a regex-anchored string ("^${crate}$") to
cargo search, but cargo search does simple substring matching and treats ^/$ as
literal, so it returns wrong results; fix by querying cargo search with the
plain crate name (e.g., "${crate}" or "${crate}" with --limit) and keep the
existing grep -E "^${crate} = \"" to perform the exact-match check; update the
cargo search invocation inside cratesio_has_exact_crate to remove the ^ and $
anchors so the downstream grep can correctly detect exact crate names.
| cratesio_latest_version() { | ||
| local crate="$1" | ||
| local out | ||
| out="$(cargo search "^${crate}$" --limit 10 2>/dev/null || true)" | ||
| # Extract the first exact match version if present | ||
| echo "${out}" | sed -n -E "s/^${crate} = \"([^\"]+)\".*$/\1/p" | head -n 1 | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same regex issue in cratesio_latest_version.
The same ^${crate}$ issue exists here. Apply the same fix as suggested for cratesio_has_exact_crate.
Suggested fix
cratesio_latest_version() {
local crate="$1"
local out
- out="$(cargo search "^${crate}$" --limit 10 2>/dev/null || true)"
+ out="$(cargo search "${crate}" --limit 20 2>/dev/null || true)"
# Extract the first exact match version if present
echo "${out}" | sed -n -E "s/^${crate} = \"([^\"]+)\".*$/\1/p" | head -n 1
}🤖 Prompt for AI Agents
In @scripts/publish_rust_crate.sh around lines 326 - 332,
cratesio_latest_version is vulnerable to unescaped regex interpolation of
${crate}; change the cargo search call to use the literal crate string (cargo
search "$crate" 2>/dev/null || true) and avoid embedding ${crate} into the sed
regex by instead finding the exact line with a fixed literal match and then
extracting the version, e.g. pipe out into grep -F "${crate} =" | head -n1 and
then extract the quoted version with a safe sed/cut (e.g. sed -E
's/^[^"]*"([^"]+)".*$/\1/'); update the function cratesio_latest_version
accordingly so no shell regex/meta-characters from crate can alter matching.
| if require_cmd cargo-clippy; then | ||
| # Clippy on the crate's default feature set. | ||
| cargo clippy --all-targets -- -D warnings | ||
| else | ||
| warn "clippy not available; skipping clippy." | ||
| fi |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Incorrect clippy detection - cargo-clippy is not a standalone command.
Clippy is invoked as cargo clippy, not as cargo-clippy. The current check will likely always fail even when clippy is installed. Use cargo clippy --version to check availability.
Suggested fix
- if require_cmd cargo-clippy; then
+ if cargo clippy --version >/dev/null 2>&1; then
# Clippy on the crate's default feature set.
cargo clippy --all-targets -- -D warnings
else
warn "clippy not available; skipping clippy."
fi📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| if require_cmd cargo-clippy; then | |
| # Clippy on the crate's default feature set. | |
| cargo clippy --all-targets -- -D warnings | |
| else | |
| warn "clippy not available; skipping clippy." | |
| fi | |
| if cargo clippy --version >/dev/null 2>&1; then | |
| # Clippy on the crate's default feature set. | |
| cargo clippy --all-targets -- -D warnings | |
| else | |
| warn "clippy not available; skipping clippy." | |
| fi |
🤖 Prompt for AI Agents
In @scripts/publish_rust_crate.sh around lines 652 - 657, The script currently
checks for a non-existent standalone command `cargo-clippy` using require_cmd,
causing clippy detection to fail; update the check in the block that uses
require_cmd so it verifies clippy by running `cargo clippy --version` (or
otherwise invoking `cargo clippy` with --version) instead of `cargo-clippy`, so
the subsequent `cargo clippy --all-targets -- -D warnings` branch runs when
clippy is actually available.
| # 5. Update Rust crate version (Cargo.toml) | ||
| update_file "s/^version = \".*\"$/version = \"$NEW_VERSION\"/" "$PROJECT_ROOT/bindings/rust/Cargo.toml" "Rust crate version (Cargo.toml)" | ||
|
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sed pattern may match unintended version = lines in Cargo.toml.
The pattern ^version = ".*"$ will match any line starting with version =, which could include dependency version specifications if they happen to be on their own line. Consider using a more targeted approach similar to the Python script used for Cargo.lock, or ensure the pattern only matches within the [package] section.
Suggested fix using Python for consistency
-# 5. Update Rust crate version (Cargo.toml)
-update_file "s/^version = \".*\"$/version = \"$NEW_VERSION\"/" "$PROJECT_ROOT/bindings/rust/Cargo.toml" "Rust crate version (Cargo.toml)"
+# 5. Update Rust crate version (Cargo.toml)
+RUST_CARGO_TOML="$PROJECT_ROOT/bindings/rust/Cargo.toml"
+if [ -f "$RUST_CARGO_TOML" ]; then
+ python3 - "$RUST_CARGO_TOML" "$NEW_VERSION" <<'PY'
+import re, sys, pathlib
+
+path = pathlib.Path(sys.argv[1])
+new_ver = sys.argv[2]
+text = path.read_text()
+
+# Match version within [package] section only
+m = re.search(r'(?ms)^\[package\]\s*(.*?)(?=^\[|\Z)', text)
+if not m:
+ raise SystemExit("[package] section not found")
+
+pkg = m.group(0)
+new_pkg, n = re.subn(r'(?m)^version\s*=\s*"[^"]+"', f'version = "{new_ver}"', pkg, count=1)
+if n != 1:
+ raise SystemExit("Failed to update version in [package]")
+
+path.write_text(text[:m.start()] + new_pkg + text[m.end():])
+print(f"✅ Updated Rust crate version to {new_ver}")
+PY
+else
+ echo "⚠️ $RUST_CARGO_TOML not found, skipping Rust crate version update"
+fiThere was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 10
🤖 Fix all issues with AI agents
In @bindings/rust/README.md:
- Line 24: The README's ccap dependency line uses version "1.5.0" which
mismatches the actual crate version in Cargo.toml
("1.5.0-test.20260111101345.e10615d"); update the README entries for ccap /
ccap-rs to use the exact version string from Cargo.toml (or alternatively update
Cargo.toml to the intended stable version) so both the README and Cargo.toml
specify the same version for ccap-rs.
In @bindings/rust/src/async.rs:
- Around line 30-39: The new() function on AsyncProvider is marked async but
performs no awaitable work (it constructs a SyncProvider, creates an mpsc
channel, wraps things in Arc/Mutex, and returns), so remove the async keyword
and make it a synchronous constructor returning Result<Self>; update its
signature from pub async fn new() -> Result<Self> to pub fn new() ->
Result<Self> and leave the body unchanged (ensure types still match
Result<Self>, and keep construction of provider: Arc::new(Mutex::new(provider)),
frame_receiver: Some(rx), and _frame_sender: tx).
- Around line 78-88: The grab_frame_timeout function casts timeout.as_millis()
to u32 which can overflow for durations > u32::MAX; add an explicit check before
casting in grab_frame_timeout (or clamp) and return a meaningful error (e.g.,
crate::CcapError::InvalidArgument or a new TimeoutOverflow variant) if the
duration exceeds u32::MAX, otherwise safely cast and proceed; reference the
grab_frame_timeout function, the timeout.as_millis() -> u32 conversion, and the
call to provider.grab_frame(timeout_ms) when implementing the fix.
- Around line 110-117: The method frame_stream currently calls
self.frame_receiver.take().expect(...), which will panic if called a second
time; change frame_stream to return a Result instead of panicking: update the
signature from pub fn frame_stream(&mut self) -> impl futures::Stream<Item =
VideoFrame> to return Result<impl Stream<Item = VideoFrame>, YourErrorType> (or
Box<dyn Stream<...>>/Pin<Box<...>> if needed), check self.frame_receiver.take()
and return an Err with a clear descriptive error if it is None, otherwise
construct and return the UnboundedReceiverStream from the receiver in Ok(...);
include a small, descriptive error type/message referencing
frame_stream/frame_receiver so callers can handle the duplicate-call case
gracefully.
In @bindings/rust/src/convert.rs:
- Around line 69-71: The calculation of dst_stride and dst_size can overflow for
extreme width/height values; replace the direct multiplies with checked
arithmetic (e.g., use checked_mul on width and height or cast to a wider integer
before multiplying) and bail out with an error or panic if either checked_mul
returns None; then only allocate dst_data after confirming dst_size fits in
usize. Update the logic around dst_stride, dst_size, and the allocation of
dst_data to use the checked results and return/propagate an error instead of
unconditionally creating the vector.
In @bindings/rust/src/error.rs:
- Line 96: Match arms for CcapErrorCode_CCAP_ERROR_INVALID_DEVICE and the
InvalidParameter variant create CcapError::InvalidDevice("".to_string()) / empty
strings which lose context; change those arms to include useful context by
formatting the original error code or message into the string (e.g.,
format!("invalid device: {:?}", err_code) or include the numeric/enum name) so
CcapError::InvalidDevice(...) and CcapError::InvalidParameter(...) carry the
code/message instead of an empty string.
- Around line 8-10: Remove the semantically confusing CcapError::None variant
and update the conversion logic so a CCAP_ERROR_NONE code is not mapped to an
Err variant; instead delete the None enum variant, change the From<i32> impl to
either panic with a clear message instructing callers to check success before
converting or replace it with a TryFrom<i32> implementation that returns
Result<CcapError, i32>/Result<(), CcapError> as appropriate, and update any call
sites that rely on From<i32> (or Err(CcapError::None)) to handle the OK case as
Ok(()) rather than an error.
In @bindings/rust/src/provider.rs:
- Around line 285-290: set_resolution currently calls set_property twice which
can leave the device half-updated if the second call fails; update
set_resolution to either validate both width and height before mutating (e.g.,
call a validation function or a non-mutating check using the same logic as
set_property) or perform the first set_property call and, if the second fails,
call set_property(PropertyName::Width, original_width) to restore the prior
value; reference the symbols set_resolution, set_property and
PropertyName::Width/PropertyName::Height and ensure any rollback handles errors
(propagating a combined error or the original failure) and returns a Result
reflecting the final state.
- Around line 211-227: open_device currently destroys an existing provider
handle without first releasing any registered frame callback, which can leak the
callback memory referenced by self.callback_ptr; before calling
sys::ccap_provider_destroy(self.handle) in open_device, call
self.cleanup_callback() (or the equivalent cleanup method) to unregister and
free the callback and clear self.callback_ptr, then proceed to destroy the
handle and recreate it, ensuring self.callback_ptr is nulled/updated after
cleanup to avoid dangling pointers.
🧹 Nitpick comments (8)
bindings/rust/README.md (2)
190-190: Minor: Consider rephrasing for conciseness.The phrase "Please feel free to submit a Pull Request" is common but could be more direct. Consider: "Contributions via Pull Requests are welcome!"
120-130: Build script and CMake paths verified; consider documenting the optional Rust integration flag.The script
scripts/build_and_install.shexists and is executable. The cmake commands are correct for building the C library frombuild/Debug, with../..properly resolving to the project root. However, the instructions should clarify that users can also integrate Rust bindings into the CMake build by adding-DCCAP_BUILD_RUST=ONto the cmake command, rather than always requiring a separatecargo buildstep. The current approach (separate C and Rust builds) is valid but this alternative integrated method is available.bindings/rust/src/async.rs (1)
19-25: Consider removing incomplete streaming infrastructure.The
frame_receiverand_frame_senderare allocated innew()but never used sinceframe_stream()is deprecated and incomplete. This creates unnecessary overhead (channel allocation) for everyAsyncProviderinstance.Consider either:
- Remove the channel fields and
frame_stream()entirely until the feature is implemented- Make the fields
Option<T>and only allocate whenframe_stream()is called♻️ Option 1: Remove unused fields
pub struct AsyncProvider { provider: Arc<Mutex<SyncProvider>>, - frame_receiver: Option<mpsc::UnboundedReceiver<VideoFrame>>, - /// Kept for future frame streaming implementation - #[allow(dead_code)] - _frame_sender: mpsc::UnboundedSender<VideoFrame>, }bindings/rust/src/convert.rs (1)
93-119: Consider extracting common conversion logic.The YUYV to RGB24/BGR24 functions share identical buffer validation, allocation, and structure. Consider extracting the common pattern to reduce duplication and improve maintainability.
This applies to most conversion functions in this module. A helper function could handle validation, allocation, and FFI call pattern.
bindings/rust/src/frame.rs (1)
153-179: Consider documenting FFI call overhead in convenience methods.Each convenience method calls
info(), which performs an FFI call. Users calling multiple properties (e.g.,width(),height(),pixel_format()) will make redundant FFI calls. Consider documenting that callinginfo()once and accessing fields directly is more efficient for accessing multiple properties.bindings/rust/src/provider.rs (3)
66-78: Consider more specific error for creation failure.
DeviceOpenFailedis returned whenccap_provider_create()fails, but creation failure is distinct from opening a device. Consider usingInternalErroror adding aProviderCreationFailederror variant for clarity.
115-153: Performance consideration for device enumeration.
get_devices()creates a newProviderinstance for each discovered device to retrieve detailed information (lines 138-148). This could be slow if device initialization is expensive. Consider documenting this cost or checking if the C API provides a more efficient batch query method.
155-193: Code duplication withDeviceInfo::from_c_struct.This method manually converts
CcapDeviceInfotoDeviceInfo, duplicating logic fromDeviceInfo::from_c_struct()in frame.rs. Consider using the existing conversion method to reduce duplication.♻️ Suggested refactor
fn get_device_info_direct(&self) -> Result<DeviceInfo> { let mut device_info = sys::CcapDeviceInfo::default(); let success = unsafe { sys::ccap_provider_get_device_info(self.handle, &mut device_info) }; if !success { return Err(CcapError::DeviceOpenFailed); } - let name = unsafe { - let cstr = CStr::from_ptr(device_info.deviceName.as_ptr()); - cstr.to_string_lossy().to_string() - }; - - let mut formats = Vec::new(); - for i in 0..device_info.pixelFormatCount { - if i < device_info.supportedPixelFormats.len() { - formats.push(PixelFormat::from(device_info.supportedPixelFormats[i])); - } - } - - let mut resolutions = Vec::new(); - for i in 0..device_info.resolutionCount { - if i < device_info.supportedResolutions.len() { - let res = &device_info.supportedResolutions[i]; - resolutions.push(Resolution { - width: res.width, - height: res.height, - }); - } - } - - Ok(DeviceInfo { - name, - supported_pixel_formats: formats, - supported_resolutions: resolutions, - }) + DeviceInfo::from_c_struct(&device_info) }
📜 Review details
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (6)
bindings/rust/README.mdbindings/rust/src/async.rsbindings/rust/src/convert.rsbindings/rust/src/error.rsbindings/rust/src/frame.rsbindings/rust/src/provider.rs
🧰 Additional context used
🧬 Code graph analysis (3)
bindings/rust/src/convert.rs (1)
bindings/rust/src/types.rs (2)
from_c_enum(62-64)from_c_enum(197-207)
bindings/rust/src/provider.rs (4)
include/ccap_def.h (1)
DeviceInfo(425-442)bindings/rust/src/error.rs (1)
from(85-111)bindings/rust/src/types.rs (5)
from(35-52)from(87-103)from(116-126)from(154-167)from(220-225)bindings/rust/src/frame.rs (7)
width(154-156)height(159-161)from_c_ptr(52-57)info(87-138)pixel_format(164-168)from_raw(75-84)from_c_ptr_ref(60-65)
bindings/rust/src/frame.rs (4)
include/ccap_def.h (1)
DeviceInfo(425-442)bindings/rust/src/types.rs (7)
from_c_enum(62-64)from_c_enum(197-207)from(35-52)from(87-103)from(116-126)from(154-167)from(220-225)bindings/rust/src/error.rs (1)
from(85-111)bindings/rust/src/provider.rs (1)
pixel_format(369-372)
🪛 LanguageTool
bindings/rust/README.md
[style] ~190-~190: Consider using a less common alternative to make your writing sound more unique and professional.
Context: ...ontributing Contributions are welcome! Please feel free to submit a Pull Request. ## License Thi...
(FEEL_FREE_TO_STYLE_ME)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (20)
- GitHub Check: Check Environment
- GitHub Check: Build (Release-clang-static)
- GitHub Check: Build (Release-gcc-shared)
- GitHub Check: Build (Debug-gcc-static)
- GitHub Check: Build (Release-gcc-static)
- GitHub Check: Windows Build MinGW (Debug-shared)
- GitHub Check: Windows Build VS2026 (Debug-shared)
- GitHub Check: Windows Build MinGW (Release-static)
- GitHub Check: Build Fedora (Release-shared)
- GitHub Check: Windows Build VS2022 (Release-shared)
- GitHub Check: Windows Build VS2026 (Release-static)
- GitHub Check: Windows Build MinGW (Debug-static)
- GitHub Check: Windows Build MinGW (Release-shared)
- GitHub Check: Windows Build VS2022 (Debug-shared)
- GitHub Check: Windows VS2022 Release - No File Playback
- GitHub Check: Windows Build VS2022 (Release-static)
- GitHub Check: Static Link (Dev) (macos-latest)
- GitHub Check: Build Source (Dist) (windows-latest)
- GitHub Check: Static Link (Dev) (windows-latest)
- GitHub Check: Build Source (Dist) (macos-latest)
🔇 Additional comments (28)
bindings/rust/README.md (4)
185-185: The MSRV is already properly enforced inCargo.tomlwithrust-version = "1.65", matching the README documentation. No action required.
69-89: Async feature and AsyncProvider implementation verified.All points are confirmed:
- The
asyncfeature is defined inCargo.tomland properly links totokio,tokio-stream, andfuturesdependencies.AsyncProvideris correctly exported from theccap::r#asyncmodule with a feature gate.- All shown async methods exist with correct signatures:
new(),find_device_names(),open(), andgrab_frame().
163-171: All thread safety claims in the README are verified and accurate.The actual implementation confirms:
VideoFramehas explicitunsafe impl Sendandunsafe impl Syncwith clear SAFETY comments (explaining frames are independent and all public methods are read-only)Providerhas explicitunsafe impl Sendbut noSyncimplementation, with a comprehensive SAFETY comment that documents the C++ API is not thread-safe and external synchronization (e.g.,Mutex) is required for concurrent accessAsyncProvideris properly implemented and referenced in documentationThe README's thread safety guidance is well-supported by the implementation and its safety documentation. No changes needed.
32-65: Public API exports are correctly implemented and match the usage example.All symbols used in the code example (lines 32-65) are properly exported from the crate root:
Provider,Result, and their methodsfind_device_names(),open_device(),grab_frame(),info(), anddata()exist with matching signatures and return types. The example accurately reflects the actual API.bindings/rust/src/error.rs (1)
114-115: LGTM!Standard Result type alias following Rust conventions.
bindings/rust/src/async.rs (2)
42-75: LGTM!Device operation methods properly lock the provider mutex and delegate to the synchronous API. The async wrappers provide appropriate concurrent access control.
125-129: Test will need update whennew()becomes synchronous.This test currently awaits
AsyncProvider::new(), but as noted in a previous comment,new()should not be async. Update this test when that change is made.bindings/rust/src/convert.rs (4)
9-20: LGTM!Good defensive validation with clear error messages. This prevents buffer overruns when calling unsafe FFI functions.
23-53: LGTM!Backend management and capability detection methods are straightforward wrappers around the C API with appropriate error handling.
185-222: LGTM!NV12 to RGB24 conversion correctly handles the planar format with proper chroma plane size calculation using
(height + 1) / 2for odd heights.
268-308: LGTM!I420 to RGB24 conversion correctly handles the three-plane YUV format with proper validation and chroma plane sizing. The
too_many_argumentsclippy allow is justified given the nature of planar format conversions.bindings/rust/src/frame.rs (7)
15-43: LGTM!Excellent defensive programming with
min()to prevent array bounds violations when converting from C structures. String and enum conversions are properly error-handled.
45-84: LGTM!Proper ownership tracking distinguishes between owned frames (from grab operations) and borrowed frames (from callbacks). This prevents double-free and use-after-free issues.
140-151: LGTM!The
data()method correctly returns the entire frame buffer usingsizeInBytesfrom the C API, which accounts for all planes in the complete frame data.
181-189: LGTM!Drop implementation correctly releases the frame only when owned, preventing double-free errors for callback-borrowed frames.
202-209: Verify Sync requirements align with C++ implementation.
unsafe impl Syncallows&VideoFrameto be shared across threads. This requires that all operations through&selfare thread-safe at the C++ level. The WARNING correctly notes this is unverified.Same verification as Send - review the C++ implementation. If the C++
CcapVideoFrameuses any thread-local storage, caches, or non-atomic reference counting, this Sync implementation would be unsound.
211-232: LGTM!
VideoFrameInfocorrectly uses lifetime parameter to tie data plane slices to theVideoFrameborrow, preventing use-after-free. Public fields provide convenient access to frame metadata.
92-107: The plane size calculation is correct and safe for all supported pixel formats.The C API guarantees that
stride[1]andstride[2]are0for non-multi-plane formats (RGB, RGBA, YUYV, UYVY), and the Rust code properly handles this by checking stride values before calculating plane sizes. For YUV multi-plane formats (NV12, I420), the stride values are always set appropriately by the C library, and the calculation(height + 1) / 2correctly handles both even and odd heights.bindings/rust/src/provider.rs (10)
8-20: LGTM!The
SendSyncPtrwrapper with manualSend + Syncimplementation is correctly used to store callback pointers in static storage. The SAFETY documentation clearly explains why this is sound.
22-63: Excellent thread safety documentation!The documentation clearly explains the thread-safety limitations of the underlying C++ implementation and provides concrete examples of safe usage patterns. The
Send(but notSync) implementation with warnings is the correct choice.
302-334: LGTM with minor note.Frame grabbing and capture control methods properly manage state. Note that
stop_capture()doesn't check if the device is opened or capture is started, but this may be intentional for idempotent cleanup.
398-461: LGTM with consideration for poisoned mutex.The error callback management correctly prevents memory leaks by cleaning up the old callback before setting a new one. The double-boxing pattern ensures pointer stability.
Note: If
GLOBAL_ERROR_CALLBACK.lock()fails (poisoned mutex), the callback is silently not registered. This is probably acceptable since a poisoned mutex indicates a panic has already occurred.
463-491: LGTM - Correct cleanup pattern.This method correctly calls
cleanup_callback()before destroying the handle (line 473), preventing the memory leak that exists inopen_device(). This is the pattern that should be followed consistently.
511-564: LGTM!Frame callback management correctly uses
from_c_ptr_ref()for borrowed frames in the callback context, preventing double-free. Cleanup on failure and old callback cleanup prevent memory leaks.
566-578: LGTM!Callback removal correctly clears the C API callback before freeing the Rust-side memory.
580-592: LGTM!The cleanup helper correctly reconstructs the Box to drop the callback. The SAFETY comment and type alias consistency note are important for maintaining this unsafe code.
595-607: LGTM!Drop implementation follows the correct sequence: clean up callbacks first, then destroy the handle. This prevents memory leaks and use-after-free issues.
90-94: The concern about device open state is unfounded. The C API does automatically open the device during creation—bothccap_provider_create_with_deviceandccap_provider_create_with_indexinvoke the parameterizedProviderconstructors (insrc/ccap_core.cpp), which explicitly callopen(). Settingis_opened = truein the Rust bindings is correct and matches the actual behavior.
bindings/rust/src/async.rs
Outdated
| pub async fn new() -> Result<Self> { | ||
| let provider = SyncProvider::new()?; | ||
| let (tx, rx) = mpsc::unbounded_channel(); | ||
|
|
||
| Ok(AsyncProvider { | ||
| provider: Arc::new(Mutex::new(provider)), | ||
| frame_receiver: Some(rx), | ||
| _frame_sender: tx, | ||
| }) | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion | 🟠 Major
Remove unnecessary async from new().
The new() method performs no asynchronous operations. Marking it async adds unnecessary overhead and confusion. Make it a regular synchronous function.
♻️ Proposed fix
/// Create a new async provider
- pub async fn new() -> Result<Self> {
+ pub fn new() -> Result<Self> {
let provider = SyncProvider::new()?;
let (tx, rx) = mpsc::unbounded_channel();
Ok(AsyncProvider {
provider: Arc::new(Mutex::new(provider)),
frame_receiver: Some(rx),
_frame_sender: tx,
})
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| pub async fn new() -> Result<Self> { | |
| let provider = SyncProvider::new()?; | |
| let (tx, rx) = mpsc::unbounded_channel(); | |
| Ok(AsyncProvider { | |
| provider: Arc::new(Mutex::new(provider)), | |
| frame_receiver: Some(rx), | |
| _frame_sender: tx, | |
| }) | |
| } | |
| pub fn new() -> Result<Self> { | |
| let provider = SyncProvider::new()?; | |
| let (tx, rx) = mpsc::unbounded_channel(); | |
| Ok(AsyncProvider { | |
| provider: Arc::new(Mutex::new(provider)), | |
| frame_receiver: Some(rx), | |
| _frame_sender: tx, | |
| }) | |
| } |
🤖 Prompt for AI Agents
In @bindings/rust/src/async.rs around lines 30 - 39, The new() function on
AsyncProvider is marked async but performs no awaitable work (it constructs a
SyncProvider, creates an mpsc channel, wraps things in Arc/Mutex, and returns),
so remove the async keyword and make it a synchronous constructor returning
Result<Self>; update its signature from pub async fn new() -> Result<Self> to
pub fn new() -> Result<Self> and leave the body unchanged (ensure types still
match Result<Self>, and keep construction of provider:
Arc::new(Mutex::new(provider)), frame_receiver: Some(rx), and _frame_sender:
tx).
bindings/rust/src/async.rs
Outdated
| pub async fn grab_frame_timeout(&self, timeout: Duration) -> Result<Option<VideoFrame>> { | ||
| let provider = Arc::clone(&self.provider); | ||
| let timeout_ms = timeout.as_millis() as u32; | ||
|
|
||
| tokio::task::spawn_blocking(move || { | ||
| let mut provider = provider.blocking_lock(); | ||
| provider.grab_frame(timeout_ms) | ||
| }) | ||
| .await | ||
| .map_err(|e| crate::CcapError::InternalError(e.to_string()))? | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Potential timeout overflow with large durations.
Converting Duration to u32 milliseconds can overflow for durations longer than ~49.7 days. While unlikely in practice for frame grabbing, consider adding a check or documenting the limitation.
🔧 Suggested improvement
pub async fn grab_frame_timeout(&self, timeout: Duration) -> Result<Option<VideoFrame>> {
let provider = Arc::clone(&self.provider);
- let timeout_ms = timeout.as_millis() as u32;
+ let timeout_ms = timeout.as_millis().min(u32::MAX as u128) as u32;
tokio::task::spawn_blocking(move || {Or document the limitation in a doc comment.
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| pub async fn grab_frame_timeout(&self, timeout: Duration) -> Result<Option<VideoFrame>> { | |
| let provider = Arc::clone(&self.provider); | |
| let timeout_ms = timeout.as_millis() as u32; | |
| tokio::task::spawn_blocking(move || { | |
| let mut provider = provider.blocking_lock(); | |
| provider.grab_frame(timeout_ms) | |
| }) | |
| .await | |
| .map_err(|e| crate::CcapError::InternalError(e.to_string()))? | |
| } | |
| pub async fn grab_frame_timeout(&self, timeout: Duration) -> Result<Option<VideoFrame>> { | |
| let provider = Arc::clone(&self.provider); | |
| let timeout_ms = timeout.as_millis().min(u32::MAX as u128) as u32; | |
| tokio::task::spawn_blocking(move || { | |
| let mut provider = provider.blocking_lock(); | |
| provider.grab_frame(timeout_ms) | |
| }) | |
| .await | |
| .map_err(|e| crate::CcapError::InternalError(e.to_string()))? | |
| } |
🤖 Prompt for AI Agents
In @bindings/rust/src/async.rs around lines 78 - 88, The grab_frame_timeout
function casts timeout.as_millis() to u32 which can overflow for durations >
u32::MAX; add an explicit check before casting in grab_frame_timeout (or clamp)
and return a meaningful error (e.g., crate::CcapError::InvalidArgument or a new
TimeoutOverflow variant) if the duration exceeds u32::MAX, otherwise safely cast
and proceed; reference the grab_frame_timeout function, the timeout.as_millis()
-> u32 conversion, and the call to provider.grab_frame(timeout_ms) when
implementing the fix.
bindings/rust/src/async.rs
Outdated
| 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) | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Deprecated method panics on second call.
The expect() on line 114 will panic if frame_stream() is called twice. While the method is deprecated and incomplete, panicking is still a poor user experience. Consider returning a Result with a descriptive error instead.
🔧 Suggested improvement
- pub fn frame_stream(&mut self) -> impl futures::Stream<Item = VideoFrame> {
+ pub fn frame_stream(&mut self) -> Result<impl futures::Stream<Item = VideoFrame>> {
let receiver = self
.frame_receiver
.take()
- .expect("Frame stream can only be created once");
+ .ok_or_else(|| CcapError::InvalidParameter("Frame stream can only be created once".to_string()))?;
- tokio_stream::wrappers::UnboundedReceiverStream::new(receiver)
+ Ok(tokio_stream::wrappers::UnboundedReceiverStream::new(receiver))
}🤖 Prompt for AI Agents
In @bindings/rust/src/async.rs around lines 110 - 117, The method frame_stream
currently calls self.frame_receiver.take().expect(...), which will panic if
called a second time; change frame_stream to return a Result instead of
panicking: update the signature from pub fn frame_stream(&mut self) -> impl
futures::Stream<Item = VideoFrame> to return Result<impl Stream<Item =
VideoFrame>, YourErrorType> (or Box<dyn Stream<...>>/Pin<Box<...>> if needed),
check self.frame_receiver.take() and return an Err with a clear descriptive
error if it is None, otherwise construct and return the UnboundedReceiverStream
from the receiver in Ok(...); include a small, descriptive error type/message
referencing frame_stream/frame_receiver so callers can handle the duplicate-call
case gracefully.
| let dst_stride = (width * 3) as usize; | ||
| let dst_size = dst_stride * height as usize; | ||
| let mut dst_data = vec![0u8; dst_size]; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Potential arithmetic overflow in buffer size calculation.
(width * 3) as usize and dst_stride * height as usize could theoretically overflow for extremely large dimensions. While unlikely in practice (would require GB+ allocations), consider checking for overflow or documenting the assumptions.
For context: u32::MAX * 3 as usize is safe on 64-bit platforms, but the subsequent multiplication by height could overflow usize on 32-bit systems with large images.
🤖 Prompt for AI Agents
In @bindings/rust/src/convert.rs around lines 69 - 71, The calculation of
dst_stride and dst_size can overflow for extreme width/height values; replace
the direct multiplies with checked arithmetic (e.g., use checked_mul on width
and height or cast to a wider integer before multiplying) and bail out with an
error or panic if either checked_mul returns None; then only allocate dst_data
after confirming dst_size fits in usize. Update the logic around dst_stride,
dst_size, and the allocation of dst_data to use the checked results and
return/propagate an error instead of unconditionally creating the vector.
| /// No error occurred | ||
| #[error("No error")] | ||
| None, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Consider removing the None error variant.
The CcapError::None variant is semantically confusing in an error enum. An error type should only represent actual error conditions. Having Err(CcapError::None) in a Result contradicts the meaning of Err. If the C API returns a "no error" code, map it to Ok(()) instead of wrapping it in the error type.
♻️ Proposed refactor
Remove the None variant from CcapError and adjust the From<i32> implementation to handle CCAP_ERROR_NONE differently (e.g., panic with a message that the caller should check for success before converting to error, or use TryFrom instead).
/// Error types for ccap operations
#[derive(Debug, Error)]
pub enum CcapError {
- /// No error occurred
- #[error("No error")]
- None,
-
/// No camera device found
#[error("No camera device found")]
NoDeviceFound,🤖 Prompt for AI Agents
In @bindings/rust/src/error.rs around lines 8 - 10, Remove the semantically
confusing CcapError::None variant and update the conversion logic so a
CCAP_ERROR_NONE code is not mapped to an Err variant; instead delete the None
enum variant, change the From<i32> impl to either panic with a clear message
instructing callers to check success before converting or replace it with a
TryFrom<i32> implementation that returns Result<CcapError, i32>/Result<(),
CcapError> as appropriate, and update any call sites that rely on From<i32> (or
Err(CcapError::None)) to handle the OK case as Ok(()) rather than an error.
| match code_u { | ||
| CcapErrorCode_CCAP_ERROR_NONE => CcapError::None, | ||
| CcapErrorCode_CCAP_ERROR_NO_DEVICE_FOUND => CcapError::NoDeviceFound, | ||
| CcapErrorCode_CCAP_ERROR_INVALID_DEVICE => CcapError::InvalidDevice("".to_string()), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Empty context strings lose error information.
When mapping error codes to variants with context fields (InvalidDevice and InvalidParameter), empty strings are provided. This loses potentially useful debugging information. Consider including the error code in the context string.
🔧 Suggested improvement
- CcapErrorCode_CCAP_ERROR_INVALID_DEVICE => CcapError::InvalidDevice("".to_string()),
+ CcapErrorCode_CCAP_ERROR_INVALID_DEVICE => CcapError::InvalidDevice(format!("error code {}", code)), CcapErrorCode_CCAP_ERROR_PROPERTY_SET_FAILED => {
- CcapError::InvalidParameter("".to_string())
+ CcapError::InvalidParameter(format!("property set failed (code {})", code))
}Also applies to: 105-106
🤖 Prompt for AI Agents
In @bindings/rust/src/error.rs at line 96, Match arms for
CcapErrorCode_CCAP_ERROR_INVALID_DEVICE and the InvalidParameter variant create
CcapError::InvalidDevice("".to_string()) / empty strings which lose context;
change those arms to include useful context by formatting the original error
code or message into the string (e.g., format!("invalid device: {:?}", err_code)
or include the numeric/enum name) so CcapError::InvalidDevice(...) and
CcapError::InvalidParameter(...) carry the code/message instead of an empty
string.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 0
🧹 Nitpick comments (2)
scripts/publish_rust_crate.sh (2)
112-176: TOML manipulation logic is correct.The use of embedded Python scripts for TOML parsing is a pragmatic choice given Bash's limitations. The regex patterns and logic are sound.
💡 Optional: Consider extracting Python scripts to separate files
If this script grows further or requires more TOML manipulation, consider extracting the Python logic into separate
.pyfiles for easier maintenance and testing. However, the current approach is acceptable for the script's scope.
365-393: Consider improving cross-platform CPU count detection.The
nproccommand is not available on macOS, though the fallback to 4 cores works. For better macOS support, consider detecting the platform and usingsysctl -n hw.ncpuon Darwin.🔧 Improved cross-platform CPU detection
- cmake --build "${PROJECT_ROOT}/build/Debug" --config Debug --parallel "$(nproc 2>/dev/null || echo 4)" + local ncpu=4 + if [[ "$(uname)" == "Darwin" ]]; then + ncpu="$(sysctl -n hw.ncpu 2>/dev/null || echo 4)" + else + ncpu="$(nproc 2>/dev/null || echo 4)" + fi + cmake --build "${PROJECT_ROOT}/build/Debug" --config Debug --parallel "${ncpu}"
📜 Review details
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
scripts/publish_rust_crate.sh
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (19)
- GitHub Check: macOS Build (Release-shared)
- GitHub Check: Windows Build VS2022 (Debug-static)
- GitHub Check: Windows Build VS2022 (Debug-shared)
- GitHub Check: Windows Build VS2022 (Release-shared)
- GitHub Check: Build (Release-gcc-static)
- GitHub Check: Build Fedora (Debug-shared)
- GitHub Check: Windows Build VS2026 (Debug-shared)
- GitHub Check: macOS Release - No File Playback
- GitHub Check: Windows Build MinGW (Release-static)
- GitHub Check: Build (Release-clang-shared)
- GitHub Check: Windows Build MinGW (Debug-shared)
- GitHub Check: Build Fedora (Release-static)
- GitHub Check: Windows Build VS2022 (Release-static)
- GitHub Check: Build (Release-gcc-shared)
- GitHub Check: Windows Build MinGW (Debug-static)
- GitHub Check: Build (Debug-clang-static)
- GitHub Check: Windows VS2022 Release - No File Playback
- GitHub Check: Build (Release-clang-static)
- GitHub Check: Static Link (Dev) (windows-latest)
🔇 Additional comments (13)
scripts/publish_rust_crate.sh (13)
1-18: LGTM: Script setup follows best practices.The shebang, error handling (
set -euo pipefail), and path resolution are all correctly implemented.
20-28: LGTM: Global variables are well-defined.All variables have clear names and sensible default values.
30-68: LGTM: Comprehensive usage documentation.The help text is clear, complete, and provides good guidance for users.
70-110: LGTM: Logging and prompting utilities are correctly implemented.The functions handle both interactive and non-interactive modes properly, with appropriate error handling for EOF scenarios.
178-193: LGTM: Native source vendoring is correctly implemented.The use of
tarfor directory copying preserves permissions and metadata, and the function includes appropriate existence checks.
195-237: LGTM: Mode selection and text prompting are well-implemented.The interactive mode selection provides clear options and appropriate defaults for non-interactive use.
244-251: Rustup installation follows official method.Downloading and executing the rustup installer is the standard installation approach. The use of HTTPS mitigates MITM risks, though users should be aware that this executes remote code.
273-363: LGTM: TOML readers, crates.io checks, and version utilities are correctly implemented.The
cargo searchoutput parsing is necessarily fragile but represents the best available approach without direct API access. All version manipulation logic is sound.
395-403: LGTM: Login hint provides helpful user guidance.The check for credentials files and associated warning message will help users diagnose authentication issues.
405-456: LGTM: Argument parsing is complete and correct.All documented options are handled properly with appropriate error messages for invalid inputs.
481-499: LGTM: Auto-publish test version logic is correctly implemented.The
-y --publishcombination correctly generates a test version with the format<version>-test.<timestamp>.<sha>, enabling CI/CD-friendly one-step test publishing as described in the PR objectives.
501-566: Excellent safety checks for publish mode and default features.The logic correctly:
- Prevents stable releases from non-main branches
- Detects when
static-linkis in default features (not crates.io-friendly)- Offers automatic remediation with
build-sourceas the default- Differentiates between dry-run and actual publish scenarios
These guards help prevent common publishing mistakes.
568-762: LGTM: Version management, vendoring, testing, and publish workflow are comprehensive and correct.This segment implements robust logic for:
- Enforcing pre-release versions on non-main branches
- Auto-vendoring native sources for self-contained crate distribution
- Running both static-link (dev) and build-source (distribution) tests
- Validating crates.io packaging requirements
- Skipping confirmation prompts for auto-publish mode (PR objective:
-y --publish)- Cleaning up auto-generated artifacts appropriately
The comment at lines 689-692 correctly explains the need for
--no-default-featureswhen testing static-link in isolation, and the implementation at line 706 follows this guidance correctly.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 3
🤖 Fix all issues with AI agents
In @scripts/README.md:
- Around line 75-99: Update the "Directory Structure" section of
scripts/README.md to list the new test_remote_crate.sh script, i.e., add an
entry for test_remote_crate.sh (description: "Remote Rust crate verification
script") alongside the other script entries in the scripts/ directory listing so
the directory tree includes test_remote_crate.sh and matches the examples and
verification scope documented earlier.
In @scripts/test_remote_crate.sh:
- Around line 99-110: The current extraction of actual_version uses `cargo
metadata --no-deps`, which omits dependencies so `ccap-rs` is often missing;
change the command used to populate the actual_version variable to call `cargo
metadata --format-version=1` without `--no-deps` (i.e., remove the `--no-deps`
flag) so dependency packages are included, keep the same JSON parsing pipeline
and redirections, and leave the `actual_version` variable and `log "Testing
ccap-rs version: ${actual_version}"` intact.
🧹 Nitpick comments (4)
scripts/README.md (1)
117-123: Consider updating usage recommendations for the new script.The "Usage Recommendations" section does not mention the new
test_remote_crate.shscript. Consider adding guidance on when to use it (e.g., "After release, test the published Rust crate from crates.io usingtest_remote_crate.sh").scripts/test_remote_crate.sh (3)
68-71: Add a safety guard beforerm -rf "${TEST_DIR}"
Right now${TEST_DIR}is derived safely, but a small defensive check prevents catastrophic deletion if path computation ever regresses.Proposed diff
log "Version: ${VERSION}" log "Test directory: ${TEST_DIR}" # Clean up any existing test directory - rm -rf "${TEST_DIR}" + if [[ -z "${TEST_DIR}" || "${TEST_DIR}" == "/" || "${TEST_DIR}" == "${PROJECT_ROOT}" ]]; then + die "Refusing to delete unsafe TEST_DIR='${TEST_DIR}'" + fi + rm -rf "${TEST_DIR}" mkdir -p "${TEST_DIR}"
51-63: “latest” version resolution viacargo search | grepis brittle; prefer crates.io API
Parsingcargo searchoutput is not a stable interface and can misbehave across cargo versions/local registry state. Consider using the crates.io HTTP API (you already depend onpython3).Sketch of a more deterministic approach
if [[ "${VERSION}" == "latest" ]]; then log "Fetching latest version from crates.io..." local latest_version - latest_version=$(cargo search ccap-rs --limit 1 2>/dev/null | grep -oE '[0-9]+\.[0-9]+\.[0-9]+(-[a-zA-Z0-9.]+)?' | head -1 || echo "") + latest_version=$( + curl -fsSL "https://crates.io/api/v1/crates/ccap-rs" | + python3 -c "import sys, json; print(json.load(sys.stdin)['crate']['max_version'])" 2>/dev/null || echo "" + )
348-375: Don’t discard build/run output on failure
--quiet 2>&1makes failures hard to diagnose (especially in CI). Consider capturing logs and printing them on error.Proposed diff
log "Building test project..." - if ! cargo build --quiet 2>&1; then + build_log="$(mktemp)" + if ! cargo build >"${build_log}" 2>&1; then error "Build failed" + cat "${build_log}" >&2 || true return 1 fi + rm -f "${build_log}" || true
📜 Review details
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (2)
scripts/README.mdscripts/test_remote_crate.sh
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (18)
- GitHub Check: Windows Build VS2022 (Release-shared)
- GitHub Check: Windows Build MinGW (Debug-static)
- GitHub Check: Windows Build VS2022 (Debug-shared)
- GitHub Check: Windows Build VS2022 (Debug-static)
- GitHub Check: Build Fedora (Debug-shared)
- GitHub Check: Build ARM64 (Release-gcc-shared)
- GitHub Check: Build Fedora (Release-static)
- GitHub Check: Windows Build MinGW (Debug-shared)
- GitHub Check: Build (Debug-gcc-static)
- GitHub Check: Build (Release-gcc-shared)
- GitHub Check: Build Fedora (Release-shared)
- GitHub Check: Build Source (Dist) (windows-latest)
- GitHub Check: Build (Release-clang-shared)
- GitHub Check: Build (Release-gcc-static)
- GitHub Check: Static Link (Dev) (macos-latest)
- GitHub Check: Static Link (Dev) (windows-latest)
- GitHub Check: Build Source (Dist) (macos-latest)
- GitHub Check: macOS Build (Release-static)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 0
🧹 Nitpick comments (7)
bindings/rust/src/frame.rs (2)
140-151: Consider documenting contiguous memory assumption.The
data()method assumes frame data is stored contiguously starting atdata[0]forsizeInBytesbytes. For planar formats (NV12, I420) where planes might not be contiguous in memory, this could create an invalid slice.If the C library guarantees contiguous allocation, a brief doc comment would clarify this contract.
📝 Suggested documentation
- /// Get all frame data as a slice + /// Get all frame data as a contiguous slice. + /// + /// # Note + /// This assumes the underlying frame data is stored contiguously in memory. + /// For planar formats, use [`info()`] to access individual planes if they + /// may be non-contiguous. pub fn data(&self) -> crate::error::Result<&[u8]> {
153-178: Consider performance note for multiple accessor calls.Each convenience method (
width(),height(), etc.) callsinfo()which performs an FFI call. Callers using multiple accessors in sequence (e.g.,frame.width()thenframe.height()) will incur repeated FFI overhead. Consider adding a doc note suggesting users callinfo()once when multiple properties are needed.bindings/rust/src/async.rs (2)
30-64: Consider spawn_blocking for potentially slow operations.Methods like
open()(device initialization) may take significant time and currently hold the async mutex lock while making blocking FFI calls. This could starve other tasks waiting on the mutex. Consider usingspawn_blockingsimilar tograb_frame_timeoutfor operations that may block.Not critical since camera operations typically need serialization, but worth considering for responsiveness.
66-77: Duration to u32 truncation.Line 69 casts
timeout.as_millis()(u128) to u32, which silently truncates timeouts exceeding ~49.7 days. While practically unlikely, consider usingtry_into()or saturating conversion for robustness.♻️ Suggested fix
- let timeout_ms = timeout.as_millis() as u32; + let timeout_ms = timeout.as_millis().min(u32::MAX as u128) as u32;bindings/rust/README.md (1)
119-123: Fix nested list indentation for markdown consistency.Nested list items should use 2-space indentation instead of 4 spaces to comply with markdown best practices (MD007).
📝 Proposed formatting fix
- **Distribution mode (default):** `build-source` - - Builds the native C/C++ implementation via the `cc` crate. - - Intended for crates.io users. + - Builds the native C/C++ implementation via the `cc` crate. + - Intended for crates.io users. - **Development mode:** `static-link` - - Links against a pre-built native library from a CameraCapture checkout (e.g. `build/Debug/libccap.a`). - - Intended for developing this repository. + - Links against a pre-built native library from a CameraCapture checkout (e.g. `build/Debug/libccap.a`). + - Intended for developing this repository.- You can disable the auto-link behavior by setting: - - `CCAP_RUST_NO_ASAN_LINK=1` + - `CCAP_RUST_NO_ASAN_LINK=1`Also applies to: 167-167
bindings/rust/build.rs (2)
163-163: Fix trailing whitespace.Line 163 has trailing whitespace after the panic message string.
🧹 Proposed fix
-Please vendor the sources into bindings/rust/native/, or set CCAP_SOURCE_DIR to a CameraCapture checkout." +Please vendor the sources into bindings/rust/native/, or set CCAP_SOURCE_DIR to a CameraCapture checkout."
274-319: ASan detection and runtime linking logic is well-implemented; consider testing with ASan-instrumented builds in CI.The code correctly detects ASan-instrumented prebuilt libraries and links the appropriate runtime:
- Symbol detection (lines 286-287): Searches for both
___asan_initand__asan_init. The dual-symbol check is practical and defensive, though the rationale for checking both variants could be documented.- macOS runtime linking (lines 300-312): Gracefully handles missing clang toolchain (checks
dylib.exists()before using rpath) and includes a fallback link with-lclang_rt.asan_osx_dynamic.- Linux runtime linking (lines 295-298): Straightforward
-lasanlink with clear documentation thatlibasan6(or equivalent) must be installed.- User override: Properly supports
CCAP_RUST_NO_ASAN_LINKenvironment variable with correctrerun-if-env-changedsetup.The Rust CI workflow currently tests static-link mode but does not build the C library with ASan enabled. While functional tests in
run_tests.shenable ASan by default, consider adding a CI job that explicitly builds with-DASAN=ONand tests the Rust bindings against it to validate the detection and linking logic end-to-end.
📜 Review details
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (1)
bindings/rust/Cargo.lockis excluded by!**/*.lock
📒 Files selected for processing (8)
.github/workflows/rust.ymlCMakeLists.txtbindings/rust/Cargo.tomlbindings/rust/README.mdbindings/rust/build.rsbindings/rust/examples/minimal_example.rsbindings/rust/src/async.rsbindings/rust/src/frame.rs
✅ Files skipped from review due to trivial changes (1)
- bindings/rust/examples/minimal_example.rs
🚧 Files skipped from review as they are similar to previous changes (3)
- bindings/rust/Cargo.toml
- .github/workflows/rust.yml
- CMakeLists.txt
🧰 Additional context used
🧬 Code graph analysis (2)
bindings/rust/src/frame.rs (3)
include/ccap_def.h (1)
DeviceInfo(425-442)bindings/rust/src/types.rs (5)
from(35-52)from(87-103)from(116-126)from(154-167)from(220-225)bindings/rust/src/error.rs (1)
from(85-111)
bindings/rust/src/async.rs (1)
bindings/rust/src/provider.rs (8)
new(67-78)find_device_names(357-359)open(196-208)start(248-250)stop(253-255)is_opened(258-260)is_started(243-245)grab_frame(303-314)
🪛 GitHub Check: Static Link (Dev) (ubuntu-latest)
bindings/rust/build.rs
[warning] 277-277:
Diff in /home/runner/work/CameraCapture/CameraCapture/bindings/rust/build.rs
bindings/rust/src/async.rs
[warning] 80-80:
Diff in /home/runner/work/CameraCapture/CameraCapture/bindings/rust/src/async.rs
🪛 LanguageTool
bindings/rust/README.md
[style] ~219-~219: Consider using a less common alternative to make your writing sound more unique and professional.
Context: ...ontributing Contributions are welcome! Please feel free to submit a Pull Request. ## License Thi...
(FEEL_FREE_TO_STYLE_ME)
🪛 markdownlint-cli2 (0.18.1)
bindings/rust/README.md
119-119: Unordered list indentation
Expected: 2; Actual: 4
(MD007, ul-indent)
120-120: Unordered list indentation
Expected: 2; Actual: 4
(MD007, ul-indent)
122-122: Unordered list indentation
Expected: 2; Actual: 4
(MD007, ul-indent)
123-123: Unordered list indentation
Expected: 2; Actual: 4
(MD007, ul-indent)
167-167: Unordered list indentation
Expected: 2; Actual: 4
(MD007, ul-indent)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (20)
- GitHub Check: Build (Release-clang-static)
- GitHub Check: macOS Release - No File Playback
- GitHub Check: Windows Build MinGW (Release-static)
- GitHub Check: Build (Debug-clang-static)
- GitHub Check: macOS Build (Debug-shared)
- GitHub Check: Build (Release-clang-shared)
- GitHub Check: Build ARM64 (Release-gcc-static)
- GitHub Check: Build ARM64 (Release-gcc-shared)
- GitHub Check: Build Fedora (Debug-shared)
- GitHub Check: Windows Build MinGW (Debug-static)
- GitHub Check: Build (Release-gcc-static)
- GitHub Check: Build (Debug-gcc-static)
- GitHub Check: Build ARM64 (Debug-gcc-static)
- GitHub Check: Build Source (Dist) (windows-latest)
- GitHub Check: Windows Build MinGW (Debug-shared)
- GitHub Check: Windows Build VS2022 (Release-static)
- GitHub Check: Windows Build VS2022 (Release-shared)
- GitHub Check: Windows Build MinGW (Release-shared)
- GitHub Check: Windows Build VS2022 (Debug-shared)
- GitHub Check: Windows VS2022 Release - No File Playback
🔇 Additional comments (17)
bindings/rust/src/frame.rs (5)
4-43: LGTM!The
DeviceInfostruct and itsfrom_c_structimplementation are well-designed. Good defensive programming with the.min()bounds checks on lines 25 and 31 to prevent buffer overreads if the count fields are inconsistent with array sizes.
45-84: LGTM!The ownership model using
owns_frameflag is appropriate for FFI scenarios where some frames are owned (from grab) and others are borrowed (from callbacks). The null-check infrom_rawis a good safety measure.
181-200: LGTM!The
Dropimplementation correctly respects theowns_frameflag. Theunsafe impl Sendis well-documented with clear safety rationale and an honest note about the C++ library's thread-safety guarantees.
202-223: LGTM!The
VideoFrameInfostruct properly uses lifetime'ato tie the borrowed slices to theVideoFrame, preventing use-after-free. The optional planes handle both planar and packed formats cleanly.
93-106: The plane size calculation correctly handles all supported pixel formats. The C library properly sets stride[1] and stride[2] to 0 for packed formats like YUYV/UYVY, and the height/2 factor is correct for the supported 4:2:0 formats (NV12, I420). No code changes needed.bindings/rust/src/async.rs (3)
14-28: LGTM!Using
Arc<Mutex<SyncProvider>>with tokio's asyncMutexis appropriate for sharing the provider across async tasks. Thenew()method correctly propagates errors from the underlying sync provider creation.
86-113: LGTM!The async tests appropriately handle the case where no camera hardware is available. The
test_async_find_devicesgracefully handles both success and error cases, which is suitable for CI environments without camera access.
79-82: No action required - implementation is correct.The 0ms timeout semantics are verified: when
timeoutInMs == 0, the C library immediately returns without waiting (conditionif (m_availableFrames.empty() && timeoutInMs > 0)insrc/ccap_imp.cpp:137skips the wait logic). This correctly implements non-blocking behavior. The Rust binding at lines 79-82 properly delegates to this behavior.bindings/rust/README.md (3)
1-28: LGTM! Clear documentation and quick start.The header and quick start sections are well-structured. The package aliasing (
package = "ccap-rs"→ccap) provides a convenient shorter import name while maintaining the full crate name on crates.io.
193-200: Excellent thread-safety documentation.The thread-safety section provides clear guidance on how to safely use the API across threads, with honest disclosure about the C++ layer's thread-safety being based on code inspection. The recommendations to use
Arc<Mutex<Provider>>orAsyncProviderare appropriate.
30-110: Well-structured examples with correct syntax.The basic and async usage examples demonstrate the API clearly. The use of
r#asyncfor the async module import is the correct raw identifier syntax for reserved keywords.bindings/rust/build.rs (6)
6-14: LGTM! Robust byte pattern search.The function includes good defensive checks (empty needle, file read errors) and uses an efficient sliding window approach for pattern matching.
16-54: Well-designed cross-platform clang discovery.The function attempts multiple strategies (PATH, xcrun on macOS) with appropriate fallbacks. Good handling of command failures and empty output.
56-73: LGTM! Clear validation and discovery logic.The root validation checks for key files, and the parent directory walk with a reasonable limit (16) handles both development and cargo publish scenarios well.
243-261: NEON build always compiles for symbol availability.The NEON source file is compiled on all architectures to provide the
hasNEON()symbol, with the implementation returning false on non-ARM viaENABLE_NEON_IMP=0. This is a reasonable trade-off for build simplicity, though it adds a small compilation cost on non-ARM platforms.
346-374: Platform-specific linking is correctly configured.System frameworks and libraries are properly linked for each platform:
- macOS: Apple frameworks + Accelerate for SIMD
- Linux: stdc++ (v4l2 correctly commented out due to availability concerns)
- Windows: DirectShow and Media Foundation libraries
426-446: Bindgen configuration is well-structured.The bindgen setup correctly:
- Uses wrapper.h with proper include paths
- Applies appropriate allowlists for
ccap_*,Ccap*, andCCAP_*identifiers- Derives useful traits (Default, Debug, PartialEq, Eq)
- Integrates with cargo via CargoCallbacks
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 4
🤖 Fix all issues with AI agents
In @bindings/rust/README.md:
- Around line 129-133: Fix the Markdown list indentation by replacing 4-space
indents with 2-space indents for the bullet lists noted in
bindings/rust/README.md (affecting the block around lines 129–133 and the lists
at 177, 183, and 189–191); update the indented list lines inside the fenced code
blocks so each list item uses two leading spaces before the list marker (e.g., "
- ..." or " +" as appropriate) and ensure the surrounding triple-backtick
fences and surrounding text remain unchanged so MD007 is satisfied.
- Line 38: Replace the bare URL "https://crates.io/crates/ccap-rs" with a
Markdown link to satisfy MD034; update the README line that currently shows the
raw URL so it becomes something like a descriptive link (e.g.,
[crates.io/crates/ccap-rs](https://crates.io/crates/ccap-rs)) to ensure the URL
is no longer bare.
In @README.md:
- Around line 215-216: Replace the two bare URLs on the README (the lines
currently showing "Crate: https://crates.io/crates/ccap-rs" and "Docs:
https://docs.rs/ccap-rs") with markdown link syntax; e.g. convert the crate URL
into a descriptive link label like "Crate:
[ccap-rs](https://crates.io/crates/ccap-rs)" and the docs URL into "Docs:
[ccap-rs](https://docs.rs/ccap-rs)" so the lines use proper [text](url) format
and satisfy MD034.
🧹 Nitpick comments (4)
docs/content/rust-bindings.md (1)
75-77: Minor clarification opportunity for environment variable setup.Line 77 mentions setting
CCAP_SOURCE_DIRbut could be slightly more explicit that this is an environment variable. Consider rephrasing to: "If needed, set the environment variable:" for improved clarity.📝 Suggested wording improvement
-If needed, set: +If needed, set the environment variable:scripts/test_remote_crate.sh (3)
79-110: Consider usingcargo addfor simplicity.The embedded Python script for Cargo.toml manipulation works but is complex. Since Rust 1.62+,
cargo addis available and would simplify this:cargo add ccap-rs@"${VERSION}"This would eliminate the need for manual TOML parsing and handle edge cases automatically.
🔧 Alternative implementation using cargo add
- # Add dependency to Cargo.toml - log "Adding ccap-rs dependency (version: ${VERSION})..." - python3 - "${VERSION}" <<'PY' -import re -import sys -from pathlib import Path - -version = sys.argv[1] -path = Path("Cargo.toml") -text = path.read_text(encoding="utf-8") - -dep_line = f'ccap-rs = "{version}"\n' - -# Find the [dependencies] section (until the next [section] or EOF) -m = re.search(r'(?ms)^\[dependencies\]\s*\n(.*?)(^\[|\Z)', text) - -if m: - body = m.group(1) - # Replace existing ccap-rs entry if present; otherwise insert at top of the section. - if re.search(r'(?m)^ccap-rs\s*=\s*"[^"]*"\s*$', body): - body2 = re.sub(r'(?m)^ccap-rs\s*=\s*"[^"]*"\s*$', dep_line.rstrip("\n"), body, count=1) - else: - body2 = dep_line + body - text = text[: m.start(1)] + body2 + text[m.end(1) :] -else: - # No [dependencies] section - append one. - if not text.endswith("\n"): - text += "\n" - text += "\n[dependencies]\n" + dep_line - -path.write_text(text, encoding="utf-8") -PY + # Add dependency using cargo add (available since Rust 1.62) + log "Adding ccap-rs dependency (version: ${VERSION})..." + cargo add "ccap-rs@${VERSION}" --quietNote: Verify that
cargo addis available in your minimum supported Rust version.
194-194: Consider making camera initialization delay configurable.The hardcoded 500ms sleep may be insufficient on slower systems or overly conservative on faster ones. Consider making this configurable via an environment variable for flexibility in different testing environments.
♻️ Proposed enhancement
Add near the top of the Rust test code:
// Allow configuring camera initialization delay via environment variable let init_delay = std::env::var("CCAP_CAMERA_INIT_DELAY_MS") .ok() .and_then(|s| s.parse().ok()) .unwrap_or(500); std::thread::sleep(std::time::Duration::from_millis(init_delay));
367-374: Preserve build output for better debugging experience.The
--quietflag suppresses all compiler output, making it difficult to diagnose build failures. Consider showing the full output or at least capturing and displaying it on failure.📋 Suggested improvement
# Build the test log "Building test project..." - if ! cargo build --quiet 2>&1; then - error "Build failed" + if ! cargo build 2>&1; then + error "Build failed - see output above for details" return 1 fiOr, if you prefer to keep quiet mode but show output on failure:
log "Building test project..." - if ! cargo build --quiet 2>&1; then - error "Build failed" + if ! build_output=$(cargo build 2>&1); then + error "Build failed:" + echo "${build_output}" >&2 return 1 + else + log "Build successful" fi - - log "Build successful"
📜 Review details
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (17)
README.mdREADME.zh-CN.mdbindings/rust/Cargo.tomlbindings/rust/README.mddocs/content/cli.mddocs/content/cli.zh.mddocs/content/cmake-options.mddocs/content/cmake-options.zh.mddocs/content/documentation.mddocs/content/documentation.zh.mddocs/content/implementation-details.mddocs/content/implementation-details.zh.mddocs/content/rust-bindings.mddocs/content/rust-bindings.zh.mddocs/documentation.htmldocs/index.htmlscripts/test_remote_crate.sh
✅ Files skipped from review due to trivial changes (3)
- docs/content/documentation.zh.md
- docs/content/cmake-options.zh.md
- docs/index.html
🚧 Files skipped from review as they are similar to previous changes (1)
- bindings/rust/Cargo.toml
🧰 Additional context used
📓 Path-based instructions (1)
docs/**/*.md
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
.mdfiles indocs/must be English and require review before publishing
Files:
docs/content/cli.mddocs/content/cmake-options.mddocs/content/implementation-details.zh.mddocs/content/implementation-details.mddocs/content/rust-bindings.mddocs/content/cli.zh.mddocs/content/documentation.mddocs/content/rust-bindings.zh.md
🧠 Learnings (1)
📚 Learning: 2025-12-31T04:11:08.028Z
Learnt from: CR
Repo: wysaid/CameraCapture PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-12-31T04:11:08.028Z
Learning: Respond in user's language (except `docs/` which must be English)
Applied to files:
docs/documentation.html
🪛 LanguageTool
bindings/rust/README.md
[style] ~244-~244: Consider using a less common alternative to make your writing sound more unique and professional.
Context: ...ontributing Contributions are welcome! Please feel free to submit a Pull Request. ## License Thi...
(FEEL_FREE_TO_STYLE_ME)
🪛 markdownlint-cli2 (0.18.1)
README.md
215-215: Bare URL used
(MD034, no-bare-urls)
216-216: Bare URL used
(MD034, no-bare-urls)
bindings/rust/README.md
38-38: Bare URL used
(MD034, no-bare-urls)
129-129: Unordered list indentation
Expected: 2; Actual: 4
(MD007, ul-indent)
130-130: Unordered list indentation
Expected: 2; Actual: 4
(MD007, ul-indent)
132-132: Unordered list indentation
Expected: 2; Actual: 4
(MD007, ul-indent)
133-133: Unordered list indentation
Expected: 2; Actual: 4
(MD007, ul-indent)
177-177: Unordered list indentation
Expected: 2; Actual: 4
(MD007, ul-indent)
183-183: Unordered list indentation
Expected: 2; Actual: 4
(MD007, ul-indent)
189-189: Unordered list indentation
Expected: 2; Actual: 4
(MD007, ul-indent)
190-190: Unordered list indentation
Expected: 2; Actual: 4
(MD007, ul-indent)
191-191: Unordered list indentation
Expected: 2; Actual: 4
(MD007, ul-indent)
docs/content/rust-bindings.zh.md
53-53: Emphasis used instead of a heading
(MD036, no-emphasis-as-heading)
70-70: Emphasis used instead of a heading
(MD036, no-emphasis-as-heading)
88-88: Emphasis used instead of a heading
(MD036, no-emphasis-as-heading)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (20)
- GitHub Check: Build Source (Dist) (windows-latest)
- GitHub Check: Build Source (Dist) (macos-latest)
- GitHub Check: Build Fedora (Release-static)
- GitHub Check: Build Fedora (Debug-static)
- GitHub Check: Windows Build VS2022 (Debug-shared)
- GitHub Check: Build (Release-gcc-shared)
- GitHub Check: Build ARM64 (Release-gcc-static)
- GitHub Check: Windows Build MinGW (Release-shared)
- GitHub Check: Build (Debug-clang-static)
- GitHub Check: Windows Build MinGW (Release-static)
- GitHub Check: Windows Build MinGW (Debug-static)
- GitHub Check: Windows Build VS2022 (Release-shared)
- GitHub Check: Windows Build VS2022 (Release-static)
- GitHub Check: Windows Build MinGW (Debug-shared)
- GitHub Check: Build ARM64 (Release-gcc-shared)
- GitHub Check: Build (Release-clang-shared)
- GitHub Check: macOS Build (Debug-shared)
- GitHub Check: Windows VS2022 Release - No File Playback
- GitHub Check: macOS Build (Debug-static)
- GitHub Check: macOS Build (Release-shared)
🔇 Additional comments (20)
docs/content/implementation-details.md (1)
408-408: LGTM! Good addition to the documentation navigation.The Rust Bindings link is appropriately placed in the "Need Help?" section and uses the 🦀 emoji consistently with the Rust theme. The link format matches the existing documentation structure.
docs/content/cmake-options.md (1)
411-411: LGTM! Appropriate documentation cross-reference.The Rust Bindings link is well-placed in the Related Documentation section and follows the same format as other documentation links.
docs/documentation.html (3)
18-20: LGTM! Appropriate syntax highlighting support.Adding Rust and TOML language support to highlight.js is necessary for the new Rust bindings documentation, which will include Rust code examples and
Cargo.tomlconfiguration snippets.
41-41: LGTM! Consistent navigation additions.The Rust Bindings links are properly integrated into both the navigation dropdown and footer documentation section, following the existing bilingual pattern with English and Chinese labels.
Also applies to: 84-84
224-229: Excellent security improvement!The sanitization of the
baseDocNameparameter effectively prevents path traversal attacks by restricting input to alphanumeric characters and hyphens only, with a safe fallback to'documentation'if the sanitized value is empty. This is a best practice for handling user-controlled input in URL parameters.docs/content/cli.zh.md (1)
485-485: LGTM! Appropriate Chinese documentation link.The Rust Bindings link is correctly added to the Chinese version of the CLI documentation with proper Chinese text. The link references the Chinese version of the Rust bindings documentation (
rust-bindings.zh.md), maintaining the bilingual documentation structure.docs/content/cli.md (1)
521-521: LGTM! Appropriate documentation cross-reference.The Rust Bindings link is well-placed in the "See Also" section with a clear and accurate description. The format is consistent with other documentation references in the same section.
README.zh-CN.md (2)
6-6: Rust CI badge placement is consistent with platform CI badges.The badge URL and workflow reference follow the same pattern as existing platform CI badges, and the positioning among build status indicators is appropriate.
174-193: Rust Bindings section is well-structured and provides clear guidance.The section logically follows the C++ and C interface examples, includes essential metadata (crate name, docs.rs link, source path), and provides practical quick-start guidance (cargo add, Cargo.toml aliasing, crate naming clarification). Chinese phrasing is clear and professional.
docs/content/rust-bindings.md (1)
1-93: Comprehensive and well-structured Rust bindings documentation.The documentation clearly introduces the crate, provides practical dependency examples (including async feature), includes a syntactically correct minimal example, and thoroughly documents feature flags and build modes. Platform support is accurately described, and related documentation links are properly positioned.
docs/content/documentation.md (1)
13-13: Documentation index correctly links to both C Interface and Rust Bindings.The wording update appropriately expands the language bindings reference to include Rust bindings alongside the C interface, with correct relative links to both documentation files.
docs/content/implementation-details.zh.md (1)
303-303: Link insertion aligns with new Rust bindings—clarify documentation guideline scope.The added link to Rust bindings documentation is appropriate, but the coding guideline specifies that files in
docs/must be English. This file is explicitly a Chinese translation (.zh.md). Please clarify whether the English-only guideline is intended to apply to translated documentation files or only to primary English documentation. If translation files are in scope, this file should not be modified; if not, this change is approved.Per coding guidelines: "docs/**/*.md: .md files in docs/ must be English and require review before publishing"
README.md (1)
6-6: Rust integration content looks solid.The new Rust badge, updated description, and Rust Bindings section integrate well into the README. The quick-install instructions and feature documentation (async support) are clear. Once the bare URLs are fixed, this section is ready.
Also applies to: 14-14, 211-230
bindings/rust/README.md (1)
1-250: Comprehensive Rust bindings documentation—structure and content are excellent.The README provides clear guidance on quick-start, examples, building, platform support, and thread safety. The sync and async usage examples are well-balanced, and the section on feature modes (build-source vs. static-link) and ASan handling shows thoughtful integration planning. Once the minor linting issues (bare URL and indentation) are resolved, this documentation is production-ready.
docs/content/rust-bindings.zh.md (1)
1-92: Documentation language policy requires clarification.The coding guidelines state that
.mdfiles indocs/must be English; however, this file is entirely in Chinese. Notably, the repository already contains 7 localized Chinese documentation files (.zh.md) paired with English equivalents, suggesting an established localization strategy that the current guideline doesn't explicitly permit.Either translate this to English or update the coding guidelines to clarify whether locale-specific variants (
.zh.md) are acceptable. If localized documentation is intended, the guideline should be revised to explicitly allow parallel translations.scripts/test_remote_crate.sh (5)
1-18: LGTM!The script header and setup follow Bash best practices with proper error handling (
set -euo pipefail) and portable shebang. Directory resolution is correct.
20-35: LGTM!Logging functions are well-implemented with proper color coding and stderr redirection for warnings and errors.
37-46: LGTM!Disabling automatic cleanup by default is a good choice for preserving test artifacts (captured images, etc.) for inspection. The comment clearly documents this behavior.
397-397: LGTM!Standard pattern for executing the main function with all script arguments.
51-63: No action needed. Thegrep -oEcombination is supported on both GNU and BSD grep (macOS default). The version extraction pattern works correctly across both implementations.Likely an incorrect or invalid review comment.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 7
🤖 Fix all issues with AI agents
In @bindings/rust/build_and_test.sh:
- Line 97: The build line uses nproc which isn’t portable on macOS/BSD; add a
reusable CPU detection (e.g., a function or top-level variable like JOBS
computed via nproc || sysctl -n hw.ncpu || fallback to 4) and replace the raw
nproc usage in the cmake --build invocation with -j"$JOBS" (also update the
earlier occurrence around line 30 to use the same JOBS variable).
- Around line 205-206: Remove the duplicate echo of the ccap dependency line:
keep a single line that prints echo ' ccap = { package = "ccap-rs", path =
"'$RUST_DIR'" }' and delete the repeated identical echo so the script outputs
the dependency only once.
- Around line 35-36: The script assumes the built CLI is named "ccap"
unconditionally when setting cli_bin; on Windows the binary is "ccap.exe" so the
Windows path must be chosen or detected. Update the assignment for cli_bin (the
variable set to "$PROJECT_ROOT/build/Debug/ccap") to either detect/append the
.exe extension when running on Windows (e.g., via OSTYPE/`uname` or a
file-exists check) or prefer a file-existence check that tries
"$PROJECT_ROOT/build/Debug/ccap" and then "$PROJECT_ROOT/build/Debug/ccap.exe"
and assigns whichever exists; adjust the cli_bin logic used later to rely on
this corrected value.
In @bindings/rust/src/provider.rs:
- Around line 210-235: open_device currently destroys and recreates self.handle
when a device name is supplied but does not clean up the per-instance frame
callback, which can leak resources; before destroying or replacing the provider
handle in open_device (same place where it calls
sys::ccap_provider_destroy(self.handle)), call the existing cleanup_callback()
method to free any previously-registered callback and reset self.callback_ptr
(same cleanup done in open_with_index), then proceed to destroy/create the
handle and set is_opened; ensure set_new_frame_callback will be able to register
a fresh callback afterwards.
- Around line 379-444: The set_error_callback implementation on Provider
currently installs a single global callback via GLOBAL_ERROR_CALLBACK, which
makes callbacks shared between all Provider instances and persist after
instances are dropped; change the API to make this explicit by converting the
instance method Provider::set_error_callback into an associated/static function
(e.g., Provider::set_global_error_callback) and update all call sites and
examples to use the new name, or alternatively add a prominent doc comment on
Provider::set_error_callback clarifying it is global; ensure you update
references to GLOBAL_ERROR_CALLBACK and the wrapper error_callback_wrapper to
match the new static/associated usage so callers understand the callback scope.
- Line 92: Update the comment next to the is_opened field to remove "likely" and
state the documented behavior: change the comment for is_opened to indicate the
device is opened by the C API, e.g. "Device opened by
ccap_provider_create_with_index/ccap_provider_create_with_device" so it reflects
the documented behavior of those functions (affects the struct initialization
where is_opened: true is set).
🧹 Nitpick comments (4)
bindings/rust/build_and_test.sh (1)
110-110: Consider makingcargo cleanoptional for faster iterative testing.While clean builds are good for CI, they slow down local development iterations. Consider making this conditional (e.g., skip if
SKIP_CLEANenv var is set).💡 Optional enhancement
# Clean previous build -cargo clean +if [ "${SKIP_CLEAN:-}" != "1" ]; then + cargo clean +fiThen developers can run:
SKIP_CLEAN=1 ./build_and_test.shbindings/rust/README.md (1)
98-102: Consider fixing nested list indentation for consistency.Markdownlint reports that nested list items use 4-space indentation instead of the expected 2 spaces. While this doesn't affect functionality, standardizing to 2-space indentation improves consistency with common Markdown conventions.
♻️ Proposed indentation fixes
- **Distribution mode (default):** `build-source` - - Builds the native C/C++ implementation via the `cc` crate. - - Intended for crates.io users. + - Builds the native C/C++ implementation via the `cc` crate. + - Intended for crates.io users. - **Development mode:** `static-link` - - Links against a pre-built native library from a CameraCapture checkout (e.g. `build/Debug/libccap.a`). - - Intended for developing this repository. + - Links against a pre-built native library from a CameraCapture checkout (e.g. `build/Debug/libccap.a`). + - Intended for developing this repository.Apply the same 2-space indentation pattern to lines 146, 152, and 157-159.
Also applies to: 146-146, 152-152, 157-159
bindings/rust/RUST_INTEGRATION_SUMMARY.md (1)
29-48: Add language specifier to fenced code block.The project structure code block (lines 29-48) lacks a language specifier. Adding
textorplaintextimproves rendering and accessibility.♻️ Proposed fix
-``` +```text bindings/rust/ ├── Cargo.toml # package configurationbindings/rust/src/provider.rs (1)
115-153: get_devices() may be inefficient due to opening each device.The implementation creates a temporary
Providerfor each discovered device (line 138) to retrieve detailed information. If devices are slow to open or if there are many devices, this could cause noticeable delays.Consider adding a lightweight device enumeration method that doesn't require opening each device, or at minimum document the performance characteristics:
📝 Suggested documentation addition
/// Get available camera devices + /// + /// Note: This method opens each device to retrieve detailed information, + /// which may be slow if there are many devices. For a faster name-only + /// enumeration, use `list_devices()`. pub fn get_devices() -> Result<Vec<DeviceInfo>> {
📜 Review details
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (1)
bindings/rust/Cargo.lockis excluded by!**/*.lock
📒 Files selected for processing (8)
bindings/rust/Cargo.tomlbindings/rust/README.mdbindings/rust/RUST_INTEGRATION_SUMMARY.mdbindings/rust/build_and_test.shbindings/rust/src/lib.rsbindings/rust/src/provider.rssrc/ccap_imp_windows.cppsrc/ccap_imp_windows.h
✅ Files skipped from review due to trivial changes (2)
- src/ccap_imp_windows.h
- src/ccap_imp_windows.cpp
🚧 Files skipped from review as they are similar to previous changes (1)
- bindings/rust/Cargo.toml
🧰 Additional context used
🧬 Code graph analysis (1)
bindings/rust/src/lib.rs (2)
include/ccap_utils.h (1)
LogLevel(80-140)bindings/rust/src/provider.rs (1)
version(337-348)
🪛 LanguageTool
bindings/rust/README.md
[style] ~211-~211: Consider using a less common alternative to make your writing sound more unique and professional.
Context: ...ontributing Contributions are welcome! Please feel free to submit a Pull Request. ## License Thi...
(FEEL_FREE_TO_STYLE_ME)
🪛 markdownlint-cli2 (0.18.1)
bindings/rust/README.md
34-34: Bare URL used
(MD034, no-bare-urls)
98-98: Unordered list indentation
Expected: 2; Actual: 4
(MD007, ul-indent)
99-99: Unordered list indentation
Expected: 2; Actual: 4
(MD007, ul-indent)
101-101: Unordered list indentation
Expected: 2; Actual: 4
(MD007, ul-indent)
102-102: Unordered list indentation
Expected: 2; Actual: 4
(MD007, ul-indent)
146-146: Unordered list indentation
Expected: 2; Actual: 4
(MD007, ul-indent)
152-152: Unordered list indentation
Expected: 2; Actual: 4
(MD007, ul-indent)
157-157: Unordered list indentation
Expected: 2; Actual: 4
(MD007, ul-indent)
158-158: Unordered list indentation
Expected: 2; Actual: 4
(MD007, ul-indent)
159-159: Unordered list indentation
Expected: 2; Actual: 4
(MD007, ul-indent)
bindings/rust/RUST_INTEGRATION_SUMMARY.md
29-29: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (20)
- GitHub Check: Build (Release-gcc-static)
- GitHub Check: Build Fedora (Debug-shared)
- GitHub Check: Build Fedora (Release-shared)
- GitHub Check: Build (Debug-gcc-shared)
- GitHub Check: Build (Release-gcc-shared)
- GitHub Check: Build Fedora (Release-static)
- GitHub Check: Build Fedora (Debug-static)
- GitHub Check: Build ARM64 (Release-gcc-shared)
- GitHub Check: Build ARM64 (Debug-gcc-shared)
- GitHub Check: Build ARM64 (Debug-gcc-static)
- GitHub Check: Build ARM64 (Release-gcc-static)
- GitHub Check: Windows Build MinGW (Debug-static)
- GitHub Check: Windows Build VS2022 (Release-static)
- GitHub Check: Windows Build MinGW (Debug-shared)
- GitHub Check: Windows Build MinGW (Release-static)
- GitHub Check: Windows Build MinGW (Release-shared)
- GitHub Check: Build Source (Dist) (windows-latest)
- GitHub Check: macOS Build (Debug-static)
- GitHub Check: macOS Build (Release-static)
- GitHub Check: Windows Build VS2022 (Release-shared)
🔇 Additional comments (12)
bindings/rust/build_and_test.sh (6)
42-55: LGTM!The parsing logic correctly handles both the success case (extracting device count from "Found N camera") and the no-devices case, with a sentinel value of -1 for unparseable output.
57-70: LGTM!The Rust output parser correctly mirrors the CLI parser's approach, handling success, no-devices, and unparseable cases consistently.
72-82: LGTM!The path setup uses the standard
BASH_SOURCEidiom and correctly derives the project root and Rust directory paths.
117-120: LGTM!Test execution is straightforward and appropriate.
122-125: LGTM!Building examples ensures they compile correctly.
127-193: Well-structured device discovery validation.The comparison logic is thorough and correctly handles multiple edge cases:
- CLI failure
- Unparseable output
- Headless environments (no cameras)
- Count mismatches
The use of
set +e/set -earound discovery commands to capture exit codes while preserving strict error handling is appropriate.bindings/rust/README.md (1)
1-220: Documentation is comprehensive and well-structured.The README provides excellent coverage of features, installation, usage examples, build modes, platform support, and API documentation. The thread safety warnings are particularly valuable.
bindings/rust/RUST_INTEGRATION_SUMMARY.md (1)
1-178: Implementation summary is clear and well-organized.The document effectively communicates the three-step implementation approach, project structure, core features, and test results.
bindings/rust/src/provider.rs (3)
511-564: LGTM: Frame callback implementation is memory-safe.The double-boxing pattern correctly ensures a stable thin pointer for the C FFI callback, and cleanup is properly handled on both success and failure paths.
595-607: LGTM: Drop implementation correctly cleans up resources.The Drop implementation properly cleans up the frame callback before destroying the C handle, preventing resource leaks.
Note: The global error callback is intentionally not cleaned up here (it's global and may be used by other instances), which is consistent with the global design.
49-63: Send implementation for Provider is correctly justified.The
unsafe impl Send for Provideris sound. Verification of the C++ implementation confirms:
- No thread-local storage exists in the Provider class (thread-local allocators in
ccap_convert.cppare separate conversion utilities)- No thread ID caching found
- Move semantics are properly supported with no thread-specific state
The safety documentation accurately reflects the actual guarantees: moving the Provider between threads is safe because
Sendonly permits ownership transfer, not concurrent access. The C++ documentation correctly warns that concurrent access requires external synchronization, which is a separate concern from move safety.bindings/rust/src/lib.rs (1)
1-37: LGTM: Clean and idiomatic crate structure.The library organization is well-designed:
- Clear separation between low-level FFI (
sys) and high-level API- Appropriate lint configuration with relaxed rules for FFI bindings
- Comprehensive re-exports for ergonomic usage
- Good use of the OUT_DIR pattern for generated bindings
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
🤖 Fix all issues with AI agents
In @bindings/rust/build.rs:
- Around line 389-423: The build script only registers core sources for
cargo:rerun-if-changed but omits platform- and SIMD-specific sources, so changes
to files like ccap_imp_apple.mm, ccap_convert_apple.cpp, ccap_imp_linux.cpp,
ccap_imp_windows.cpp, ccap_convert_avx2.cpp, and ccap_convert_neon.cpp won't
trigger rebuilds; fix by adding conditional blocks that, when build_from_source
is true, emit println!("cargo:rerun-if-changed={}/src/<file>",
ccap_root.display()) for each platform-specific and SIMD file using
cfg(target_os = "...") and cfg(any(target_arch = "...")) guards, mirroring the
existing pattern around build_from_source found in build.rs so all
platform-specific sources are tracked.
🧹 Nitpick comments (2)
bindings/rust/build.rs (2)
99-99: Consider removing the unused_is_packagedbinding.The variable is assigned but never used in the code. If it's not needed for future logic, remove it to improve clarity.
♻️ Proposed fix
- let (ccap_root, _is_packaged) = if build_from_source { + let ccap_root = if build_from_source { // 1) Vendored sources under ./native (ideal for crates.io) if manifest_path.join("native").exists() { - (manifest_path.join("native"), true) + manifest_path.join("native") } else if let Some(root) = find_ccap_root_from(&manifest_path) { // 2) Search parent dirs for CameraCapture repo root (works for git checkout // and for `cargo publish --dry-run` which builds from target/package) - (root, false) + root } else if let Ok(root) = env::var("CCAP_SOURCE_DIR") { // 3) Allow override via CCAP_SOURCE_DIR let root = PathBuf::from(root); if looks_like_ccap_root(&root) { - (root, false) + root } else { panic!( "CCAP_SOURCE_DIR is set but does not look like CameraCapture root: {}", root.display() ); } } else { // Keep a placeholder; if build-source is enabled we'll error with a clear message. - (manifest_path.clone(), false) + manifest_path.clone() } } else { // Dev/static-link mode: even if ./native exists, we still prefer the repo root so we can // link against pre-built build/Debug|Release artifacts. if let Some(root) = find_ccap_root_from(&manifest_path) { - (root, false) + root } else if let Ok(root) = env::var("CCAP_SOURCE_DIR") { let root = PathBuf::from(root); if looks_like_ccap_root(&root) { - (root, false) + root } else { panic!( "CCAP_SOURCE_DIR is set but does not look like CameraCapture root: {}", root.display() ); } } else if static_link { panic!( "static-link feature is enabled, but CameraCapture repo root was not found.\n\ \ Tried (in order):\n\ - searching parent directories for include/ccap_c.h and src/ccap_core.cpp\n\ - CCAP_SOURCE_DIR environment variable\n\ \ Please set CCAP_SOURCE_DIR to a CameraCapture checkout (with build/Debug|Release built)." ); } else { // Fallback placeholder. - (manifest_path.clone(), false) + manifest_path.clone() } };
153-263: LGTM! Consider adding source file validation.The build-from-source logic correctly handles platform-specific sources, SIMD variants, and compiler flags. The NEON file is always compiled for symbol availability, which is correct.
One optional improvement: you could validate that critical source files exist before calling
build.compile()to provide clearer error messages if files are missing.♻️ Optional defensive validation
Before line 168, you could add:
// Validate critical source files exist let required_sources = [ "src/ccap_core.cpp", "src/ccap_utils.cpp", "src/ccap_convert.cpp", ]; for src in &required_sources { if !ccap_root.join(src).exists() { panic!("Required source file not found: {}", ccap_root.join(src).display()); } }
📜 Review details
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
bindings/rust/build.rs
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
- GitHub Check: Windows Build MinGW (Debug-static)
- GitHub Check: Windows Build MinGW (Release-shared)
- GitHub Check: Windows Build MinGW (Debug-shared)
🔇 Additional comments (7)
bindings/rust/build.rs (7)
6-14: LGTM!The byte-pattern search logic is correct and efficiently uses a sliding window approach.
16-54: LGTM!The clang resource directory discovery logic correctly prioritizes PATH clang and falls back to xcrun on macOS.
56-73: LGTM!The root detection logic is sound and the 16-level parent traversal accommodates deep packaging structures like
target/package/<crate>-<ver>.
274-318: LGTM! ASan detection and runtime linking is well-implemented.The logic correctly:
- Detects ASan instrumentation in prebuilt archives by scanning for
___asan_init(macOS Mach-O) and__asan_init(Linux ELF).- Links the ASan runtime explicitly, which is necessary since rustc uses
-nodefaultlibs.- Finds and adds rpath for the clang ASan runtime on macOS.
- Provides an opt-out mechanism via
CCAP_RUST_NO_ASAN_LINK.
345-373: LGTM!Platform-specific linking configuration is correct for all supported platforms (macOS frameworks, Windows Media Foundation, Linux C++ stdlib).
425-445: LGTM!The bindgen configuration correctly generates FFI bindings with appropriate type derivations and symbol allowlisting.
336-340: Clarify the source of MSVC library naming behavior.The comment states "we always link to the Release version (ccap.lib)" but this behavior is actually enforced by CMake's
DEBUG_POSTFIX "d"configuration (CMakeLists.txt lines 174-180), not by the Rust code itself. The unconditionalcargo:rustc-link-lib=static=ccaprequest happens to resolve to the Release version only because CMake names debug builds with a "d" suffix.Either add a comment explaining that CMake handles this, or remove the misleading note if the linking behavior is already correct as-is.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 4
🤖 Fix all issues with AI agents
In @bindings/rust/build_and_test.sh:
- Around line 7-24: The jobs_count function can return without printing if a
detected command (e.g., sysctl) fails silently; update the function so each
detection captures the command output into a variable and only echo+return when
that variable is non-empty (for example, replace the sysctl branch with
capturing sysctl -n hw.ncpu into a var and if it is non-empty then echo it and
return); apply the same safe-capture pattern to nproc and getconf branches so
the function never returns without producing a value, falling back to echo 4
only if all attempts yield no output.
In @scripts/test_remote_crate.sh:
- Around line 367-372: The current build step redirects stderr into stdout
(`cargo build --quiet 2>&1`), which can swallow useful error output; update the
`log`/`error` block so failures surface the build output: either remove the
stderr redirection so Cargo prints errors to the console, or capture the command
output into a variable when running the build and, on failure, call the existing
`error` helper with that captured output (e.g., capture output from `cargo build
--quiet` and pass it into `error` before returning 1). Ensure you update the
invocation around the `cargo build` call in this script to include the captured
output in the `error` message.
- Around line 51-63: The version-extraction regex used when VERSION=="latest"
doesn't capture semver build metadata; update the grep pattern used to populate
latest_version (the command assigning latest_version via cargo search ... | grep
-oE '...') to allow an optional build metadata suffix by replacing
`[0-9]+\.[0-9]+\.[0-9]+(-[a-zA-Z0-9.]+)?` with a pattern like
`[0-9]+\.[0-9]+\.[0-9]+(-[0-9A-Za-z.]+)?(\+[0-9A-Za-z.]+)?` so both pre-release
and +build metadata (e.g., 1.0.0+build.123) are matched; keep the rest of the
logic setting VERSION and logging intact.
🧹 Nitpick comments (5)
bindings/rust/README.md (2)
29-34: Consider providing a concrete version example instead of<latest>placeholder.The
<latest>placeholder requires users to look up the current version. Consider providing a concrete example version or a command to fetch the latest version automatically.Suggested improvement
[dependencies] -ccap = { package = "ccap-rs", version = "<latest>" } +ccap = { package = "ccap-rs", version = "1.0" } # Check crates.io for latestOr add a script-based approach:
# Get current version and add to Cargo.toml cargo add ccap-rs --rename ccap
200-203: Minor style suggestion.Per static analysis, "Please feel free to submit" could be simplified.
Optional wording improvement
-Contributions are welcome! Please feel free to submit a Pull Request. +Contributions are welcome! Submit a Pull Request to get started.bindings/rust/build_and_test.sh (2)
26-65: Redundantcdafterpopdoperations.Line 63 uses
cd "$original_dir"but thepushd/popdpairs (lines 42-52) should already restore the working directory. Thiscdis redundant but harmless.Optional cleanup
- cd "$original_dir" + # pushd/popd already restored the directory; this cd is optional echo "$cli_bin"
134-136:cargo cleanbefore every build may slow iterative development.Running
cargo cleanunconditionally clears the entire target directory, forcing a full rebuild every time. This is appropriate for CI but may be slow for local development.Consider making clean optional
-# Clean previous build -cargo clean +# Clean previous build (optional: set CCAP_SKIP_CLEAN=1 to skip) +if [[ "${CCAP_SKIP_CLEAN:-0}" != "1" ]]; then + cargo clean +fibindings/rust/src/provider.rs (1)
345-349: Minor inconsistency:stop_capturedoesn't checkis_opened.Unlike
start_capture()(line 333-334), this function doesn't verify the device is opened before calling the C API. While probably safe (likely a no-op), adding a check would make the API more consistent and predictable.♻️ Suggested consistency improvement
pub fn stop_capture(&mut self) -> Result<()> { + if !self.is_opened { + return Err(CcapError::DeviceNotOpened); + } unsafe { sys::ccap_provider_stop(self.handle) }; Ok(()) }
📜 Review details
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (5)
README.mdbindings/rust/README.mdbindings/rust/build_and_test.shbindings/rust/src/provider.rsscripts/test_remote_crate.sh
🧰 Additional context used
🪛 LanguageTool
bindings/rust/README.md
[style] ~202-~202: Consider using a less common alternative to make your writing sound more unique and professional.
Context: ...ontributing Contributions are welcome! Please feel free to submit a Pull Request. ## License Thi...
(FEEL_FREE_TO_STYLE_ME)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (20)
- GitHub Check: Windows Build VS2022 (Debug-static)
- GitHub Check: Windows Build MinGW (Debug-shared)
- GitHub Check: Windows Build VS2022 (Debug-shared)
- GitHub Check: Build Fedora (Debug-static)
- GitHub Check: Static Link (Dev) (ubuntu-latest)
- GitHub Check: Static Link (Dev) (macos-latest)
- GitHub Check: Static Link (Dev) (windows-latest)
- GitHub Check: Build Source (Dist) (ubuntu-latest)
- GitHub Check: Build Source (Dist) (windows-latest)
- GitHub Check: macOS Release - No File Playback
- GitHub Check: Build Fedora (Release-static)
- GitHub Check: Build Fedora (Release-shared)
- GitHub Check: macOS Build (Release-shared)
- GitHub Check: Build (Release-clang-shared)
- GitHub Check: Build (Release-clang-static)
- GitHub Check: Build (Debug-clang-shared)
- GitHub Check: Build ARM64 (Release-gcc-shared)
- GitHub Check: Build (Release-gcc-shared)
- GitHub Check: Build ARM64 (Release-gcc-static)
- GitHub Check: Build (Release-gcc-static)
🔇 Additional comments (21)
bindings/rust/README.md (3)
1-16: LGTM - Well-structured README header.The badges, feature list, and project description provide a clear overview of the Rust bindings. The features accurately reflect the capabilities documented in the PR objectives.
36-72: LGTM - Clear and practical usage example.The basic usage example demonstrates the core workflow (device discovery, opening, frame capture) with proper error handling using the
Resulttype.
176-184: Good thread safety documentation with appropriate caveats.The thread safety section clearly documents that
VideoFrameisSend,ProviderisSendbut not thread-safe, and includes a practical recommendation for multi-threaded access. The note about C++ layer thread-safety being based on code inspection is appropriately cautious.README.md (3)
6-6: LGTM - Rust CI badge addition.The Rust CI badge is correctly placed alongside the other build badges, providing visibility into the Rust bindings build status.
211-231: LGTM - Well-integrated Rust Bindings section.The new section is appropriately placed after the Pure C Interface section and provides clear quick-install instructions with links to crates.io and docs.rs.
Note: Same suggestion as in
bindings/rust/README.mdapplies here—consider using a concrete version example instead of<latest>.
14-14: LGTM - Updated project description.The description now accurately reflects the availability of Rust bindings alongside C++ and C interfaces.
bindings/rust/build_and_test.sh (2)
178-217: LGTM - Comprehensive device discovery validation logic.The decision logic handles various edge cases well:
- CLI discovery failure
- Parse failures for both CLI and Rust outputs
- Rust failure when CLI sees devices
- Headless environment detection (no cameras)
- Device count mismatch detection
This provides good validation that the Rust bindings correctly discover devices.
67-95: LGTM - Robust output parsing functions.The regex patterns correctly handle the expected output formats:
- CLI:
Found N camera...orNo camera devices found- Rust:
## Found N video capture device...orFailed to find any video capture deviceReturning
-1for unparseable output allows the caller to distinguish between "zero devices" and "parse failure".scripts/test_remote_crate.sh (4)
1-46: LGTM - Well-structured script initialization.Good practices:
set -euo pipefailfor strict error handling- Clear logging functions with color coding
- Cleanup function defined (intentionally disabled to preserve test artifacts)
81-110: Embedded Python script for Cargo.toml manipulation is clever but complex.The Python script correctly handles:
- Finding and modifying the
[dependencies]section- Replacing existing
ccap-rsentries- Creating
[dependencies]section if missingThis is a reasonable approach given the complexity of TOML manipulation in pure Bash.
141-365: LGTM - Comprehensive test code generation.The generated Rust test code covers:
- Provider creation and device listing
- Camera capture with frame saving (when devices available)
- Pixel format conversion (NV12 → RGB24/BGR24) with size validation
- PixelFormat enum variant availability
- Video file playback (Windows/macOS only, with
#[cfg]guard)Error handling uses early exits appropriately for critical failures while allowing non-critical operations to continue with warnings.
246-258: Test data dimensions are hardcoded and minimal.The 16x16 test image is sufficient for basic conversion validation. The comment on line 256 correctly notes that stride equals width (no padding), which is important for the conversion functions.
bindings/rust/src/provider.rs (9)
8-20: LGTM: Safe global callback storage pattern.The
SendSyncPtrwrapper with safety justification andMutex-protected global storage correctly implements thread-safe callback management.
170-186: LGTM: Proper bounds checking.The bounds checks on lines 172 and 179 before accessing
supportedPixelFormatsandsupportedResolutionsarrays prevent out-of-bounds access.
290-305: LGTM: Excellent atomicity handling with rollback.The resolution setter prevents partial updates by saving the old values and performing best-effort rollback if the height update fails. This is good defensive programming that maintains device state consistency.
214-223: LGTM: Proper cleanup order before handle destruction.Lines 217-219 correctly stop capture and remove callbacks before destroying the C API handle, preventing use-after-free errors.
549-631: LGTM: Correct callback memory management.The double-boxing pattern (lines 578-580), cleanup on failure (lines 595-597), and consistent type aliases ensure memory safety. The
cleanup_callback()helper properly reconstructs and drops the boxed callback.
477-492: LGTM: Defensive cleanup in error callback clearing.Lines 483-485 correctly clear the C-side callback even when no Rust callback is stored in
GLOBAL_ERROR_CALLBACK. This defensive approach handles edge cases where the state might be inconsistent.
633-645: LGTM: Proper Drop cleanup order.Cleaning up the callback (line 636) before destroying the handle (lines 638-643) prevents potential use-after-free if the C API invokes callbacks during handle destruction.
22-63: Documentation correctly warns about thread-safety limitations.The extensive documentation (lines 22-48) and safety comments (lines 55-62) clearly state that while
ProviderisSend, the underlying C++ is NOT thread-safe and requires external synchronization.Potential footgun: Users might assume
Sendimplies safety for concurrent access. Consider adding a compile-time assertion or runtime check in debug builds to detect concurrent access, though this may not be feasible.As per coding guidelines or learnings, does the project have a pattern for detecting or preventing concurrent access to non-thread-safe
Sendtypes?
80-113: Replace "likely" with definitive language in comments.Lines 92 and 110 state "C API likely opens device automatically"—this is unnecessarily uncertain. The C API constructors (
ccap_provider_create_with_deviceandccap_provider_create_with_index) invoke the C++Providerconstructors which explicitly callopen()internally, confirming the assumption is correct. Update the comments to remove "likely" and state the actual behavior definitively (e.g., "C API opens device automatically").
| pub fn open_with_index(&mut self, device_index: i32, auto_start: bool) -> Result<()> { | ||
| // Destroy old handle if exists | ||
| if !self.handle.is_null() { | ||
| unsafe { | ||
| sys::ccap_provider_destroy(self.handle); | ||
| } | ||
| } | ||
|
|
||
| // Clean up old callback if exists | ||
| self.cleanup_callback(); | ||
|
|
||
| // Create a new provider with the specified device index | ||
| self.handle = unsafe { sys::ccap_provider_create_with_index(device_index, ptr::null()) }; | ||
|
|
||
| if self.handle.is_null() { | ||
| return Err(CcapError::InvalidDevice(format!( | ||
| "device index {}", | ||
| device_index | ||
| ))); | ||
| } | ||
|
|
||
| self.is_opened = false; | ||
| self.open()?; | ||
| if auto_start { | ||
| self.start_capture()?; | ||
| } | ||
| Ok(()) | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# First, find the with_device implementation to understand the pattern
rg -n "fn with_device" bindings/rust/src/ -A 20Repository: wysaid/CameraCapture
Length of output: 2711
🏁 Script executed:
#!/bin/bash
# Search for all uses of ccap_provider_create_with_index and ccap_provider_open_by_index
rg -n "ccap_provider_create_with_index|ccap_provider_open_by_index" bindings/rust/src/Repository: wysaid/CameraCapture
Length of output: 449
🏁 Script executed:
#!/bin/bash
# Look for any documentation or comments about the C API
fd -e c -e h . | xargs rg -l "ccap_provider_create_with_index|ccap_provider_open_by_index" 2>/dev/null | head -5Repository: wysaid/CameraCapture
Length of output: 236
🏁 Script executed:
#!/bin/bash
# Check if there are any tests or examples
fd -e rs . bindings/rust | xargs rg -l "open_with_index\|with_device" 2>/dev/nullRepository: wysaid/CameraCapture
Length of output: 48
🏁 Script executed:
#!/bin/bash
# Examine the C API header to understand function semantics
cat -n include/ccap_c.h | grep -A 10 -B 2 "ccap_provider_create_with_index\|ccap_provider_open_by_index"Repository: wysaid/CameraCapture
Length of output: 1091
🏁 Script executed:
#!/bin/bash
# Check C examples to see the correct usage pattern
cat -n examples/desktop/1-minimal_example_c.cRepository: wysaid/CameraCapture
Length of output: 3053
🏁 Script executed:
#!/bin/bash
# Also check another C example
cat -n examples/desktop/2-capture_grab_c.c | head -100Repository: wysaid/CameraCapture
Length of output: 4052
🏁 Script executed:
#!/bin/bash
# Examine the open() method to understand the -1 device index
sed -n '195,210p' bindings/rust/src/provider.rsRepository: wysaid/CameraCapture
Length of output: 492
🏁 Script executed:
#!/bin/bash
# Get more context around the open() method
sed -n '185,215p' bindings/rust/src/provider.rsRepository: wysaid/CameraCapture
Length of output: 988
🏁 Script executed:
#!/bin/bash
# Check C API documentation for open_by_index parameter meaning
rg -A 20 "ccap_provider_open_by_index" include/ccap_c.hRepository: wysaid/CameraCapture
Length of output: 852
🏁 Script executed:
#!/bin/bash
# Search for usages of with_device to see if it works or fails
rg -n "with_device" bindings/rust/src/ -B 2 -A 5Repository: wysaid/CameraCapture
Length of output: 2979
🏁 Script executed:
#!/bin/bash
# Check if there are any Rust tests or examples
find . -name "*.rs" -path "*/examples/*" -o -name "*test*.rs" | head -10Repository: wysaid/CameraCapture
Length of output: 277
🏁 Script executed:
#!/bin/bash
# Look for any C implementation or documentation about -1 deviceIndex
rg -B 5 -A 15 "deviceIndex.*-1|== -1" include/ examples/ --type=cRepository: wysaid/CameraCapture
Length of output: 18118
🏁 Script executed:
#!/bin/bash
# Check Rust integration tests
head -200 bindings/rust/tests/integration_tests.rsRepository: wysaid/CameraCapture
Length of output: 2658
🏁 Script executed:
#!/bin/bash
# Check minimal example
cat -n bindings/rust/examples/minimal_example.rs | head -150Repository: wysaid/CameraCapture
Length of output: 2326
🏁 Script executed:
#!/bin/bash
# Check the start() and is_started() implementation
rg -n "fn start\|fn is_started" bindings/rust/src/provider.rs -A 10Repository: wysaid/CameraCapture
Length of output: 46
🏁 Script executed:
#!/bin/bash
# Check if there's any documentation about the expected flow
rg -B 5 -A 10 "with_device|open_with_index" bindings/rust/src/provider.rs | head -150Repository: wysaid/CameraCapture
Length of output: 3114
🏁 Script executed:
#!/bin/bash
# Search for all calls to start() to understand the complete flow
rg -n "fn start\|fn start_capture" bindings/rust/src/provider.rs -A 8Repository: wysaid/CameraCapture
Length of output: 46
🏁 Script executed:
#!/bin/bash
# Check if is_opened affects start behavior
rg -n "is_opened\|is_started" bindings/rust/src/provider.rs -B 2 -A 5Repository: wysaid/CameraCapture
Length of output: 46
🏁 Script executed:
#!/bin/bash
# Read the full provider.rs to understand all methods
wc -l bindings/rust/src/provider.rsRepository: wysaid/CameraCapture
Length of output: 97
🏁 Script executed:
#!/bin/bash
# Get a broader view of method names
grep -n "pub fn" bindings/rust/src/provider.rs | head -40Repository: wysaid/CameraCapture
Length of output: 2052
🏁 Script executed:
#!/bin/bash
# Look for the actual implementation of start_capture
sed -n '400,600p' bindings/rust/src/provider.rsRepository: wysaid/CameraCapture
Length of output: 7010
🏁 Script executed:
#!/bin/bash
# Look at start_capture and open implementations
sed -n '196,210p' bindings/rust/src/provider.rsRepository: wysaid/CameraCapture
Length of output: 461
🏁 Script executed:
#!/bin/bash
# Look at start_capture implementation
sed -n '332,346p' bindings/rust/src/provider.rsRepository: wysaid/CameraCapture
Length of output: 470
🏁 Script executed:
#!/bin/bash
# Also check start() method
sed -n '253,262p' bindings/rust/src/provider.rsRepository: wysaid/CameraCapture
Length of output: 307
🏁 Script executed:
#!/bin/bash
# Let me verify the actual test to see if with_device actually works
grep -A 30 "test_provider_with_index" bindings/rust/tests/integration_tests.rsRepository: wysaid/CameraCapture
Length of output: 1154
🏁 Script executed:
#!/bin/bash
# Check if there are any other usages of with_device in the codebase
rg -n "\.with_device\(" bindings/rust/ -B 2 -A 5Repository: wysaid/CameraCapture
Length of output: 46
Critical inconsistency in device opening logic—with_device() and with_device_name() incorrectly assume the device is opened automatically.
The issue extends beyond open_with_index(). Two methods have the same bug:
with_device() (line 81-92):
- Creates provider with
ccap_provider_create_with_index - Sets
is_opened = truewith comment "C API likely opens device automatically" - Does NOT call
open()
with_device_name() (line 98-112):
- Creates provider with
ccap_provider_create_with_device - Sets
is_opened = truewith same incorrect assumption - Does NOT call
open()
open_with_index() (line 502-529):
- Creates provider with
ccap_provider_create_with_index - Sets
is_opened = false - Calls
open()which invokesccap_provider_open_by_index(self.handle, -1, false)
Evidence of the bug:
The minimal example (bindings/rust/examples/minimal_example.rs, lines 23-24) explicitly calls open() after with_device():
let mut provider = Provider::with_device(camera_index_i32)?;
provider.open()?;This proves the C API does not automatically open the device on creation. Both with_device() and with_device_name() falsely claim is_opened = true without actually opening the device.
Consequences:
- Code relying only on
with_device()without callingopen()has incorrect internal state start_capture()checksis_openedbut the device may not be truly opened- The inconsistency creates a semantic bug where the API claims success but the device is not operational
Fix both with_device() and with_device_name() to set is_opened = false and document that open() must be called explicitly, or redesign them to call open() internally.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
🤖 Fix all issues with AI agents
In @docs/content/rust-bindings.zh.md:
- Around line 5-6: Lines containing "Crate:" and "API 文档:" use bare URLs;
replace each bare URL with a markdown link (e.g., change
'Crate:https://crates.io/crates/ccap-rs' to 'Crate:[ccap-rs on
crates.io](https://crates.io/crates/ccap-rs)' and 'API
文档:https://docs.rs/ccap-rs' to 'API 文档:[ccap-rs API
文档](https://docs.rs/ccap-rs)') so the URLs are rendered as proper markdown
links.
📜 Review details
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (2)
docs/content/rust-bindings.mddocs/content/rust-bindings.zh.md
🚧 Files skipped from review as they are similar to previous changes (1)
- docs/content/rust-bindings.md
🧰 Additional context used
📓 Path-based instructions (1)
docs/**/*.md
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
.mdfiles indocs/must be English and require review before publishing
Files:
docs/content/rust-bindings.zh.md
🪛 markdownlint-cli2 (0.18.1)
docs/content/rust-bindings.zh.md
5-5: Bare URL used
(MD034, no-bare-urls)
6-6: Bare URL used
(MD034, no-bare-urls)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
- GitHub Check: Build (Release-clang-static)
- GitHub Check: Build (Release-clang-shared)
- GitHub Check: Windows Build MinGW (Release-shared)
🔇 Additional comments (1)
docs/content/rust-bindings.zh.md (1)
1-89: No action required — this file follows the established project localization pattern.The repository intentionally maintains parallel Chinese (
.zh.md) versions of all documentation files indocs/content/. This is supported by the documentation system itself (docs/documentation.htmlcontains explicit language routing logic to serve.zh.mdfiles for Chinese language selection), and all.zh.mdvariants are actively cross-linked and referenced throughout the codebase, including inREADME.zh-CN.md. The file is correctly placed and follows the project's documented localization strategy.Likely an incorrect or invalid review comment.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 3
🤖 Fix all issues with AI agents
In @bindings/rust/build.rs:
- Around line 466-473: The rerun trigger for ccap_convert_avx2.cpp is
conditionally emitted behind #[cfg(any(target_arch = "x86", target_arch =
"x86_64"))] while the AVX2 source is compiled unconditionally for hasAVX2()
symbols; fix by emitting the
println!("cargo:rerun-if-changed={}/src/ccap_convert_avx2.cpp",
ccap_root.display()) unconditionally in build.rs (or move it into the same
unconditional block that builds the AVX2 file) so changes to
ccap_convert_avx2.cpp always cause a rebuild.
In @bindings/rust/src/frame.rs:
- Around line 140-151: The data() method currently builds a &[u8] from a raw C
pointer tied to a temporary CcapVideoFrameInfo, which is unsafe because the
slice may outlive the underlying frame memory; change the API to return an owned
Vec<u8> (e.g., pub fn data(&self) -> crate::error::Result<Vec<u8>>) and inside
data() copy the bytes from the raw pointer into a Vec (create the slice
from_raw_parts then .to_vec()) before returning; update callers to accept
Vec<u8> and keep the same error handling (return Err(CcapError::FrameGrabFailed)
on failure).
- Around line 86-138: The info() and data() methods expose slices that can
outlive or point to driver-owned hardware buffers that may be reused, creating
dangling-pointer/undefined-behavior risks; fix by making frame data owned or
explicitly detached: update VideoFrameInfo (and VideoFrame::data) so the
returned buffers are owned copies (Vec<u8> or owned container) created inside
info() by copying from info.data[i] with the computed plane sizes (plane0_size,
plane1_size, plane2_size), change VideoFrameInfo.data_planes to hold owned
Vec<u8> (or Option<Vec<u8>>) instead of borrowed &[u8], and adjust data() to
return references tied to &self or return owned data as well; alternatively, if
you prefer zero-copy, expose and bind the C++ detach() method to Rust and call
it before creating slices—use the function names info(), data(), VideoFrameInfo,
and the C FFI call sys::ccap_video_frame_get_info and add a binding for detach
to ensure safety.
🧹 Nitpick comments (2)
bindings/rust/build.rs (1)
155-164: Remove trailing whitespace in panic message.Line 163 has a trailing space in the panic message string literal.
✨ Suggested fix
"build-source feature is enabled, but CameraCapture sources were not found.\n\ \ Tried (in order):\n\ - ./native (vendored) under the crate root\n\ - searching parent directories for include/ccap_c.h and src/ccap_core.cpp\n\ - CCAP_SOURCE_DIR environment variable\n\ \ -Please vendor the sources into bindings/rust/native/, or set CCAP_SOURCE_DIR to a CameraCapture checkout." +Please vendor the sources into bindings/rust/native/, or set CCAP_SOURCE_DIR to a CameraCapture checkout." );bindings/rust/src/frame.rs (1)
153-178: Consider returning Result from convenience methods.The convenience methods (
width(),height(), etc.) silently return default values (0, Unknown) wheninfo()fails, potentially hiding issues like null frame pointers or invalid state. While convenient for some use cases, this could make debugging harder.Consider either:
- Returning
Result<u32>,Result<PixelFormat>, etc., to surface errors- Adding
try_width(),try_height()variants that return Results- Documenting prominently that 0/Unknown indicates an error state
📜 Review details
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (3)
bindings/rust/build.rsbindings/rust/src/convert.rsbindings/rust/src/frame.rs
🧰 Additional context used
🧬 Code graph analysis (2)
bindings/rust/src/convert.rs (1)
bindings/rust/src/types.rs (2)
from_c_enum(62-64)from_c_enum(197-207)
bindings/rust/src/frame.rs (3)
include/ccap_def.h (1)
DeviceInfo(425-442)bindings/rust/src/types.rs (7)
from_c_enum(62-64)from_c_enum(197-207)from(35-52)from(87-103)from(116-126)from(154-167)from(220-225)bindings/rust/src/error.rs (1)
from(85-111)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (20)
- GitHub Check: Windows Build MinGW (Debug-static)
- GitHub Check: Windows Build VS2022 (Debug-static)
- GitHub Check: Windows Build MinGW (Release-shared)
- GitHub Check: Windows Build VS2022 (Debug-shared)
- GitHub Check: Windows Build MinGW (Debug-shared)
- GitHub Check: Windows Build VS2022 (Release-static)
- GitHub Check: Windows VS2022 Release - No File Playback
- GitHub Check: Static Link (Dev) (macos-latest)
- GitHub Check: Static Link (Dev) (windows-latest)
- GitHub Check: Static Link (Dev) (ubuntu-latest)
- GitHub Check: Build (Release-gcc-shared)
- GitHub Check: Build Fedora (Release-static)
- GitHub Check: Build ARM64 (Debug-gcc-shared)
- GitHub Check: Build ARM64 (Release-gcc-static)
- GitHub Check: Build (Debug-clang-static)
- GitHub Check: Build (Release-clang-static)
- GitHub Check: Build (Release-gcc-static)
- GitHub Check: Build (Release-clang-shared)
- GitHub Check: Build Source (Dist) (macos-latest)
- GitHub Check: Build Fedora (Release-shared)
🔇 Additional comments (13)
bindings/rust/build.rs (4)
6-14: LGTM: Well-defensive file pattern matching.The function correctly handles edge cases (read failure, empty needle) and uses an efficient sliding window approach.
284-323: ASan detection heuristic is pragmatic for dev builds.The byte-pattern matching for ASan symbols (
___asan_init/__asan_init) is a reasonable heuristic, though it could theoretically produce false positives/negatives if symbol names change across compiler versions. TheCCAP_RUST_NO_ASAN_LINKescape hatch provides users with a workaround if needed.
221-246: Excellent: Unconditional AVX2 compilation with conditional flags.The approach of always building the AVX2 object file but only adding SIMD flags on x86/x86_64 ensures that
hasAVX2()symbols are available on all platforms while avoiding compiler errors on non-x86 architectures. The MSVC vs non-MSVC flag handling is also correct.
483-495: Well-scoped bindgen configuration.The allowlist approach (
ccap_.*,Ccap.*,CCAP_.*) appropriately restricts bindings to only the camera capture API, avoiding unnecessary code generation. The derive directives enable useful traits.bindings/rust/src/convert.rs (5)
10-20: LGTM: Clear and informative validation error messages.The helper provides precise feedback including actual vs. required buffer sizes, which aids debugging.
387-461: Excellent: Comprehensive round-trip validation.The RGB↔BGR round-trip test with per-pixel verification (including coordinates in assertion messages) provides strong correctness guarantees and excellent debugging support.
463-499: Appropriate YUV conversion tolerance.The tolerance range (100-156 for neutral gray from Y=128, U=128, V=128) correctly accounts for YUV→RGB conversion rounding and different color space standards.
560-579: Good: Validation error path coverage.Testing that undersized buffers produce clear
InvalidParametererrors with "too small" messages confirms the error handling works as expected.
55-86: C conversion functions returnvoidand cannot report errors, so no return value checking is possible. The current approach of validating input buffer sizes before calling the C FFI is appropriate for this API design. No action needed.bindings/rust/src/frame.rs (4)
24-35: Excellent: Defensive bounds checking prevents buffer overruns.Using
.min()to clamp array accesses (lines 25, 31) protects against C API inconsistencies where count fields might exceed array bounds.
52-65: LGTM: Clear ownership semantics.The distinction between owned (
from_c_ptr) and borrowed (from_c_ptr_ref) frames is well-designed for handling both grabbed frames and callback scenarios. Theowns_frameflag ensures proper resource management.
181-189: LGTM: Correct conditional resource cleanup.The
Dropimplementation properly respects theowns_frameflag, ensuring callback-borrowed frames aren't double-freed while owned frames are properly released.
191-222: Theunsafe impl Send for VideoFrameis correctly implemented and verified. The C++ implementation confirms all safety assumptions:
- Independent frames:
ccap_provider_grab()creates a newstd::shared_ptr<VideoFrame>viastd::move(), ensuring independence from the Provider- Thread-safe reference counting:
std::shared_ptruses atomic operations for its control block, making it thread-safe across threads- Thread-safe release:
ccap_video_frame_release()simply deletes the shared_ptr, which is an atomic-safe operation- Immutable frame data: VideoFrame contains only immutable fields (pointers, integers, enum values, and a thread-safe shared_ptr for the allocator)
The inline documentation is accurate and well-reasoned. No changes needed.
| // SIMD-specific sources | ||
| #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] | ||
| { | ||
| println!( | ||
| "cargo:rerun-if-changed={}/src/ccap_convert_avx2.cpp", | ||
| ccap_root.display() | ||
| ); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Inconsistent rerun trigger for AVX2 source.
The AVX2 source file is unconditionally compiled (lines 221-246, comment states "Always build AVX2 file for hasAVX2() symbols"), but the rerun-if-changed directive is gated by #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]. This means changes to ccap_convert_avx2.cpp on non-x86 platforms won't trigger a rebuild even though the file is compiled.
🔧 Suggested fix
- // SIMD-specific sources
- #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
- {
- println!(
- "cargo:rerun-if-changed={}/src/ccap_convert_avx2.cpp",
- ccap_root.display()
- );
- }
+ // SIMD-specific sources
+ // AVX2 is always built (provides symbols on all platforms)
+ println!(
+ "cargo:rerun-if-changed={}/src/ccap_convert_avx2.cpp",
+ ccap_root.display()
+ );📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| // SIMD-specific sources | |
| #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] | |
| { | |
| println!( | |
| "cargo:rerun-if-changed={}/src/ccap_convert_avx2.cpp", | |
| ccap_root.display() | |
| ); | |
| } | |
| // SIMD-specific sources | |
| // AVX2 is always built (provides symbols on all platforms) | |
| println!( | |
| "cargo:rerun-if-changed={}/src/ccap_convert_avx2.cpp", | |
| ccap_root.display() | |
| ); |
🤖 Prompt for AI Agents
In @bindings/rust/build.rs around lines 466 - 473, The rerun trigger for
ccap_convert_avx2.cpp is conditionally emitted behind #[cfg(any(target_arch =
"x86", target_arch = "x86_64"))] while the AVX2 source is compiled
unconditionally for hasAVX2() symbols; fix by emitting the
println!("cargo:rerun-if-changed={}/src/ccap_convert_avx2.cpp",
ccap_root.display()) unconditionally in build.rs (or move it into the same
unconditional block that builds the AVX2 file) so changes to
ccap_convert_avx2.cpp always cause a rebuild.
| /// Get frame information | ||
| pub fn info<'a>(&'a self) -> crate::error::Result<VideoFrameInfo<'a>> { | ||
| let mut info = sys::CcapVideoFrameInfo::default(); | ||
|
|
||
| let success = unsafe { sys::ccap_video_frame_get_info(self.frame, &mut info) }; | ||
|
|
||
| if success { | ||
| // Calculate proper plane sizes based on pixel format | ||
| // For plane 0 (Y or main): stride * height | ||
| // For chroma planes (UV): stride * height/2 for most formats | ||
| let plane0_size = (info.stride[0] as usize) * (info.height as usize); | ||
| let plane1_size = if info.stride[1] > 0 { | ||
| (info.stride[1] as usize) * ((info.height as usize + 1) / 2) | ||
| } else { | ||
| 0 | ||
| }; | ||
| let plane2_size = if info.stride[2] > 0 { | ||
| (info.stride[2] as usize) * ((info.height as usize + 1) / 2) | ||
| } else { | ||
| 0 | ||
| }; | ||
|
|
||
| Ok(VideoFrameInfo { | ||
| width: info.width, | ||
| height: info.height, | ||
| pixel_format: PixelFormat::from(info.pixelFormat), | ||
| size_in_bytes: info.sizeInBytes, | ||
| timestamp: info.timestamp, | ||
| frame_index: info.frameIndex, | ||
| orientation: FrameOrientation::from(info.orientation), | ||
| data_planes: [ | ||
| if info.data[0].is_null() { | ||
| None | ||
| } else { | ||
| Some(unsafe { std::slice::from_raw_parts(info.data[0], plane0_size) }) | ||
| }, | ||
| if info.data[1].is_null() { | ||
| None | ||
| } else { | ||
| Some(unsafe { std::slice::from_raw_parts(info.data[1], plane1_size) }) | ||
| }, | ||
| if info.data[2].is_null() { | ||
| None | ||
| } else { | ||
| Some(unsafe { std::slice::from_raw_parts(info.data[2], plane2_size) }) | ||
| }, | ||
| ], | ||
| strides: [info.stride[0], info.stride[1], info.stride[2]], | ||
| }) | ||
| } else { | ||
| Err(CcapError::FrameGrabFailed) | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🏁 Script executed:
# Find C/C++ header files and implementations
find . -type f \( -name "*.h" -o -name "*.hpp" -o -name "*.c" -o -name "*.cpp" \) | head -20Repository: wysaid/CameraCapture
Length of output: 726
🏁 Script executed:
# Search for ccap_video_frame_get_info definition
rg -i "ccap_video_frame_get_info" --type-list | head -5Repository: wysaid/CameraCapture
Length of output: 453
🏁 Script executed:
# Search in common locations for header/source files
rg -n "ccap_video_frame_get_info" -A 20 2>/dev/null | head -100Repository: wysaid/CameraCapture
Length of output: 5645
🏁 Script executed:
# Look for CcapVideoFrameInfo struct definition
rg -n "CcapVideoFrameInfo" -B 2 -A 15 2>/dev/null | head -150Repository: wysaid/CameraCapture
Length of output: 8736
🏁 Script executed:
# Find the implementation of ccap_video_frame_get_info
rg -n "ccap_video_frame_get_info" --type cpp --type c -B 2 -A 20 2>/dev/null | head -200Repository: wysaid/CameraCapture
Length of output: 14619
🏁 Script executed:
# Look for CcapVideoFrame struct definition and implementation
rg -n "class VideoFrame" -A 30 --type cpp 2>/dev/null | head -150Repository: wysaid/CameraCapture
Length of output: 46
🏁 Script executed:
# Find header files with CcapVideoFrame or VideoFrame definitions
fd -e h -e hpp | xargs rg -l "CcapVideoFrame\|class VideoFrame" 2>/dev/nullRepository: wysaid/CameraCapture
Length of output: 48
🏁 Script executed:
# Check the include directory for full struct definitions
cat include/ccap_c.h | head -200Repository: wysaid/CameraCapture
Length of output: 8914
🏁 Script executed:
# Find VideoFrame class definition
rg -n "class VideoFrame" -A 50 --type cpp 2>/dev/null | head -200Repository: wysaid/CameraCapture
Length of output: 46
🏁 Script executed:
# Look for VideoFrame struct/class and data member declarations
rg -n "data\[3\]" -B 5 -A 2 --type cpp --type h 2>/dev/null | head -150Repository: wysaid/CameraCapture
Length of output: 1202
🏁 Script executed:
# Find the Drop/release implementation for VideoFrame in Rust bindings
rg -n "impl Drop" -A 10 bindings/rust/src/ 2>/dev/nullRepository: wysaid/CameraCapture
Length of output: 1318
🏁 Script executed:
# Check if there are any allocation/deallocation patterns in the C code
rg -n "ccap_video_frame_release" -B 5 -A 10 src/ccap_c.cpp 2>/dev/null | head -100Repository: wysaid/CameraCapture
Length of output: 554
🏁 Script executed:
# Find VideoFrame C++ class definition to understand data member management
rg -n "class VideoFrame" -A 80 include/ccap_def.h 2>/dev/null | head -200Repository: wysaid/CameraCapture
Length of output: 46
🏁 Script executed:
# Check how data is allocated/deallocated in VideoFrame
rg -n "data\[0\] =" -B 3 -A 3 src/ --type cpp 2>/dev/null | head -100Repository: wysaid/CameraCapture
Length of output: 6336
🏁 Script executed:
# Verify the data() method return type more carefully
cat bindings/rust/src/frame.rs | sed -n '140,152p'Repository: wysaid/CameraCapture
Length of output: 527
🏁 Script executed:
# Check if VideoFrame can be used concurrently or if frame data can be invalidated
rg -n "grab\|release" include/ccap_c.h -A 2 -B 2 2>/dev/null | grep -A 3 -B 3 "grab\|release"Repository: wysaid/CameraCapture
Length of output: 46
🏁 Script executed:
# Check the full VideoFrame class definition to understand lifetime guarantees
rg -n "struct VideoFrame\|class VideoFrame" -A 100 include/ccap_def.h 2>/dev/null | head -250Repository: wysaid/CameraCapture
Length of output: 46
🏁 Script executed:
# Verify the allocator class to understand data ownership
rg -n "class.*Allocator\|struct.*Allocator" -A 30 include/ --type h 2>/dev/null | head -150Repository: wysaid/CameraCapture
Length of output: 46
🏁 Script executed:
# Check if there are any threading or concurrent access concerns
rg -n "shared_ptr.*VideoFrame\|make_shared" src/ccap_c.cpp -B 3 -A 3 2>/dev/nullRepository: wysaid/CameraCapture
Length of output: 46
🏁 Script executed:
# Find VideoFrame definition with broader search
rg -n "VideoFrame" include/ccap_def.h -A 60 | head -300Repository: wysaid/CameraCapture
Length of output: 4703
🏁 Script executed:
# Check what allocator is used in VideoFrame
rg -n "allocator" include/ccap_def.h -B 2 -A 2 | head -100Repository: wysaid/CameraCapture
Length of output: 1594
🏁 Script executed:
# Check the frame.rs more carefully for the data() method issues
cat bindings/rust/src/frame.rs | sed -n '1,70p'Repository: wysaid/CameraCapture
Length of output: 2280
🏁 Script executed:
# Examine the VideoFrameInfo struct and how lifetimes are bound
cat bindings/rust/src/frame.rs | sed -n '1,50p'Repository: wysaid/CameraCapture
Length of output: 1674
🏁 Script executed:
# Check if detach() is exposed in Rust bindings
rg -n "detach" bindings/rust/src/ 2>/dev/nullRepository: wysaid/CameraCapture
Length of output: 295
🏁 Script executed:
# Check the VideoFrameInfo struct definition in Rust to see lifetime constraints
rg -n "struct VideoFrameInfo\|pub struct VideoFrameInfo" -A 20 bindings/rust/src/ 2>/dev/nullRepository: wysaid/CameraCapture
Length of output: 46
🏁 Script executed:
# Verify the complete data() method implementation one more time
sed -n '140,151p' bindings/rust/src/frame.rsRepository: wysaid/CameraCapture
Length of output: 526
Fix critical lifetime safety issues in info() and data() methods.
The data() method (lines 140-151) is fundamentally unsafe: it returns &[u8] with no lifetime binding to &self, allowing the slice to outlive the VideoFrame. If the frame is dropped, the slice becomes a dangling pointer.
The info() method (lines 86-138) has a related but subtler issue. While it correctly binds slice lifetimes to &'a self, this assumes the underlying data pointers remain valid for the frame's entire lifetime. However, the C++ implementation reveals that data[i] can point to hardware buffers managed by the camera driver. According to the C++ documentation (ccap_def.h:407-413), these hardware buffers can be reused by the driver even while the VideoFrame object exists, unless explicitly detached. The Rust bindings expose no way to call the detach() method, leaving slices created by info() potentially pointing to reused memory.
The safe fix requires either:
- Binding the slice lifetime in
data()to&self(make it return&'_ [u8]or wrap in a struct with lifetime) - Copying frame data at construction time to guarantee stability, or
- Exposing the C++
detach()method to Rust callers to explicitly copy hardware buffers to owned memory
🤖 Prompt for AI Agents
In @bindings/rust/src/frame.rs around lines 86 - 138, The info() and data()
methods expose slices that can outlive or point to driver-owned hardware buffers
that may be reused, creating dangling-pointer/undefined-behavior risks; fix by
making frame data owned or explicitly detached: update VideoFrameInfo (and
VideoFrame::data) so the returned buffers are owned copies (Vec<u8> or owned
container) created inside info() by copying from info.data[i] with the computed
plane sizes (plane0_size, plane1_size, plane2_size), change
VideoFrameInfo.data_planes to hold owned Vec<u8> (or Option<Vec<u8>>) instead of
borrowed &[u8], and adjust data() to return references tied to &self or return
owned data as well; alternatively, if you prefer zero-copy, expose and bind the
C++ detach() method to Rust and call it before creating slices—use the function
names info(), data(), VideoFrameInfo, and the C FFI call
sys::ccap_video_frame_get_info and add a binding for detach to ensure safety.
| /// Get all frame data as a slice | ||
| pub fn data(&self) -> crate::error::Result<&[u8]> { | ||
| let mut info = sys::CcapVideoFrameInfo::default(); | ||
|
|
||
| let success = unsafe { sys::ccap_video_frame_get_info(self.frame, &mut info) }; | ||
|
|
||
| if success && !info.data[0].is_null() { | ||
| Ok(unsafe { std::slice::from_raw_parts(info.data[0], info.sizeInBytes as usize) }) | ||
| } else { | ||
| Err(CcapError::FrameGrabFailed) | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same lifetime concern applies to data() method.
The data() method creates a slice from a C pointer (line 147) with lifetime tied to &self, sharing the same safety concern as info(). The returned slice must remain valid for the VideoFrame's entire lifetime.
🤖 Prompt for AI Agents
In @bindings/rust/src/frame.rs around lines 140 - 151, The data() method
currently builds a &[u8] from a raw C pointer tied to a temporary
CcapVideoFrameInfo, which is unsafe because the slice may outlive the underlying
frame memory; change the API to return an owned Vec<u8> (e.g., pub fn
data(&self) -> crate::error::Result<Vec<u8>>) and inside data() copy the bytes
from the raw pointer into a Vec (create the slice from_raw_parts then .to_vec())
before returning; update callers to accept Vec<u8> and keep the same error
handling (return Err(CcapError::FrameGrabFailed) on failure).
Critical fixes: - Add GitHub Actions workflow permissions (contents: read) - Fix memory leak in Provider callback management - Add callback_ptr field to track callback lifecycle - Implement cleanup_callback() method - Clean up callbacks in Drop implementation - Fix open_device ignoring device_name parameter - Fix pixel format conversion type errors in set/get operations - Fix VideoFrame data plane size calculations - Plane 0: stride * height - Chroma planes: stride * (height + 1) / 2 Important fixes: - Remove strict device count assertion in tests - Add bounds checking for DeviceInfo arrays - Fix README example code - Use open_device() instead of open() - Use grab_frame(timeout) instead of grab_frame_blocking() Minor fixes: - Add ConversionFailed error variant - Clean up unused imports in utils.rs - Document AsyncProvider's reserved _frame_sender field - Fix shell script quoting in build_and_test.sh - Fix remove_new_frame_callback error type All critical and major issues from PR review have been addressed.
- Remove non-existent functions: ccap_convert_frame, ccap_convert_is_backend_available, ccap_convert_get_backend_name - Remove non-existent conversion functions: MJPEG, YV12, yuyv422 - Fix NV12/I420/YUYV conversion functions to match C API signatures (add stride parameters) - Use correct constant CCAP_CONVERT_FLAG_DEFAULT instead of non-existent NONE - Remove unused ConversionFailed error variant - Fix unused_variables warning in build.rs - All conversion functions now match actual C API in include/ccap_convert_c.h
Problem Analysis: - Build Source (Dist) mode on macOS/Windows was failing with undefined symbols - Root cause: ccap_file_reader_apple.mm and ccap_file_reader_windows.cpp were missing - These files are required when CCAP_ENABLE_FILE_PLAYBACK is enabled (default ON in CMake) - The FileReaderApple/Windows classes are referenced in ccap_imp_apple.mm and ccap_imp_windows.cpp Changes: - Add ccap_file_reader_apple.mm to macOS platform sources - Add ccap_file_reader_windows.cpp to Windows platform sources - Define CCAP_ENABLE_FILE_PLAYBACK=1 to enable file playback functionality Verification: - Linux build-source mode: ✅ cargo build --no-default-features --features build-source - Linux tests: ✅ cargo test --no-default-features --features build-source - All 11 tests passed This fix ensures build-source mode includes all required source files for the file playback feature, resolving linker errors on macOS and Windows.
…ty and test robustness - Upgrade Rust crate version from 1.1.0 to 1.5.0 to match main project - Enhance build_and_test.sh with CLI-assisted device discovery validation: - Auto-build ccap CLI if not found (with CCAP_BUILD_CLI=ON) - Compare Rust print_camera and CLI --list-devices device counts - Report mismatches or inconsistencies; skip tests gracefully if no cameras - Fix: Use print_camera instead of non-existent list_cameras example - Fix: Directory restoration after CLI construction - Update: Usage examples list to match actual examples - Strengthen build_and_test.sh library rebuild: - Always rebuild when libccap.a missing, even if build dir exists - Use cmake --build instead of make for better portability - Check CMakeCache.txt to skip redundant reconfiguration - Extend scripts/update_version.sh to sync Rust crate version: - Update Cargo.toml version when running version script - Update Cargo.lock ccap entry via Python regex - Ensures future version bumps include Rust bindings - Add <limits> header to src/ccap_convert_frame.cpp: - Prevent uint32_t sizeInBytes overflow (return false if allocator->size() > UINT32_MAX) - Safety check for inplaceConvertFrame post-conversion
- Rename package to 'ccap-rs' for crates.io, keep lib name 'ccap' for imports - Change default features from 'static-link' to 'build-source' for crates.io compatibility - Update build.rs with robust source root discovery supporting packaged (vendor) mode - Implement publish_rust_crate.sh with multi-mode workflow: * dry-run: verify-only with auto-prepare (no upload) * preview: pre-release publish (non-main default) * stable: stable release (main branch default) * expert: as-is publish (requires explicit overrides) - Auto-vendor native sources (include/ + src/) to bindings/rust/native/ - Auto-fix Cargo.toml defaults to 'build-source' for dist-mode friendliness - Isolate static-link tests to truly validate pre-built library workflow - Translate RUST_INTEGRATION_SUMMARY.md to English - Update version update script to handle 'ccap-rs' package name - Branch-aware versioning: non-main = '-test.<timestamp>.<sha>' pre-release
Drop the incomplete frame_stream API and remove unused deps. Mark VideoFrame as Send (not Sync) and update docs/examples accordingly.
…s, and address PR #44 review feedback Rust bindings improvements: - Provider::open_device: clean up callbacks before destroying handle (prevents UAF/leaks) - set_resolution: add rollback logic to avoid partial state updates if height set fails - Global error callback: clarify process-global semantics with alias APIs - Add set_global_error_callback() and clear_global_error_callback() aliases - Always clear C-side callback in clear_error_callback() Build and test script improvements: - build_and_test.sh: cross-platform CPU core detection (nproc → sysctl/getconf fallback) - build_and_test.sh: robust CLI binary discovery (handle .exe on Windows) - build_and_test.sh: remove duplicate echo output - test_remote_crate.sh: fix cargo metadata version detection (remove --no-deps) - test_remote_crate.sh: improve cargo update error handling message Documentation fixes: - Wrap bare URLs in angle brackets (MD034) - Fix list indentation and table formatting (MD007/MD060) - Add blank lines around list features (MD032) Verified with: - cargo fmt (format all code) - cargo test (Rust bindings pass) - markdownlint (no errors remaining)
This PR introduces Rust bindings for the CameraCapture library, providing a high-performance and lightweight alternative to existing solutions.
Key Features
image, noopencv), making it ideal for embedded systems and CLI tools.VideoFrameInfolifetime issues) to prevent use-after-free errors.tokiointegration for modern async Rust applications.Changes
bindings/rustdirectory containing the Cargo project.Provider,VideoFrame, andConvertstructs.convertmodule.build.rsto correctly link against Debug/Release builds of the C++ library.Comparison with
nokhwaUnlike
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
Documentation
Chores
✏️ Tip: You can customize this high-level summary in your review settings.