WARNING: THIS SITE IS A MIRROR OF GITHUB.COM / IT CANNOT LOGIN OR REGISTER ACCOUNTS / THE CONTENTS ARE PROVIDED AS-IS / THIS SITE ASSUMES NO RESPONSIBILITY FOR ANY DISPLAYED CONTENT OR LINKS / IF YOU FOUND SOMETHING MAY NOT GOOD FOR EVERYONE, CONTACT ADMIN AT ilovescratch@foxmail.com
Skip to content

Conversation

@richiemcilroy
Copy link
Member

@richiemcilroy richiemcilroy commented Dec 19, 2025

  • Fix memory leaks in macOS screen capture and camera buffer handling
  • Refactor image buffer lifecycle management to ensure proper cleanup
  • Add fragmented MP4 muxer to reduce memory footprint during encoding
  • Improve error handling to prevent resource leaks on failure paths

Summary by CodeRabbit

  • New Features

    • Segmented MP4 recording with per-segment failure tracking and manifest generation
    • Fragmented MP4 writer and macOS segmented FFmpeg muxer
    • Memory-leak detector example and tooling
  • Bug Fixes

    • Safer handling when image/frame buffers are unavailable (prevents crashes/skips frames)
  • Improvements

    • Increased frame queue retry limits for more robust queuing
    • Reduced unnecessary buffer cloning and improved encoder finalization logging
    • Streamlined desktop diagnostics to a macOS-focused shape
  • Chores

    • Added claude directory to ignore list and expanded local permissions allowlist

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

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Dec 19, 2025

Walkthrough

Refactors image/sample buffer ownership and guards missing buffers; adds FFmpeg-based macOS segmented muxer with per-segment manifests and failure tracking; adjusts MP4 encoder finish/timing behavior; increases queue retry limits; adds memory-leak detector example and updates desktop diagnostics types.

Changes

Cohort / File(s) Summary
Config & Ignore
.claude/settings.local.json, .gitignore
Expanded Claude allow list and reformatted permissions JSON; added .claude/ to .gitignore.
Camera & SampleBuf refactor
crates/camera/src/macos.rs, crates/recording/src/feeds/camera.rs, crates/recording/src/sources/native_camera.rs, crates/scap-screencapturekit/src/capture.rs, crates/scap-ffmpeg/src/screencapturekit.rs, crates/camera-ffmpeg/src/macos.rs
NativeCapturedFrame now stores only SampleBuf; image_buf accessors changed to return Option; added NoImageBuffer error variants and consolidated/precomputed plane stride logic to avoid repeated image_buf calls; guarded against missing image buffers; adjusted buffer cloning/retaining patterns and smaller native camera channel size (256→8).
macOS segmented FFmpeg muxer & pipeline
crates/recording/src/output_pipeline/mod.rs, crates/recording/src/output_pipeline/macos_segmented_ffmpeg.rs, crates/recording/src/output_pipeline/fragmented.rs, crates/recording/src/output_pipeline/macos.rs, crates/recording/src/capture_pipeline.rs
Added MacOSSegmentedMuxer with per-segment encoders, manifest/fragment structs, manifest writing, sample_buf→ffmpeg frame conversion, segment threads and failure tracking (is_failed), swapped studio pipeline to use MacOSSegmentedMuxer, and increased MAX_QUEUE_RETRIES (500→1500) for relevant muxers.
enc-avfoundation MP4 finish/timing
crates/enc-avfoundation/src/mp4.rs, crates/enc-avfoundation/src/segmented.rs
Replaced most_recent_frame caching with last_frame_timestamp; added WriterFailed/NoEncoder error variants; split finish into finish_start/finish_nowait and wait_for_writer_finished with timeouts/polling/logging; threaded per-segment is_failed into segmented encoder and manifest generation.
enc-ffmpeg fragmented MP4 support
crates/enc-ffmpeg/src/mux/mod.rs, crates/enc-ffmpeg/src/mux/fragmented_mp4.rs, crates/enc-ffmpeg/src/lib.rs
Added new fragmented_mp4 module and FragmentedMP4File API: init, queue_video_frame, optional audio, finish, and public types for init/finish errors and results; re-exported module in crate public API.
Recording examples & desktop UI types
crates/recording/examples/memory-leak-detector.rs, apps/desktop/src/utils/tauri.ts, apps/desktop/src/routes/(window-chrome)/settings/feedback.tsx
Added memory-leak-detector example (macOS-specific mem stats, trackers, test modes); replaced Windows/GPU-focused diagnostics types with macOS-centric types and screenCaptureSupported; updated settings UI to show macOS info and Screen Capture capability.

Sequence Diagram(s)

sequenceDiagram
    participant Pipeline as Capture Pipeline
    participant Mux as MacOSSegmentedMuxer
    participant SegEnc as Segment Encoder Thread
    participant FFmpeg as FFmpeg Writer
    participant Manifest as Manifest Writer

    Pipeline->>Mux: send_video_frame(frame, timestamp)
    activate Mux
    Mux->>Mux: check_rotation_and_segment()
    alt rotate_needed
        Mux->>SegEnc: finish_nowait() (non-blocking)
        SegEnc->>FFmpeg: write_trailer()/flush
        FFmpeg-->>SegEnc: status (ok/err)
        SegEnc-->>Mux: report_finish_result
        Mux->>Manifest: update segment entry (is_failed/is_complete)
        Mux->>Mux: spawn new Segment Encoder
    end
    Mux->>SegEnc: push_frame_to_channel(frame, adjusted_ts)
    activate SegEnc
    SegEnc->>FFmpeg: queue_video_frame()/queue_audio_frame()
    FFmpeg-->>SegEnc: ok / error
    deactivate SegEnc
    Mux-->>Pipeline: ack (or backpressure/drop)
    deactivate Mux
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

  • Areas requiring extra attention:
    • MacOSSegmentedMuxer: thread lifecycle, channel boundaries, manifest atomicity, and per-segment failure handling.
    • MP4 finish/wait logic: timeouts, polling, error mapping to WriterFailed/NoEncoder, and interaction with asset writer state.
    • Buffer ownership changes: SampleBuf-only NativeCapturedFrame and Option image_buf propagation across many call sites.
    • FFmpeg conversion helpers and stride/plane logic: correctness for multi-planar formats and safe memory access (BaseAddrLockGuard / NoImageBuffer).
    • memory-leak-detector example: platform-specific commands and measurement parsing.

Possibly related PRs

Suggested labels

codex

Suggested reviewers

  • Brendonovich

Poem

🐰 Buffers hop, and segments bind,

frames leap in FFmpeg's mind,
Manifests note wins and fails,
Retry more when backpressure wails,
Memory tracks the path we find — recording stays refined.

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 18.75% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The pull request title accurately summarizes the main objective of the changeset - fixing memory leaks and reducing memory usage throughout the recording pipeline, which is reflected across all modified files including buffer lifecycle refactoring, new muxer implementations, and error handling improvements.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch encoder-fixes

📜 Recent review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d9ea73d and 5e2fc84.

📒 Files selected for processing (1)
  • apps/desktop/src/routes/(window-chrome)/settings/feedback.tsx (2 hunks)
🧰 Additional context used
📓 Path-based instructions (4)
**/*.tsx

📄 CodeRabbit inference engine (CLAUDE.md)

Use React Query hooks with Server Actions for mutations and perform precise cache updates using setQueryData/setQueriesData instead of broad invalidations

Files:

  • apps/desktop/src/routes/(window-chrome)/settings/feedback.tsx
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.{ts,tsx,js,jsx}: Use strict TypeScript; avoid any type; leverage shared types from @cap/* packages
Follow camelCase naming for variables and functions; PascalCase for components; hooks must start with 'use' prefix
Use Biome for linting and formatting; match existing formatting conventions in the codebase
Use Tailwind CSS for styling in web components; stay consistent with spacing and tokens
Use static skeletons for loading states that mirror content; avoid bouncing animations
Memoize expensive work, code-split naturally, and use Next/Image for remote assets

**/*.{ts,tsx,js,jsx}: Use 2-space indent for TypeScript files; format with Biome using pnpm format
Use Biome for code formatting and linting; run pnpm format regularly
Use kebab-case for file names (e.g., user-menu.tsx); use PascalCase for components

Files:

  • apps/desktop/src/routes/(window-chrome)/settings/feedback.tsx
**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.{ts,tsx}: Never add any form of comments to code (single-line //, multi-line /* /, JSDoc /* */, or any other comment syntax); code must be self-explanatory through naming, types, and structure
Directory naming must use lowercase-dashed convention

Files:

  • apps/desktop/src/routes/(window-chrome)/settings/feedback.tsx
**/*.{ts,tsx,js,jsx,rs}

📄 CodeRabbit inference engine (AGENTS.md)

Never add comments to code (//, /* */, ///, //!, #, etc.); code must be self-explanatory through naming, types, and structure

Files:

  • apps/desktop/src/routes/(window-chrome)/settings/feedback.tsx
🧠 Learnings (1)
📓 Common learnings
Learnt from: Brendonovich
Repo: CapSoftware/Cap PR: 1305
File: crates/recording/src/output_pipeline/macos.rs:80-90
Timestamp: 2025-10-28T08:39:42.230Z
Learning: In `crates/recording/src/output_pipeline/macos.rs`, the `AVFoundationMp4Muxer` intentionally holds the `Mutex<MP4Encoder>` lock during retry attempts in `send_video_frame()` and `send_audio_frame()`. This blocking behavior is correct because frame processing must happen sequentially to prevent audio and video frames from being interleaved incorrectly in the encoder.
⏰ 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). (2)
  • GitHub Check: Clippy (x86_64-pc-windows-msvc, windows-latest)
  • GitHub Check: Analyze (rust)
🔇 Additional comments (2)
apps/desktop/src/routes/(window-chrome)/settings/feedback.tsx (2)

151-160: The SystemDiagnostics type has been correctly updated with the screenCaptureSupported field. The Rust backend (crates/recording/src/diagnostics.rs) defines screen_capture_supported: bool, and the TypeScript bindings (apps/desktop/src/utils/tauri.ts) properly export screenCaptureSupported: boolean. No references to the old graphicsCaptureSupported field remain. The migration is complete and consistent across the backend and frontend type definitions.


135-144: The SystemDiagnostics type has been correctly updated with macosVersion: MacOSVersionInfo | null, and MacOSVersionInfo properly includes the displayName: string property. No orphaned windowsVersion references exist. The code is properly typed.


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

❤️ Share

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

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (11)
crates/enc-avfoundation/src/segmented.rs (2)

24-37: Consider logging sync failures for consistency.

Both dir.sync_all() (line 27) and file.sync_all() (line 35) silently discard errors with let _ =. While these are best-effort operations, other parts of this file log warnings on write failures. Consistent logging aids debugging.

Suggested improvement
     if let Some(parent) = path.parent()
         && let Ok(dir) = std::fs::File::open(parent)
     {
-        let _ = dir.sync_all();
+        if let Err(e) = dir.sync_all() {
+            warn!("Directory fsync failed for {}: {e}", parent.display());
+        }
     }
 fn sync_file(path: &Path) {
-    if let Ok(file) = std::fs::File::open(path) {
-        let _ = file.sync_all();
+    if let Ok(file) = std::fs::File::open(path)
+        && let Err(e) = file.sync_all()
+    {
+        warn!("File fsync failed for {}: {e}", path.display());
     }
 }

70-71: Unconventional skip_serializing_if predicate.

std::ops::Not::not works here because impl Not for &bool exists, but it's obscure. A named helper would be clearer.

More readable alternative
fn is_false(v: &bool) -> bool {
    !*v
}
-    #[serde(skip_serializing_if = "std::ops::Not::not")]
+    #[serde(skip_serializing_if = "is_false")]
     is_failed: bool,
crates/recording/examples/memory-leak-detector.rs (3)

452-455: Unsafe set_var in multi-threaded context.

std::env::set_var is unsafe in Rust 2024 edition and can cause undefined behavior in multi-threaded programs. Since this is called before tokio::main initializes the runtime, it's technically safe here, but the approach is fragile.

Consider using the RUST_LOG environment variable externally or initializing the subscriber with explicit configuration:

Suggested alternative
-    unsafe { std::env::set_var("RUST_LOG", "info,cap_recording=debug") };
-    tracing_subscriber::fmt::init();
+    tracing_subscriber::fmt()
+        .with_env_filter("info,cap_recording=debug")
+        .init();

383-383: Intentional memory leak via std::mem::forget.

std::mem::forget(dir) prevents cleanup of the temporary directory, which appears intentional so users can inspect the recording output. However, this leaks resources and the intent isn't immediately clear from the code.

Since the coding guidelines prohibit comments, consider renaming or restructuring to make the intent explicit:

Suggested approach
-    std::mem::forget(dir);
+    let output_dir = dir.into_path();
+    println!("Recording preserved at: {}", output_dir.display());

Using into_path() explicitly transfers ownership and prevents cleanup while being self-documenting.


186-186: Use .last() instead of indexing with len() - 1.

This is more idiomatic and avoids potential issues if the vector were empty (though guarded by the check on line 180).

Suggested fix
         let first = &self.samples[0];
-        let last = &self.samples[self.samples.len() - 1];
+        let last = self.samples.last().unwrap();
crates/recording/src/output_pipeline/macos_segmented_ffmpeg.rs (5)

17-23: Consider adding is_failed field to align with fragmented.rs.

The SegmentInfo struct is missing the is_failed field that exists in the fragmented.rs implementation (as seen in the relevant code snippets). This could limit proper failure tracking in manifests.

🔎 Proposed fix
 #[derive(Debug, Clone)]
 pub struct SegmentInfo {
     pub path: PathBuf,
     pub index: u32,
     pub duration: Duration,
     pub file_size: Option<u64>,
+    pub is_failed: bool,
 }

25-33: Missing is_failed field in FragmentEntry.

The FragmentEntry struct in fragmented.rs includes an is_failed field for per-segment failure tracking. This implementation lacks that field, creating inconsistency between the two muxers' manifest formats.

🔎 Proposed fix
 #[derive(Serialize)]
 struct FragmentEntry {
     path: String,
     index: u32,
     duration: f64,
     is_complete: bool,
     #[serde(skip_serializing_if = "Option::is_none")]
     file_size: Option<u64>,
+    #[serde(default, skip_serializing_if = "std::ops::Not::not")]
+    is_failed: bool,
 }

471-514: Detached thread for segment cleanup may accumulate.

The cleanup thread spawned during rotation is not joined or tracked. If segment rotations occur faster than cleanup completes, threads could accumulate. Consider using a bounded thread pool or tracking spawned handles.


630-634: Audio frames are silently ignored.

The AudioMuxer implementation is a no-op. If this is intentional (video-only segments), consider adding a trace log or documenting this limitation.


636-745: Extract duplicated frame conversion logic into a shared utility module.

The sample_buf_to_ffmpeg_frame() function and BaseAddrLockGuard struct in this file duplicate nearly identical implementations from crates/scap-ffmpeg/src/screencapturekit.rs. Both files implement identical pixel format handling (NV12 and BGRA), stride calculation, and memory locking logic. Consider moving this conversion logic into a shared utility to reduce maintenance burden.

crates/enc-avfoundation/src/mp4.rs (1)

518-522: Consider logging errors in Drop.

While ignoring the Result in Drop is unavoidable since errors cannot be propagated, consider logging any failure to aid debugging:

🔎 Optional improvement
 impl Drop for MP4Encoder {
     fn drop(&mut self) {
-        let _ = self.finish(None);
+        if let Err(e) = self.finish(None) {
+            error!("Failed to finish encoder in Drop: {:?}", e);
+        }
     }
 }
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 2ece58b and d3ffa4c.

📒 Files selected for processing (24)
  • .claude/settings.local.json (1 hunks)
  • .gitignore (1 hunks)
  • crates/camera-ffmpeg/src/macos.rs (14 hunks)
  • crates/camera/src/macos.rs (2 hunks)
  • crates/enc-avfoundation/src/mp4.rs (12 hunks)
  • crates/enc-avfoundation/src/segmented.rs (13 hunks)
  • crates/enc-ffmpeg/src/lib.rs (1 hunks)
  • crates/enc-ffmpeg/src/mux/fragmented_mp4.rs (1 hunks)
  • crates/enc-ffmpeg/src/mux/mod.rs (1 hunks)
  • crates/recording/examples/camera-benchmark.rs (4 hunks)
  • crates/recording/examples/encoding-benchmark.rs (2 hunks)
  • crates/recording/examples/memory-leak-detector.rs (1 hunks)
  • crates/recording/examples/recording-benchmark.rs (1 hunks)
  • crates/recording/src/capture_pipeline.rs (2 hunks)
  • crates/recording/src/feeds/camera.rs (1 hunks)
  • crates/recording/src/output_pipeline/fragmented.rs (2 hunks)
  • crates/recording/src/output_pipeline/macos.rs (2 hunks)
  • crates/recording/src/output_pipeline/macos_segmented_ffmpeg.rs (1 hunks)
  • crates/recording/src/output_pipeline/mod.rs (1 hunks)
  • crates/recording/src/sources/native_camera.rs (1 hunks)
  • crates/recording/src/sources/screen_capture/macos.rs (1 hunks)
  • crates/rendering/src/cpu_yuv.rs (3 hunks)
  • crates/scap-ffmpeg/src/screencapturekit.rs (3 hunks)
  • crates/scap-screencapturekit/src/capture.rs (2 hunks)
🧰 Additional context used
📓 Path-based instructions (2)
**/*.rs

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.rs: Never use dbg!() macro; use proper logging (tracing::debug!, etc.) instead
Never write let _ = async_fn() which silently drops futures; await or explicitly handle them
Use duration.saturating_sub(other) instead of duration - other to avoid panics on underflow
Merge nested if statements: write 'if a && b { }' instead of 'if a { if b { } }'
Don't call .clone() on Copy types (integers, bools, etc.); copy them directly
Use function references directly: iter.map(foo) instead of iter.map(|x| foo(x))
Accept &[T] or &str instead of &Vec or &String in function parameters for flexibility
Use .is_empty() instead of .len() == 0 or .len() > 0 / .len() != 0
Don't assign () to a variable: write foo(); instead of let _ = foo(); or let x = foo(); when return is unit
Use .unwrap_or(val) instead of .unwrap_or_else(|| val) when the default is a simple/cheap value
Use 'for item in &collection' or 'for (i, item) in collection.iter().enumerate()' instead of 'for i in 0..collection.len()'
Use value.clamp(min, max) instead of manual if chains or .min(max).max(min) patterns
Always handle Result/Option or types marked #[must_use]; never ignore them

**/*.rs: Use rustfmt and workspace clippy lints for Rust code formatting and linting
Use snake_case for Rust module names and kebab-case for crate names
Never use dbg!() macro in Rust code; use proper logging instead (Clippy: dbg_macro = deny)
Always handle Result/Option or types marked #[must_use]; never ignore them (Rust compiler lint: unused_must_use = deny)
Never write let _ = async_fn() which silently drops futures; await or explicitly handle them (Clippy: let_underscore_future = deny)
Use saturating_sub instead of - for Duration to avoid panics (Clippy: unchecked_duration_subtraction = deny)
Merge nested if statements: use if a && b { } instead of if a { if b { } } (Clippy: collapsible_if = deny)
Don't call .clone() on Copy types; just copy them directly (Clippy: clone_on_copy = deny)
U...

Files:

  • crates/recording/src/sources/screen_capture/macos.rs
  • crates/recording/src/feeds/camera.rs
  • crates/recording/src/sources/native_camera.rs
  • crates/recording/src/output_pipeline/fragmented.rs
  • crates/rendering/src/cpu_yuv.rs
  • crates/recording/examples/camera-benchmark.rs
  • crates/enc-ffmpeg/src/mux/mod.rs
  • crates/recording/src/output_pipeline/mod.rs
  • crates/enc-ffmpeg/src/lib.rs
  • crates/scap-ffmpeg/src/screencapturekit.rs
  • crates/camera/src/macos.rs
  • crates/camera-ffmpeg/src/macos.rs
  • crates/recording/src/output_pipeline/macos_segmented_ffmpeg.rs
  • crates/enc-avfoundation/src/segmented.rs
  • crates/recording/examples/encoding-benchmark.rs
  • crates/recording/examples/memory-leak-detector.rs
  • crates/recording/examples/recording-benchmark.rs
  • crates/scap-screencapturekit/src/capture.rs
  • crates/recording/src/capture_pipeline.rs
  • crates/recording/src/output_pipeline/macos.rs
  • crates/enc-ffmpeg/src/mux/fragmented_mp4.rs
  • crates/enc-avfoundation/src/mp4.rs
**/*.{ts,tsx,js,jsx,rs}

📄 CodeRabbit inference engine (AGENTS.md)

Never add comments to code (//, /* */, ///, //!, #, etc.); code must be self-explanatory through naming, types, and structure

Files:

  • crates/recording/src/sources/screen_capture/macos.rs
  • crates/recording/src/feeds/camera.rs
  • crates/recording/src/sources/native_camera.rs
  • crates/recording/src/output_pipeline/fragmented.rs
  • crates/rendering/src/cpu_yuv.rs
  • crates/recording/examples/camera-benchmark.rs
  • crates/enc-ffmpeg/src/mux/mod.rs
  • crates/recording/src/output_pipeline/mod.rs
  • crates/enc-ffmpeg/src/lib.rs
  • crates/scap-ffmpeg/src/screencapturekit.rs
  • crates/camera/src/macos.rs
  • crates/camera-ffmpeg/src/macos.rs
  • crates/recording/src/output_pipeline/macos_segmented_ffmpeg.rs
  • crates/enc-avfoundation/src/segmented.rs
  • crates/recording/examples/encoding-benchmark.rs
  • crates/recording/examples/memory-leak-detector.rs
  • crates/recording/examples/recording-benchmark.rs
  • crates/scap-screencapturekit/src/capture.rs
  • crates/recording/src/capture_pipeline.rs
  • crates/recording/src/output_pipeline/macos.rs
  • crates/enc-ffmpeg/src/mux/fragmented_mp4.rs
  • crates/enc-avfoundation/src/mp4.rs
🧠 Learnings (4)
📓 Common learnings
Learnt from: Brendonovich
Repo: CapSoftware/Cap PR: 1305
File: crates/recording/src/output_pipeline/macos.rs:80-90
Timestamp: 2025-10-28T08:39:42.230Z
Learning: In `crates/recording/src/output_pipeline/macos.rs`, the `AVFoundationMp4Muxer` intentionally holds the `Mutex<MP4Encoder>` lock during retry attempts in `send_video_frame()` and `send_audio_frame()`. This blocking behavior is correct because frame processing must happen sequentially to prevent audio and video frames from being interleaved incorrectly in the encoder.
📚 Learning: 2025-10-17T05:58:22.586Z
Learnt from: Brendonovich
Repo: CapSoftware/Cap PR: 1219
File: crates/enc-avfoundation/src/mp4.rs:350-373
Timestamp: 2025-10-17T05:58:22.586Z
Learning: In crates/enc-avfoundation/src/mp4.rs, the `finish()` method intentionally skips video extension when `is_paused` is true. This is correct behavior because if recording is paused, the video should not be extended beyond the pause point—the pause is user-initiated, unlike the case where ScreenCaptureKit stops providing frames during static content.

Applied to files:

  • crates/recording/src/sources/screen_capture/macos.rs
  • crates/recording/src/output_pipeline/fragmented.rs
  • crates/recording/src/output_pipeline/macos_segmented_ffmpeg.rs
  • crates/enc-avfoundation/src/segmented.rs
  • crates/scap-screencapturekit/src/capture.rs
  • crates/recording/src/capture_pipeline.rs
  • crates/recording/src/output_pipeline/macos.rs
  • crates/enc-ffmpeg/src/mux/fragmented_mp4.rs
  • crates/enc-avfoundation/src/mp4.rs
📚 Learning: 2025-10-28T08:39:42.230Z
Learnt from: Brendonovich
Repo: CapSoftware/Cap PR: 1305
File: crates/recording/src/output_pipeline/macos.rs:80-90
Timestamp: 2025-10-28T08:39:42.230Z
Learning: In `crates/recording/src/output_pipeline/macos.rs`, the `AVFoundationMp4Muxer` intentionally holds the `Mutex<MP4Encoder>` lock during retry attempts in `send_video_frame()` and `send_audio_frame()`. This blocking behavior is correct because frame processing must happen sequentially to prevent audio and video frames from being interleaved incorrectly in the encoder.

Applied to files:

  • crates/recording/src/sources/screen_capture/macos.rs
  • crates/recording/src/feeds/camera.rs
  • crates/recording/src/output_pipeline/fragmented.rs
  • crates/recording/examples/camera-benchmark.rs
  • crates/recording/src/output_pipeline/mod.rs
  • crates/enc-ffmpeg/src/lib.rs
  • crates/scap-ffmpeg/src/screencapturekit.rs
  • crates/camera-ffmpeg/src/macos.rs
  • crates/recording/src/output_pipeline/macos_segmented_ffmpeg.rs
  • crates/enc-avfoundation/src/segmented.rs
  • crates/scap-screencapturekit/src/capture.rs
  • crates/recording/src/capture_pipeline.rs
  • crates/recording/src/output_pipeline/macos.rs
  • crates/enc-ffmpeg/src/mux/fragmented_mp4.rs
  • crates/enc-avfoundation/src/mp4.rs
📚 Learning: 2025-12-07T14:29:19.180Z
Learnt from: CR
Repo: CapSoftware/Cap PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-07T14:29:19.180Z
Learning: Applies to **/*.rs : Never use dbg!() macro; use proper logging (tracing::debug!, etc.) instead

Applied to files:

  • crates/recording/examples/camera-benchmark.rs
🧬 Code graph analysis (4)
crates/recording/src/sources/screen_capture/macos.rs (1)
crates/scap-screencapturekit/src/capture.rs (1)
  • image_buf (110-112)
crates/scap-ffmpeg/src/screencapturekit.rs (2)
crates/scap-ffmpeg/src/lib.rs (1)
  • as_ffmpeg (15-15)
crates/scap-screencapturekit/src/capture.rs (1)
  • image_buf (110-112)
crates/recording/src/output_pipeline/macos_segmented_ffmpeg.rs (2)
crates/recording/src/output_pipeline/fragmented.rs (2)
  • default (37-42)
  • default (183-188)
crates/recording/src/fragmentation/mod.rs (2)
  • current_index (86-88)
  • total_duration (132-138)
crates/enc-avfoundation/src/segmented.rs (2)
crates/enc-avfoundation/src/mp4.rs (1)
  • wait_for_writer_finished (672-714)
crates/recording/src/fragmentation/mod.rs (1)
  • sync_file (31-35)
⏰ 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). (1)
  • GitHub Check: Analyze (rust)
🔇 Additional comments (52)
crates/rendering/src/cpu_yuv.rs (3)

993-993: LGTM! Improved test assertion message readability.

The named placeholders make test failure output clearer and easier to debug.


1063-1063: LGTM! Consistent test assertion formatting.

Named placeholders maintain consistency with other test assertions and improve debugging experience.


1114-1114: LGTM! Clear and consistent test assertion.

The named placeholders and descriptive prefix ("YUV420P") make it easy to distinguish test failures from different conversion functions.

crates/recording/examples/recording-benchmark.rs (1)

268-268: LGTM: Clean simplification of match arm.

The explicit "full" pattern was redundant since the default branch executes the same logic. This change maintains identical behavior while simplifying the code.

crates/recording/examples/encoding-benchmark.rs (2)

195-195: LGTM: Modern formatting syntax.

The change to named parameters in println! is a minor style improvement with no functional impact.


409-409: LGTM: Consistent simplification.

This matches the same cleanup applied in recording-benchmark.rs, removing the redundant explicit "full" pattern while maintaining identical behavior through the default branch.

crates/recording/examples/camera-benchmark.rs (1)

301-304: Improved error handling during drain phase.

This change properly handles encoding errors during the drain phase by logging the error and recording the dropped output metric, making it consistent with the error handling in the main benchmark loop. Previously, these errors were silently ignored.

crates/enc-avfoundation/src/segmented.rs (8)

1-11: LGTM!

Imports are appropriate for the new background finalization and failure tracking functionality.


179-189: File size may be inaccurate due to async finalization.

file_size is read immediately after spawning the background finalization thread. Since wait_for_writer_finished runs asynchronously, the file may not be fully written when metadata is queried, potentially yielding stale sizes in the manifest.

If accurate sizes are needed for playback or stitching, consider either:

  1. Accepting None for in-progress segments and updating sizes post-finalization
  2. Deferring size calculation to manifest finalization

205-219: LGTM!

Good error handling with contextual logging for the segment index, enabling easier debugging if encoder creation fails during rotation.


325-378: Consistent failure tracking pattern.

The finish method properly mirrors the rotation logic with explicit success/failure paths. The same file_size timing consideration applies here as noted for rotate_segment.


380-424: Good failure summary and manifest finalization.

The warning log for failed segments provides useful operational visibility. The is_complete flag correctly indicates session completion while individual is_failed flags track per-segment status.


264-311: LGTM!

The in-progress manifest correctly tracks both completed segments (with their failure states) and the current active segment. The is_failed: false for the current segment is appropriate since it's still being written.


131-148: LGTM!

Clear error propagation with QueueFrameError::NoEncoder when the encoder is unavailable.


53-60: LGTM!

The is_failed field in SegmentInfo enables clean per-segment failure tracking throughout the pipeline.

crates/recording/src/feeds/camera.rs (1)

479-482: Good refactor: Using standard .clone() pattern.

The change from .retained() to .clone() aligns with standard Rust conventions for reference-counted types. Both methods properly increment the reference count, but .clone() is more idiomatic.

crates/recording/src/output_pipeline/macos.rs (2)

38-38: Increased retry tolerance improves reliability under load.

Increasing MAX_QUEUE_RETRIES from 500 to 1500 extends the timeout window from 1 second to 3 seconds, giving the encoder more time to recover when under pressure. This complements the buffer size reductions elsewhere in the PR.


149-149: Consistent retry limit increase for camera muxer.

The same retry limit increase applied to the camera muxer ensures consistent timeout behavior across both muxer types.

crates/recording/src/sources/screen_capture/macos.rs (1)

177-182: Proper guard against missing image buffer prevents potential crashes.

The addition of the let Some(image_buf) = frame.image_buf() else { return; } guard properly handles cases where the image buffer is unavailable, preventing potential panics. This defensive pattern aligns with the PR's goal of robust buffer lifecycle management.

crates/recording/src/output_pipeline/fragmented.rs (2)

47-47: Consistent retry limit across all muxer variants.

The retry limit increase matches the change in the non-fragmented muxers, ensuring consistent timeout behavior across all macOS encoding paths.


193-193: Camera muxer timeout aligned with screen muxer.

Consistent retry limits across both fragmented muxer types ensure predictable behavior.

crates/camera/src/macos.rs (2)

102-104: Proper guard prevents construction of frames without image data.

The early return when image_buf() is None ensures that only valid frames are passed to the callback, preventing downstream errors.


203-212: Memory optimization: stores only SampleBuf and extracts ImageBuf on demand.

This refactor reduces memory usage by storing a single reference to SampleBuf instead of separate references to both ImageBuf and SampleBuf. The image_buf() method extracts the image buffer on demand and returns an Option to handle cases where the buffer is unavailable. All call sites properly handle the Option return value.

crates/recording/src/sources/native_camera.rs (1)

32-32: Buffer size reduction requires load testing validation.

The drop detection logic (lines 81-88) is correctly implemented with proper logging. However, buffer capacity at 8 frames should be validated under realistic encoding load conditions. The current code logs frame drops but doesn't prevent them. Without benchmarks or integration tests that measure frame drop rates at typical framerates and resolutions, the adequacy of this size remains unvalidated. Consider running the existing benchmark examples (crates/recording/examples/camera-benchmark.rs) with measured drop rates to confirm this buffer size doesn't cause excessive frame loss under expected conditions.

crates/recording/examples/memory-leak-detector.rs (2)

296-310: Camera lock not explicitly released on early return paths.

The CameraFeed is spawned and a lock is acquired, but if the recording fails later, the lock isn't explicitly released. While Rust's ownership will eventually clean up, for a memory leak detector tool, explicit cleanup would be more robust.

Consider whether camera::Unlock or similar cleanup should be performed in error paths.


1-528: LGTM overall for a development/diagnostic tool.

The memory leak detector is well-structured with clear separation between memory tracking, test execution, and CLI handling. The use of tracing for logging follows the coding guidelines.

crates/enc-ffmpeg/src/mux/fragmented_mp4.rs (2)

62-68: Unsafe FFmpeg option setting looks correct but fragile.

The unsafe block correctly sets fragmented MP4 movflags. The CString::new().unwrap() calls are safe since the strings are hardcoded ASCII without null bytes.


120-153: Good error handling pattern in finish().

The implementation correctly:

  • Continues to flush audio even if video flush fails
  • Logs individual encoder failures while still attempting trailer write
  • Returns structured FinishResult with per-encoder outcomes

This resilient approach prevents partial failures from causing resource leaks.

crates/enc-ffmpeg/src/mux/mod.rs (1)

1-5: LGTM.

The new fragmented_mp4 module is correctly declared and maintains consistent ordering with existing modules.

crates/enc-ffmpeg/src/lib.rs (1)

16-18: LGTM.

The re-export pattern is consistent with the existing segmented_audio module exposure, maintaining API consistency.

crates/recording/src/output_pipeline/mod.rs (1)

6-7: LGTM.

The new macos_segmented_ffmpeg module follows the established pattern for platform-specific modules, with proper #[cfg(target_os = "macos")] guards matching the existing fragmented and macos modules.

Also applies to: 14-15

crates/scap-screencapturekit/src/capture.rs (2)

28-32: LGTM!

The early return for missing image buffer prevents processing invalid screen frames, and the simplified VideoFrame construction avoids redundant buffer storage.


110-112: LGTM!

Delegating to sample_buf.image_buf() instead of storing a separate reference is a cleaner approach that aligns with the memory leak fixes.

crates/recording/src/capture_pipeline.rs (2)

9-9: LGTM!

Import updated to use the new MacOSSegmentedMuxer for the fragmented recording path.


89-89: LGTM!

Consistent with the import change, switching to the new FFmpeg-based segmented muxer.

crates/scap-ffmpeg/src/screencapturekit.rs (3)

10-10: LGTM!

New error variant properly handles the case when no image buffer is available.


15-24: LGTM!

Good refactor: obtaining the image buffer reference once, checking for None early, and precomputing plane strides before the lock avoids redundant method calls during the copy loops.


30-91: LGTM!

The pixel format matching and frame copying logic correctly uses the precomputed strides.

crates/camera-ffmpeg/src/macos.rs (3)

23-24: LGTM!

NoImageBuffer error variant added consistently with the scap-ffmpeg implementation.


58-90: LGTM!

Good refactor: obtaining the image buffer once, early error on None, and precomputing plane info before the lock. This avoids repeated method calls during frame processing.


291-319: LGTM!

The y420 branch correctly validates plane_count >= 3 before iterating over all three planes using the precomputed plane_info.

crates/recording/src/output_pipeline/macos_segmented_ffmpeg.rs (4)

162-194: LGTM!

The Muxer::setup implementation correctly creates the segments directory and initializes state. Good use of with_context for error messages.


204-264: LGTM!

The finish method properly handles timeout-based thread joining and uses saturating_sub for duration calculation as per coding guidelines.


100-131: LGTM!

The PauseTracker::adjust method correctly handles pause/resume transitions with proper overflow checks using checked_sub and checked_add.


611-624: No accessibility issue.

The sample_buf field is public in the VideoFrame struct (crates/recording/src/sources/screen_capture/macos.rs), and screen_capture is properly imported in this module. The field is accessible.

Likely an incorrect or invalid review comment.

crates/enc-avfoundation/src/mp4.rs (7)

19-19: Excellent memory leak fix!

Replacing most_recent_frame (which stored both frame buffer and timestamp) with just last_frame_timestamp eliminates unnecessary retention of frame buffer references. This directly addresses the memory leak issue described in the PR objectives.

Also applies to: 206-206


55-56: LGTM - Improved error diagnostics.

The new error variants provide better error information:

  • WriterFailed captures the actual error from the asset writer
  • NoEncoder clearly indicates when audio encoder is missing

Also applies to: 61-62


278-281: Verify error handling for frame copy.

The explicit drop(frame) is excellent for memory management—it immediately releases the original frame after creating the copy, reducing peak memory usage.

However, the .unwrap() on line 278 will panic if copy_with_new_timing fails. Consider proper error propagation:

🔎 Suggested improvement
-        let new_frame = frame.copy_with_new_timing(&[timing]).unwrap();
+        let new_frame = frame.copy_with_new_timing(&[timing])
+            .map_err(QueueFrameError::Construct)?;
         drop(frame);

300-302: LGTM - Clear error handling.

Proper use of the new NoEncoder error variant when audio input is not configured.


455-515: Excellent refactoring for async finalization support.

The finish logic is well-separated:

  • finish() provides synchronous completion
  • finish_nowait() enables async patterns
  • finish_start() handles initialization

The use of saturating_sub (line 500) correctly follows coding guidelines to prevent panics on duration underflow. The logic properly handles pause states and timestamp adjustments.


653-656: LGTM - Enhanced error diagnostics.

The improved error handling captures and propagates the actual writer error when available, providing better debugging information.


668-714: Polling approach is appropriate given the available cidre bindings.

The implementation correctly uses polling because the cidre bindings only expose a synchronous finish_writing() method without callback support. The 10-second timeout, progressive 2-second logging, and proper error handling for all writer states make this a robust solution within the available API constraints. The blocking behavior is correct for sequential frame processing in this context.

Comment on lines +107 to +118
pub fn queue_audio_frame(&mut self, frame: frame::Audio) {
if self.is_finished {
return;
}

let Some(audio) = &mut self.audio else {
return;
};

self.has_frames = true;
audio.send_frame(frame, &mut self.output);
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Find fragmented_mp4.rs file
find . -name "fragmented_mp4.rs" -type f

Repository: CapSoftware/Cap

Length of output: 104


🏁 Script executed:

#!/bin/bash
# Search for AudioEncoder trait definition
rg "trait AudioEncoder" -A 10

# Search for queue_audio_frame method
rg "fn queue_audio_frame" -A 8

# Search for queue_video_frame method
rg "fn queue_video_frame" -A 8

# Search for send_frame method in audio encoder context
rg "fn send_frame" -B 2 -A 5

Repository: CapSoftware/Cap

Length of output: 8616


Inconsistency: queue_audio_frame should return Result like queue_video_frame.

The queue_audio_frame method returns () while the analogous queue_video_frame returns Result<(), h264::QueueFrameError>. This inconsistency masks encoding errors. Although the AudioEncoder::send_frame trait method itself returns (), the implementations explicitly drop errors from internal methods using let _ = .... Align error handling by returning Result from queue_audio_frame:

-    pub fn queue_audio_frame(&mut self, frame: frame::Audio) {
+    pub fn queue_audio_frame(&mut self, frame: frame::Audio) -> Result<(), Box<dyn std::error::Error>> {
         if self.is_finished {
-            return;
+            return Ok(());
         }

         let Some(audio) = &mut self.audio else {
-            return;
+            return Ok(());
         };

         self.has_frames = true;
-        audio.send_frame(frame, &mut self.output);
+        audio.send_frame(frame, &mut self.output);
+        Ok(())
     }
🤖 Prompt for AI Agents
In crates/enc-ffmpeg/src/mux/fragmented_mp4.rs around lines 107-118,
queue_audio_frame currently returns () while queue_video_frame returns
Result<(), h264::QueueFrameError>, which hides encoding errors; change
queue_audio_frame to return Result<(), h264::QueueFrameError>, update the call
to audio.send_frame to propagate its Result (or update the
AudioEncoder::send_frame trait and all its implementations to return Result if
they currently return ()), map/convert any internal encoder errors into
h264::QueueFrameError as needed, and update all callers of queue_audio_frame to
handle the Result so audio encoding failures are not silently ignored.

Comment on lines +164 to +169
impl Drop for FragmentedMP4File {
fn drop(&mut self) {
if let Err(e) = self.finish() {
error!("Failed to finish FragmentedMP4File in Drop: {e}");
}
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Drop implementation logs AlreadyFinished as error.

When finish() is called explicitly before drop, the Drop impl will call finish() again and log AlreadyFinished as an error, which is misleading since it's expected behavior.

Suggested fix
 impl Drop for FragmentedMP4File {
     fn drop(&mut self) {
-        if let Err(e) = self.finish() {
-            error!("Failed to finish FragmentedMP4File in Drop: {e}");
+        if !self.is_finished {
+            if let Err(e) = self.finish() {
+                error!("Failed to finish FragmentedMP4File in Drop: {e}");
+            }
         }
     }
 }

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

🤖 Prompt for AI Agents
In crates/enc-ffmpeg/src/mux/fragmented_mp4.rs around lines 164 to 169, the Drop
impl calls self.finish() and unconditionally logs any error, which causes an
misleading error log for the expected AlreadyFinished case; change the drop
logic to call finish(), match the returned error, and suppress or ignore the
AlreadyFinished/expected-no-op variant while only logging unexpected errors (or
map the AlreadyFinished to Ok) so Drop does not emit an error for the normal
already-finished path.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (1)
crates/recording/src/output_pipeline/macos_segmented_ffmpeg.rs (1)

219-237: Consider extracting the thread join-with-timeout pattern.

The polling logic for waiting on encoder threads (with 5s timeout and 50ms intervals) appears in both finish() and rotate_segment(). Extracting this pattern into a helper function would reduce duplication and improve maintainability.

🔎 Example helper function
fn join_encoder_thread_with_timeout(
    handle: JoinHandle<anyhow::Result<()>>,
    timeout: Duration,
    context: &str,
) {
    let start = std::time::Instant::now();
    loop {
        if handle.is_finished() {
            if let Err(panic_payload) = handle.join() {
                warn!("Screen encoder thread panicked during {context}: {panic_payload:?}");
            }
            break;
        }
        if start.elapsed() > timeout {
            warn!("Screen encoder thread did not finish within {timeout:?} during {context}, abandoning");
            break;
        }
        std::thread::sleep(Duration::from_millis(50));
    }
}

Also applies to: 474-493

📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d3ffa4c and d9ea73d.

📒 Files selected for processing (2)
  • apps/desktop/src/utils/tauri.ts (2 hunks)
  • crates/recording/src/output_pipeline/macos_segmented_ffmpeg.rs (1 hunks)
🧰 Additional context used
📓 Path-based instructions (6)
**/*.ts

📄 CodeRabbit inference engine (CLAUDE.md)

Use TanStack Query v5 for all client-side server state and data fetching in TypeScript files

Files:

  • apps/desktop/src/utils/tauri.ts
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.{ts,tsx,js,jsx}: Use strict TypeScript; avoid any type; leverage shared types from @cap/* packages
Follow camelCase naming for variables and functions; PascalCase for components; hooks must start with 'use' prefix
Use Biome for linting and formatting; match existing formatting conventions in the codebase
Use Tailwind CSS for styling in web components; stay consistent with spacing and tokens
Use static skeletons for loading states that mirror content; avoid bouncing animations
Memoize expensive work, code-split naturally, and use Next/Image for remote assets

**/*.{ts,tsx,js,jsx}: Use 2-space indent for TypeScript files; format with Biome using pnpm format
Use Biome for code formatting and linting; run pnpm format regularly
Use kebab-case for file names (e.g., user-menu.tsx); use PascalCase for components

Files:

  • apps/desktop/src/utils/tauri.ts
**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.{ts,tsx}: Never add any form of comments to code (single-line //, multi-line /* /, JSDoc /* */, or any other comment syntax); code must be self-explanatory through naming, types, and structure
Directory naming must use lowercase-dashed convention

Files:

  • apps/desktop/src/utils/tauri.ts
apps/desktop/**/*.ts

📄 CodeRabbit inference engine (CLAUDE.md)

apps/desktop/**/*.ts: Use @tanstack/solid-query for server state management in SolidJS components
Use generated commands and events from tauri_specta for IPC; never manually construct IPC calls
Listen directly to generated events from tauri_specta and use typed event interfaces

Files:

  • apps/desktop/src/utils/tauri.ts
**/*.{ts,tsx,js,jsx,rs}

📄 CodeRabbit inference engine (AGENTS.md)

Never add comments to code (//, /* */, ///, //!, #, etc.); code must be self-explanatory through naming, types, and structure

Files:

  • apps/desktop/src/utils/tauri.ts
  • crates/recording/src/output_pipeline/macos_segmented_ffmpeg.rs
**/*.rs

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.rs: Never use dbg!() macro; use proper logging (tracing::debug!, etc.) instead
Never write let _ = async_fn() which silently drops futures; await or explicitly handle them
Use duration.saturating_sub(other) instead of duration - other to avoid panics on underflow
Merge nested if statements: write 'if a && b { }' instead of 'if a { if b { } }'
Don't call .clone() on Copy types (integers, bools, etc.); copy them directly
Use function references directly: iter.map(foo) instead of iter.map(|x| foo(x))
Accept &[T] or &str instead of &Vec or &String in function parameters for flexibility
Use .is_empty() instead of .len() == 0 or .len() > 0 / .len() != 0
Don't assign () to a variable: write foo(); instead of let _ = foo(); or let x = foo(); when return is unit
Use .unwrap_or(val) instead of .unwrap_or_else(|| val) when the default is a simple/cheap value
Use 'for item in &collection' or 'for (i, item) in collection.iter().enumerate()' instead of 'for i in 0..collection.len()'
Use value.clamp(min, max) instead of manual if chains or .min(max).max(min) patterns
Always handle Result/Option or types marked #[must_use]; never ignore them

**/*.rs: Use rustfmt and workspace clippy lints for Rust code formatting and linting
Use snake_case for Rust module names and kebab-case for crate names
Never use dbg!() macro in Rust code; use proper logging instead (Clippy: dbg_macro = deny)
Always handle Result/Option or types marked #[must_use]; never ignore them (Rust compiler lint: unused_must_use = deny)
Never write let _ = async_fn() which silently drops futures; await or explicitly handle them (Clippy: let_underscore_future = deny)
Use saturating_sub instead of - for Duration to avoid panics (Clippy: unchecked_duration_subtraction = deny)
Merge nested if statements: use if a && b { } instead of if a { if b { } } (Clippy: collapsible_if = deny)
Don't call .clone() on Copy types; just copy them directly (Clippy: clone_on_copy = deny)
U...

Files:

  • crates/recording/src/output_pipeline/macos_segmented_ffmpeg.rs
🧠 Learnings (2)
📚 Learning: 2025-10-28T08:39:42.230Z
Learnt from: Brendonovich
Repo: CapSoftware/Cap PR: 1305
File: crates/recording/src/output_pipeline/macos.rs:80-90
Timestamp: 2025-10-28T08:39:42.230Z
Learning: In `crates/recording/src/output_pipeline/macos.rs`, the `AVFoundationMp4Muxer` intentionally holds the `Mutex<MP4Encoder>` lock during retry attempts in `send_video_frame()` and `send_audio_frame()`. This blocking behavior is correct because frame processing must happen sequentially to prevent audio and video frames from being interleaved incorrectly in the encoder.

Applied to files:

  • crates/recording/src/output_pipeline/macos_segmented_ffmpeg.rs
📚 Learning: 2025-10-17T05:58:22.586Z
Learnt from: Brendonovich
Repo: CapSoftware/Cap PR: 1219
File: crates/enc-avfoundation/src/mp4.rs:350-373
Timestamp: 2025-10-17T05:58:22.586Z
Learning: In crates/enc-avfoundation/src/mp4.rs, the `finish()` method intentionally skips video extension when `is_paused` is true. This is correct behavior because if recording is paused, the video should not be extended beyond the pause point—the pause is user-initiated, unlike the case where ScreenCaptureKit stops providing frames during static content.

Applied to files:

  • crates/recording/src/output_pipeline/macos_segmented_ffmpeg.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: Clippy (aarch64-apple-darwin, macos-latest)
  • GitHub Check: Clippy (x86_64-pc-windows-msvc, windows-latest)
  • GitHub Check: Analyze (rust)
🔇 Additional comments (6)
apps/desktop/src/utils/tauri.ts (1)

438-438: LGTM!

The MacOSVersionInfo type definition is clear and appropriate for storing macOS version information.

crates/recording/src/output_pipeline/macos_segmented_ffmpeg.rs (5)

630-634: Verify intentional audio omission.

The AudioMuxer implementation silently discards all audio frames. Confirm this is intentional for the current scope, as the PR mentions fixing "camera buffer handling" which could include audio.

If audio is not needed for this segmented muxer, consider adding a log message during setup to make this explicit:

fn setup(...) -> anyhow::Result<Self> {
    // ... existing setup ...
    info!("MacOSSegmentedMuxer: audio frames will not be encoded");
    Ok(Self { ... })
}

100-131: LGTM: Robust timestamp adjustment.

The pause tracking logic correctly uses checked_sub and checked_add for all Duration arithmetic with clear error messages, preventing panics on underflow/overflow.

As per coding guidelines, saturating_sub or checked_sub must be used for Duration subtraction.


453-532: LGTM: Segment rotation with proper cleanup.

The segment rotation correctly uses saturating_sub for duration calculation (line 457) and spawns an async cleanup thread to avoid blocking the recording pipeline. The frame drop tracker is properly reset at segment boundaries.


636-712: LGTM: Correct pixel buffer conversion with stride handling.

The conversion function properly:

  • Checks for missing image buffers before access
  • Uses RAII guard to ensure pixel buffer is locked during memory access
  • Handles stride mismatches with row-by-row copying for both NV12 and BGRA formats
  • Returns descriptive errors for unsupported formats

The row-by-row approach is necessary for correct handling of different stride values between source and destination.


726-740: Verify memory safety of pixel buffer access.

The unsafe slice creation in plane_data() relies on cidre providing valid base addresses and sizes. Ensure that:

  1. The CVPixelBuffer lifetime extends beyond the guard's lifetime
  2. The plane data calculation plane_bytes_per_row * plane_height correctly represents the available memory
  3. The cidre library provides accurate base address pointers

The current RAII pattern with lifetime 'a is correct for ensuring the lock is held during slice access. However, the underlying FFI assumptions should be validated.

#!/bin/bash
# Verify CVPixelBuffer memory access patterns in cidre usage
rg -nP --type=rust -C3 'ImageBuf.*plane_base_address|plane_bytes_per_row' 

export type StudioRecordingMeta = { segment: SingleSegment } | { inner: MultipleSegments }
export type StudioRecordingStatus = { status: "InProgress" } | { status: "NeedsRemux" } | { status: "Failed"; error: string } | { status: "Complete" }
export type SystemDiagnostics = { windowsVersion: WindowsVersionInfo | null; gpuInfo: GpuInfoDiag | null; allGpus: AllGpusInfo | null; renderingStatus: RenderingStatus; availableEncoders: string[]; graphicsCaptureSupported: boolean; d3D11VideoProcessorAvailable: boolean }
export type SystemDiagnostics = { macosVersion: MacOSVersionInfo | null; availableEncoders: string[]; screenCaptureSupported: boolean }
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Breaking change in SystemDiagnostics type requires verification.

The SystemDiagnostics type has been completely restructured from Windows/GPU-centric diagnostics to macOS-centric diagnostics. This breaking change removes several fields (e.g., windowsVersion, gpuInfo, allGpus, renderingStatus, graphicsCaptureSupported, d3D11VideoProcessorAvailable) and introduces new macOS-specific fields (macosVersion, screenCaptureSupported).

Ensure that all TypeScript consumers of this type have been updated to reference the new field names and structure.

#!/bin/bash

# Search for usages of removed SystemDiagnostics fields
echo "=== Searching for removed SystemDiagnostics fields ==="
rg -n --type=ts --type=tsx -C3 '\b(windowsVersion|gpuInfo|allGpus|renderingStatus|graphicsCaptureSupported|d3D11VideoProcessorAvailable)\b'

echo ""
echo "=== Searching for SystemDiagnostics type usage ==="
rg -n --type=ts --type=tsx -C3 'SystemDiagnostics'
🤖 Prompt for AI Agents
In apps/desktop/src/utils/tauri.ts around line 501, the SystemDiagnostics type
was changed from a Windows/GPU-centric shape to a macOS-centric shape (removed
fields: windowsVersion, gpuInfo, allGpus, renderingStatus,
graphicsCaptureSupported, d3D11VideoProcessorAvailable; added macosVersion,
screenCaptureSupported), which is a breaking change; locate all TypeScript
usages (run the provided ripgrep commands), update each consumer to use the new
field names or accessors, or restore a compatibility union/interface or mapping
function that populates the old fields from the new diagnostics where
appropriate, and add unit/type tests to confirm no remaining references to
removed fields remain and compilation succeeds.

Comment on lines +742 to +745
impl Drop for BaseAddrLockGuard<'_> {
fn drop(&mut self) {
let _ = unsafe { self.0.unlock_lock_base_addr(self.1) };
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Log unlock failures in Drop.

The unlock_lock_base_addr result is silently ignored. While Drop cannot propagate errors, failures during unlock should be logged to aid debugging.

🔎 Proposed fix to log unlock errors
 impl Drop for BaseAddrLockGuard<'_> {
     fn drop(&mut self) {
-        let _ = unsafe { self.0.unlock_lock_base_addr(self.1) };
+        if let Err(e) = unsafe { self.0.unlock_lock_base_addr(self.1) } {
+            error!("Failed to unlock pixel buffer base address: {:?}", e);
+        }
     }
 }

As per coding guidelines, Results should not be ignored.

📝 Committable suggestion

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

Suggested change
impl Drop for BaseAddrLockGuard<'_> {
fn drop(&mut self) {
let _ = unsafe { self.0.unlock_lock_base_addr(self.1) };
}
impl Drop for BaseAddrLockGuard<'_> {
fn drop(&mut self) {
if let Err(e) = unsafe { self.0.unlock_lock_base_addr(self.1) } {
error!("Failed to unlock pixel buffer base address: {:?}", e);
}
}
}
🤖 Prompt for AI Agents
In crates/recording/src/output_pipeline/macos_segmented_ffmpeg.rs around lines
742 to 745, the Drop impl for BaseAddrLockGuard currently ignores the Result
from unlock_lock_base_addr; change it to call unlock_lock_base_addr inside the
unsafe block, check the Result, and log any Err using the project's logger
(include the base addr/self.1 and a clear message), but do not panic — just log
the failure since Drop cannot return errors.

@richiemcilroy richiemcilroy merged commit 444df9e into main Dec 19, 2025
17 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants