How VPhone Screen Recorder Balances Frame Rate and Disk Throughput During Long Recordings
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 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). 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 withincaptureFrame()and immediately prior to frame append operations (lines 77-78 and 97-98). When theAVAssetWriterInputreports 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:
// 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:
// 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:
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:
Task {
try await recorder.copyScreenshotToPasteboard(view: vmView)
}
Summary
- Fixed 30 Hz sampling creates a predictable capture cadence that simplifies timestamp management and synchronization.
isReadyForMoreMediaDatachecks provide disk-aware back-pressure, allowing the recorder to skip frames when storage I/O saturates.expectsMediaDataInRealTimeenablesAVAssetWriterto 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. However, increasing the frame rate would proportionally increase disk write requirements, potentially triggering more aggressive frame dropping on slower storage systems.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →