Overview
TopicRecorder writes subscribed streams straight into an MCAP file. MCAP is Foxglove’s open recording format, so you can step through a finished recording in Foxglove Studio, or parse it offline with the mcap command-line tool or Python’s mcap library.
Recording rides on the same subscription machinery: recorder.record(stream.subscribe()) registers a channel, and everything registered starts hitting disk once you call recorder.start(path). Both device streams and global transform streams can be recorded.
Matching example: examples/python/wuji_glove/2.recording.py.
For the full recording API and compression options, see Data Recording and Offline Processing. This page covers only how to run it and what the measurements show.
The C SDK has no matching recording API. This page covers Python.
Prerequisites
Hardware
This example only reads. It writes no device parameters.
Software
Before You Start
Read Subscribing With Callbacks first. Recording takes the subscription handles returned bysubscribe(), and the queue semantics are identical.
Data Flow
File Structure
Recording six device streams plus two global streams for 5 seconds produces these channels:
Messages use
json encoding and each channel carries its own schema, so Foxglove Studio shows field names directly without a separate message-definition file.
The file header’s library field reads mcap-rs-0.24.0, the SDK’s built-in Rust writer.
Compression Modes
Recording the same 3 streams for 5 seconds, about 1970 frames:
JSON encoding repeats field names, which compresses well. Choose
zstd for long recordings, lz4 where write latency matters, and none only when another process needs to stream the file while it’s still being written.
Looking at a single lz4 chunk, 3101 bytes of raw data compress to 938.
Quality Metrics
The summary returned byhandle.stop() carries a quality object:
Three things stand out:
sync_rate falls to 0.857 once tf joins. The six device streams all sit near 130 Hz and line up with each other. tf measures 872 Hz and tf_static only 1 Hz, an order of magnitude apart from the rest, so the sync check naturally fails. Mixing rate bands in one recording makes this number low by design, and it doesn’t mean the data is bad.
max_sync_offset_ms can show one large outlier. The 3-stream run measured 2966 ms while its avg was only 7.89 ms. That comes from the first frames of each channel not yet being aligned right after recording starts. Judge recording quality by avg and frame_drop_rate, and don’t let a startup spike in max mislead you.
duration_s is wall-clock time, not effective recording time. Pausing breaks the correspondence, as the next section shows.
Pause and Resume
Besidesstop(), the handle offers pause() and resume(), which the example doesn’t demonstrate. Recording 2 seconds, pausing 2 seconds, then recording 2 more with 3 streams:
The frame count matches 4 seconds of effective recording, with nothing written while paused.
duration_s still follows the wall clock, so deriving a rate from total_frames / duration_s comes out low.
That run also raised spc_alert_count to 1. Channels go silent while paused and statistical process control flags it as an anomaly. That’s expected, not a fault.
The handle also exposes subscribe_alerts(), subscribe_metrics(), and subscribe_status(), which deliver alerts, quality metrics, and status during recording rather than making you wait for stop().
Runtime Handling
This block in the example is deliberate:
asyncio.shield keeps stop() from being cancelled by an outer interrupt. Without that guard, Ctrl+C can kill the task before MCAP finishes writing its index, leaving a file whose summary can’t be read.
Integration Steps
record() call before start(). Channels registered afterward never reach the file.
Running It
Reading a Recording
With themcap command-line tool installed:
json.loads(message.data) gives you the same field structure as a live subscription.
Adapting It
Register Only the Channels You Need
All eight streams with zstd measured 6.01 MB over 5 seconds, roughly 72 MB per minute.tf alone accounts for 4361 messages, more than the six device streams combined. Decide which streams you actually need before a long capture.
Lower the Rate Before Recording
Subscription handles support rate control. Dropping 130 Hz to 30 Hz cuts file size to under a quarter. See Adjusting Stream Rate.Monitor Quality While Recording
Rather than waiting forstop() to learn whether frames dropped, read metrics live with handle.subscribe_metrics() and alerts with handle.subscribe_alerts(). Any long unattended capture should wire up both.
Related
- Subscribing With Callbacks: frame structures for all six streams and invalid-channel handling
- Subscribing Asynchronously: the subscription queue’s buffering boundary
- Replaying Data Offline: rerun algorithms over a finished recording
- Adjusting Stream Rate: lower the rate before recording to control file size
- Data Subscription: subscription model and lifecycle