# How VPhone Screen Recorder Balances Frame Rate and Disk Throughput During Long Recordings

> Discover how VPhone Screen Recorder optimizes frame rate and disk throughput for long recordings. Learn about its timer-driven loop and back-pressure system for smooth performance.

- Repository: [Lakr/vphone-cli](https://github.com/Lakr233/vphone-cli)
- Tags: performance
- Published: 2026-09-13

---

**VPhone Screen Recorder uses a 30 Hz timer-driven capture loop combined with `AVAssetWriter` real-time back-pressure signals to maintain target frame rates when disk I/O permits, while automatically dropping frames when storage throughput cannot keep pace.**

The `VPhoneScreenRecorder` class in the [Lakr233/vphone-cli](https://github.com/Lakr233/vphone-cli) repository implements a resilient screen capture system designed for extended recording sessions of virtual machine displays. By leveraging `AVAssetWriter` with real-time encoding constraints and deterministic timer-based scheduling, the recorder gracefully balances the demand for consistent 30 fps video against variable disk write speeds without blocking the main thread or exhausting system memory.

## Timer-Driven Capture Architecture

The recorder establishes a fixed cadence using a `Timer` that fires at **30 Hz** (lines 96-101 in [`sources/vphone-cli/VPhoneScreenRecorder.swift`](https://github.com/Lakr233/vphone-cli/blob/main/sources/vphone-cli/VPhoneScreenRecorder.swift)). Each tick invokes `captureFrame()`, creating a deterministic sampling rate that decouples the capture scheduling from the underlying graphics pipeline performance.

This approach ensures consistent timing metadata regardless of transient system load. The timer maintains the nominal frame rate until the storage subsystem introduces back-pressure, at which point the encoder naturally skips intervals rather than buffering indefinitely.

## Back-Pressure Signals and Frame Dropping

Before appending any pixel data, the implementation checks two critical readiness conditions to prevent overwhelming slow disks or blocking the UI:

- **`videoInput.isReadyForMoreMediaData`** — This property is examined both within `captureFrame()` and immediately prior to frame append operations (lines 77-78 and 97-98). When the `AVAssetWriterInput` reports it cannot accept additional media data, the current frame capture is skipped, effectively dropping the frame to match available disk throughput.

- **`screenshotInFlight`** — A boolean guard prevents overlapping screenshot requests (lines 88-91), ensuring only one graphics capture operation executes at any moment. This concurrency control eliminates race conditions that could otherwise spike memory usage or corrupt the video stream.

When either condition fails, the recorder silently drops the frame and waits for the next 30 Hz timer interval, maintaining system responsiveness.

## Real-Time Encoding Configuration

The recorder initializes its `AVAssetWriterInput` with `expectsMediaDataInRealTime` set to `true` (line 71). This configuration instructs the encoder to operate in a streaming mode optimized for live capture rather than file conversion:

```swift
// Configuration excerpt from VPhoneScreenRecorder.swift
videoInput.expectsMediaDataInRealTime = true

```

With this setting enabled, `AVAssetWriter` internally manages back-pressure by dropping frames when its buffer thresholds are reached, rather than allocating unbounded memory to queue pending writes. This behavior is essential for recordings lasting hours, as it bounds the memory footprint regardless of disk speed fluctuations.

## Synchronization and Memory Efficiency

Frame timing follows a strict **30 fps timescale** using `CMTime(value: frameCount, timescale: 30)` (lines 46-48), ensuring monotonically increasing timestamps that remain synchronized with the audio track (if present) and avoid variable frame rate complexities.

The implementation utilizes a pixel-buffer pool for efficient memory reuse, eliminating expensive allocations during the capture hot path:

```swift
// Frame timing example from the source
let presentationTime = CMTime(value: frameCount, timescale: 30)
pixelBufferAdaptor.append(pixelBuffer, withPresentationTime: presentationTime)

```

This deterministic timing model simplifies long-duration recording sessions by treating frame drops as temporal gaps rather than requiring complex variable-frame-rate metadata adjustments.

## Practical Implementation

To start recording a virtual machine view with automatic frame rate adaptation:

```swift
let recorder = VPhoneScreenRecorder()
try recorder.startRecording(view: vmView)

// Later, to finalize:
Task {
    if let url = await recorder.stopRecording() {
        print("Recording saved to \(url.path)")
    }
}

```

For single-frame capture without video encoding:

```swift
Task {
    try await recorder.copyScreenshotToPasteboard(view: vmView)
}

```

## Summary

- **Fixed 30 Hz sampling** creates a predictable capture cadence that simplifies timestamp management and synchronization.
- **`isReadyForMoreMediaData` checks** provide disk-aware back-pressure, allowing the recorder to skip frames when storage I/O saturates.
- **`expectsMediaDataInRealTime`** enables `AVAssetWriter` to handle back-pressure internally, preventing unbounded memory growth during extended recordings.
- **Concurrency guards** (`screenshotInFlight`) and pixel-buffer pooling minimize memory churn and prevent race conditions.
- The architecture gracefully degrades from 30 fps to lower effective rates based on disk throughput while maintaining UI responsiveness.

## Frequently Asked Questions

### What happens when the disk cannot write frames fast enough?

When disk throughput drops below the 30 fps data rate, `videoInput.isReadyForMoreMediaData` returns `false`, causing `captureFrame()` to skip the current capture cycle. The timer continues firing at 30 Hz, but frames are dropped until the writer catches up, resulting in a temporarily lower effective frame rate without blocking the main thread.

### Why does VPhone use a fixed 30 Hz timer instead of variable frame rates?

The fixed timer interval (lines 96-101) generates deterministic `CMTime` stamps with a constant 30 fps timescale (lines 46-48). This simplifies encoding logic, ensures compatibility with standard video players, and prevents timestamp drift that could occur with adaptive capture intervals during variable system load.

### How does the recorder prevent memory exhaustion during hours-long recordings?

Setting `expectsMediaDataInRealTime = true` (line 71) configures `AVAssetWriter` to drop incoming frames rather than buffer them when disk writes lag behind captures. Combined with pixel-buffer pooling and the `screenshotInFlight` guard, this bounds the memory footprint regardless of recording duration or storage speed fluctuations.

### Can I modify the target frame rate from 30 fps to 60 fps?

While the source code hardcodes `timescale: 30` and a 30 Hz timer interval, you could theoretically adjust these values in [`VPhoneScreenRecorder.swift`](https://github.com/Lakr233/vphone-cli/blob/main/VPhoneScreenRecorder.swift). However, increasing the frame rate would proportionally increase disk write requirements, potentially triggering more aggressive frame dropping on slower storage systems.