Skip to content

Conversation

@Brendonovich
Copy link
Contributor

@Brendonovich Brendonovich commented Sep 17, 2025

Summary by CodeRabbit

  • New Features

    • Start-time-aware timestamping in studio-mode recordings for consistent frame timing.
    • Cross-platform support (macOS and Windows) for aligned video timestamps.
  • Bug Fixes

    • Improved audio/video synchronization and reduced drift/jitter in studio-mode recordings, especially on Windows screen capture.
  • Improvements

    • Smoother playback due to more precise PTS sequencing.
    • Enhanced error logging for encoder failures to aid diagnostics.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Sep 17, 2025

Walkthrough

Adds start_time-aware PTS alignment to studio-mode capture pipelines by introducing a start_time parameter, computing elapsed durations, and converting them to PTS via a new H264Encoder::get_pts. Adjusts H.264 encoder time_base usage, updates Direct3D paths (macOS/Windows) to set frame PTS, wires start_time through studio_recording, and updates a recording example import.

Changes

Cohort / File(s) Summary
H.264 encoder time base & PTS API
crates/enc-ffmpeg/src/video/h264.rs
Use input_config.time_base instead of frame_rate.invert(); add H264Encoder::TIME_BASE; add get_pts(Duration) -> i64; remove output stream time_base set (left as comment).
Studio-mode pipeline start_time integration
crates/recording/src/capture_pipeline.rs
Extend MakeCapturePipeline::make_studio_mode_pipeline with start_time: Timestamps; plumb parameter through CMSampleBufferCapture (unused) and Direct3DCapture (macOS/Windows); compute elapsed since first timestamp offset by start_time; set FFmpeg frame PTS via encoder.get_pts(elapsed); adjust logging in Windows path.
Studio recording wiring
crates/recording/src/studio_recording.rs
Pass start_time to ScreenCaptureMethod::make_studio_mode_pipeline in create_segment_pipeline.
Example import update
crates/recording/examples/recording-cli.rs
Switch Actor::builder import from instant_recording::Actor to studio_recording::Actor; call chain unchanged.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  participant SR as StudioRecording
  participant CP as CapturePipeline (studio mode)
  participant D3D as Direct3DCapture
  participant ENC as H264Encoder
  participant FF as FFmpeg Frame

  Note over SR,CP: Pipeline creation with start_time (Timestamps)
  SR->>CP: make_studio_mode_pipeline(..., start_time)
  CP->>D3D: initialize(start_time)

  loop For each captured frame
    D3D->>D3D: Compute elapsed = (ts - first_ts) aligned to start_time
    D3D->>ENC: get_pts(elapsed)
    ENC-->>D3D: pts (i64)
    D3D->>FF: set pts
    Note right of FF: PTS aligned to start_time
  end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Poem

I tap my paws to measured time,
Start clocks align, encoders chime.
Frames hop in sync, no drift to dread—
A carrot metronome in my head.
With PTS neat and timelines true,
This bunny signs off: “Render, woo!” 🥕✨

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 33.33% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check ✅ Passed The title "set pts in ffmpeg screen encoder" is concise and accurately reflects the primary change in the diff: adding PTS handling/time_base usage for the FFmpeg H264 screen encoder (new H264Encoder::get_pts and wiring into capture pipelines). It is specific enough for a reviewer scanning PR history to understand the main intent.
✨ Finishing touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch fix-ffmpeg-screen-encoder

Tip

👮 Agentic pre-merge checks are now available in preview!

Pro plan users can now enable pre-merge checks in their settings to enforce checklists before merging PRs.

  • Built-in checks – Quickly apply ready-made checks to enforce title conventions, require pull request descriptions that follow templates, validate linked issues for compliance, and more.
  • Custom agentic checks – Define your own rules using CodeRabbit’s advanced agentic capabilities to enforce organization-specific policies and workflows. For example, you can instruct CodeRabbit’s agent to verify that API documentation is updated whenever API schema files are modified in a PR. Note: Upto 5 custom checks are currently allowed during the preview period. Pricing for this feature will be announced in a few weeks.

Please see the documentation for more information.

Example:

reviews:
  pre_merge_checks:
    custom_checks:
      - name: "Undocumented Breaking Changes"
        mode: "warning"
        instructions: |
          Pass/fail criteria: All breaking changes to public APIs, CLI flags, environment variables, configuration keys, database schemas, or HTTP/GraphQL endpoints must be documented in the "Breaking Change" section of the PR description and in CHANGELOG.md. Exclude purely internal or private changes (e.g., code not exported from package entry points or explicitly marked as internal).

Please share your feedback with us on this Discord post.


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.

@Brendonovich Brendonovich merged commit 49b87e6 into main Sep 17, 2025
14 of 15 checks passed
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: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
crates/recording/src/studio_recording.rs (1)

808-813: Avoid underflow on Duration math and unify PTS rounding

Subtracting Durations can underflow if a late/OOO frame arrives; some blocks round PTS, others truncate. Guard with checked_sub and round consistently.

Mic:

-                let elapsed = timestamp.duration_since(start_time)
-                    - first_timestamp.duration_since(start_time);
-                frame.set_pts(Some((elapsed.as_secs_f64() * rate) as i64));
+                if let Some(elapsed) = timestamp
+                    .duration_since(start_time)
+                    .checked_sub(first_timestamp.duration_since(start_time))
+                {
+                    frame.set_pts(Some((elapsed.as_secs_f64() * rate).round() as i64));
+                } else {
+                    continue;
+                }

System audio:

-                let elapsed = timestamp.duration_since(start_time)
-                    - first_timestamp.duration_since(start_time);
-                frame.set_pts(Some((elapsed.as_secs_f64() * rate).round() as i64));
+                if let Some(elapsed) = timestamp
+                    .duration_since(start_time)
+                    .checked_sub(first_timestamp.duration_since(start_time))
+                {
+                    frame.set_pts(Some((elapsed.as_secs_f64() * rate).round() as i64));
+                } else {
+                    continue;
+                }

Camera:

-                let elapsed = timestamp.duration_since(start_time)
-                    - first_timestamp.duration_since(start_time);
-                frame.set_pts(Some((elapsed.as_secs_f64() * rate) as i64));
+                if let Some(elapsed) = timestamp
+                    .duration_since(start_time)
+                    .checked_sub(first_timestamp.duration_since(start_time))
+                {
+                    frame.set_pts(Some((elapsed.as_secs_f64() * rate).round() as i64));
+                } else {
+                    continue;
+                }

Also applies to: 863-866, 916-919

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

323-325: Harden Windows software-encoder path: no unwrap, prevent Duration underflow, avoid shadowing

  • Avoid panic on oneshot send.
  • Use checked_sub to prevent underflow if timestamps regress.
  • Don’t shadow first_timestamp with a different type; improves clarity.
-                                    timestamp_tx.send(timestamp).unwrap();
+                                    let _ = timestamp_tx.send(timestamp);
-                    let mut first_timestamp = None;
+                    let mut first_timestamp: Option<Timestamp> = None;
@@
-                        let first_timestamp = first_timestamp.get_or_insert(timestamp);
+                        let first_ts = first_timestamp.get_or_insert(timestamp);
@@
-                        let elapsed = timestamp.duration_since(start_time)
-                            - first_timestamp.duration_since(start_time);
-                        ff_frame.set_pts(Some(encoder.get_pts(elapsed)));
+                        if let Some(elapsed) = timestamp
+                            .duration_since(start_time)
+                            .checked_sub(first_ts.duration_since(start_time))
+                        {
+                            ff_frame.set_pts(Some(encoder.get_pts(elapsed)));
+                        } else {
+                            continue;
+                        }

Also applies to: 355-375

🧹 Nitpick comments (3)
crates/enc-ffmpeg/src/video/h264.rs (1)

126-127: Make stream time_base explicit (or drop the dead constant)

Relying on muxer defaults can cause subtle PTS rescale issues; also TIME_BASE is unused.

-        // output_stream.set_time_base((1, H264Encoder::TIME_BASE));
+        // Make stream time_base explicit to avoid muxer defaults differing from encoder PTS.
+        output_stream.set_time_base(input_config.time_base);
-    const TIME_BASE: i32 = 15000;
+    // removed unused TIME_BASE

Also applies to: 152-153

crates/recording/src/studio_recording.rs (1)

468-471: Prefer tracing over println! for consistency

Use tracing::{info} to match the rest of the module’s logging.

-            println!("recording successfully stopped");
+            tracing::info!("recording successfully stopped");
crates/recording/src/capture_pipeline.rs (1)

50-59: macOS studio path ignores start_time

Underscoring avoids warnings, but this leaves PTS alignment asymmetric vs Windows. Confirm AVFoundation path doesn’t require start_time for A/V sync, or plan follow‑up to propagate it.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 5fdf8fc and b78033f.

⛔ Files ignored due to path filters (1)
  • Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (4)
  • crates/enc-ffmpeg/src/video/h264.rs (5 hunks)
  • crates/recording/examples/recording-cli.rs (1 hunks)
  • crates/recording/src/capture_pipeline.rs (6 hunks)
  • crates/recording/src/studio_recording.rs (1 hunks)
🧰 Additional context used
📓 Path-based instructions (2)
{crates,apps/cli,apps/desktop/src-tauri}/**/*.rs

📄 CodeRabbit inference engine (AGENTS.md)

{crates,apps/cli,apps/desktop/src-tauri}/**/*.rs: Rust code must be formatted with rustfmt and adhere to workspace clippy lints
Rust module files should use snake_case names

Files:

  • crates/recording/examples/recording-cli.rs
  • crates/enc-ffmpeg/src/video/h264.rs
  • crates/recording/src/studio_recording.rs
  • crates/recording/src/capture_pipeline.rs
{crates,apps/cli,apps/desktop/src-tauri}/**/{src,tests}/**/*.rs

📄 CodeRabbit inference engine (AGENTS.md)

Rust tests should live in src (inline/unit) or tests (integration) directories

Files:

  • crates/enc-ffmpeg/src/video/h264.rs
  • crates/recording/src/studio_recording.rs
  • crates/recording/src/capture_pipeline.rs
🧬 Code graph analysis (2)
crates/recording/examples/recording-cli.rs (2)
crates/recording/src/studio_recording.rs (1)
  • builder (129-131)
crates/recording/src/instant_recording.rs (1)
  • builder (154-156)
crates/recording/src/studio_recording.rs (1)
crates/video-decode/src/ffmpeg.rs (1)
  • start_time (98-100)
⏰ 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). (4)
  • GitHub Check: Clippy
  • GitHub Check: Build Desktop (aarch64-apple-darwin, macos-latest)
  • GitHub Check: Build Desktop (x86_64-pc-windows-msvc, windows-latest)
  • GitHub Check: Analyze (rust)
🔇 Additional comments (5)
crates/enc-ffmpeg/src/video/h264.rs (1)

108-110: Confirm encoder time_base source

Switching to input_config.time_base is fine if all queued frame PTS are computed in the same base (they are via get_pts). Please confirm VideoInfo::time_base matches capture timestamps end‑to‑end (Windows + macOS), or document the assumption.

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

59-71: Switch to studio_recording::Actor looks good

Call site change aligns with the studio‑mode pipeline. No issues spotted.

crates/recording/src/studio_recording.rs (1)

751-757: Start-time propagation into screen pipeline: LGTM

Passing start_time into make_studio_mode_pipeline is correct and consistent with the rest of the pipeline.

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

285-287: Explicit tracing on native-encoder fallback: LGTM

Clearer diagnostics; good change.


21-31: API change: in-repo impls & callers updated — no action required

All MakeCapturePipeline impls and the call site in this repo were updated to accept the new start_time parameter:

  • crates/recording/src/capture_pipeline.rs — impl for screen_capture::CMSampleBufferCapture: fn make_studio_mode_pipeline(..., _start_time: Timestamps).
  • crates/recording/src/capture_pipeline.rs — impl for screen_capture::Direct3DCapture: fn make_studio_mode_pipeline(..., start_time: Timestamps).
  • crates/recording/src/studio_recording.rs — call passes start_time into ScreenCaptureMethod::make_studio_mode_pipeline (around line ~751).

Comment on lines +211 to +214
pub fn get_pts(&self, duration: Duration) -> i64 {
(duration.as_secs_f32() * self.config.time_base.denominator() as f32
/ self.config.time_base.numerator() as f32) as i64
}
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

Fix PTS conversion precision and rounding

Using f32 loses precision and truncation can drift PTS over long runs. Use f64 and round to nearest tick.

-    pub fn get_pts(&self, duration: Duration) -> i64 {
-        (duration.as_secs_f32() * self.config.time_base.denominator() as f32
-            / self.config.time_base.numerator() as f32) as i64
-    }
+    pub fn get_pts(&self, duration: Duration) -> i64 {
+        let tb = self.config.time_base;
+        (duration.as_secs_f64() * tb.denominator() as f64 / tb.numerator() as f64).round() as i64
+    }
📝 Committable suggestion

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

Suggested change
pub fn get_pts(&self, duration: Duration) -> i64 {
(duration.as_secs_f32() * self.config.time_base.denominator() as f32
/ self.config.time_base.numerator() as f32) as i64
}
pub fn get_pts(&self, duration: Duration) -> i64 {
let tb = self.config.time_base;
(duration.as_secs_f64() * tb.denominator() as f64 / tb.numerator() as f64).round() as i64
}
🤖 Prompt for AI Agents
In crates/enc-ffmpeg/src/video/h264.rs around lines 211 to 214, the PTS
conversion currently uses f32 and a truncating cast which loses precision and
causes drift; change the computation to use f64 (use duration.as_secs_f64()),
cast the time_base numerator/denominator to f64, perform the division and
multiplication in f64, round the resulting value to the nearest integer (e.g.
.round()) and then cast to i64 to return the final PTS value.

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