Recording-Pipeline
The four steps of a recording — MQTT command from the StudyControlApp, OBS recording through the OBSController, the cockpit_recording flag in the telemetry log, and the manual FFmpeg step.
This page describes the Recording-Pipeline as a sequence of four steps. Each section names the repository, what happens there, and what you do. No repository holds the whole chain, so it is documented here.
All statements come from the code, checked on 2026-09-18 in the repositories
study-control-app, beamngpy (package EcoSimlabPy) and obscontroller.
The OBSController is not public. Step 4 is not in any repository.
Prerequisites
- OBS Studio with the WebSocket server enabled. The OBSController connects
to it. Password authentication must be off (
obscontroller/README.md). - An MQTT broker. All three steps talk over the same broker.
- The OBSController binary and its
.envfile. The.envfile setsRECORDING_CONTROL_TOPIC,MQTT_BROKERandOBS_WS. The status topic defaults tofrom_obs_controller/recording_status. - A running trip in the StudyControlApp. The recording button is bound to a trip and to its participant and treatment.
- FFmpeg for step 4.
Step 1 — Start and stop the recording from the StudyControlApp
Repository: study-control-app
(packages/frontend/src/features/execution/composables/useCockpitRecording.ts).
The frontend publishes one MQTT message on the topic
from_sca_frontend/recording_control. The payload holds the required field
recording and an optional file name:
{
"recording": true,
"options": {
"fileName": "tripID:7-subjectId:3-treatmentId:1-driverProfile:NONE"
}
}
The app builds the file name from the trip, the participant, the treatment and
the driver profile. Without a profile, the label is NONE.
What you do: Open the trip, then select the recording button. The button
toggles: it sends recording: true when no recording runs, and
recording: false when one runs.
The app does not read the state from OBS. It polls the backend endpoint
getCockpitRecordingStatus, which returns the flag of the latest telemetry
row of the trip (packages/backend/src/domains/execution/data.repository.ts).
The button therefore reports success only after step 3 has delivered the flag.
Step 2 — The OBSController records in OBS Studio
Repository: obscontroller (Go, internal GitLab, not public).
The controller subscribes to the control topic and drives OBS over the
WebSocket API (main.go, function handleRecordingControl):
- It parses the payload. If the JSON is invalid or the field
recordingis missing, it logs one line and leaves the recording untouched. - It reads the current OBS recording state. It only acts on a real change of state.
- Before a start, it writes
options.fileNameinto the OBS profile settingOutput/FilenameFormatting. A failure here only warns, because the recording matters more than its name. - It starts or stops the recording and publishes the new state on
from_obs_controller/recording_status.
What you do: Start the controller next to OBS Studio before the trip. Check its log if the recording does not start.
The controller is not public. To get the binary or the source, ask the team.
Step 3 — The flag cockpit_recording in the telemetry log
Repository: beamngpy, package EcoSimlabPy.
EcoSimLabPy subscribes to from_obs_controller/recording_status
(EcoSimlabPy/api/v2_topics.py). It stores the field recording as
cockpit_recording (EcoSimlabPy/vehicle/vehicle_controller.py) and puts it
into the control state of every tick. The flag is part of the state contract
(EcoSimlabPy/contract/beamng_state_v2.py) and is therefore in every message
of the driving data.
The StudyControlApp reads the flag into the telemetry row as cockpitRecording
(packages/backend/src/domains/execution/beamngState.v2Reader.ts) and stores
it with the trip.
What you do: Nothing during the run. After the run, use the flag to cut the
driving data to the recorded part. The flag is false for every tick outside
the recording.
Step 4 — Post-processing with FFmpeg to Full HD
No repository. Manual work.
OBS Studio writes the file in the resolution of the recorded scene. The video studies need Full HD. The conversion runs with FFmpeg outside of all three repositories.
What you do: Convert the file yourself after the study. There is no script
and no documented command line for this step in study-control-app,
beamngpy or obscontroller.
Result data
| Artifact | Where | Name |
|---|---|---|
| Video file | The OBS recording folder | From options.fileName, unless OBS placeholders change it |
| Driving data | The StudyControlApp database, per trip | Column cockpitRecording per row |
The video and the driving data are two files. The flag is the only link between them.
Open questions
- The FFmpeg step has no script and no documented command line. The target resolution is Full HD; the exact call is not recorded anywhere.
- The OBSController is not public and has no license. A short repository document follows when the repository is public.
- OBS replaces characters that the file system forbids. Which file names the StudyControlApp produces in such a case is unchecked.