Documentation
Hardware Dashcam Architecture
Technical specification for the continuous multi-camera hardware dashcam subsystem, including video pipeline architecture, WebVTT telemetry sidecar generation, crash resilience mechanisms, storage lifecycle, and Home Assistant integration.
1. System Architecture & Components
The dashcam is implemented as a headless, privileged background service running inside modehelper (android.uid.system). It attaches directly to the vehicle’s Extended View System (EVS) rendering engine without intermediate user-interface dependencies.
┌──────────────────────────────────────────────────────────────┐
│ modehelper (android.uid.system) │
│ │
│ ┌─────────────┐ IGraphicBufferProducer ┌─────────┐ │
│ │ EvsClient │ ─────────────────────────────> │ EVS HAL │ │
│ └──────┬──────┘ └─────────┘ │
│ │ Surface │
│ ▼ │
│ ┌──────────────┐ │
│ │ DashRecorder │ │
│ └──────┬───────┘ │
│ │ FragmentedMp4 (1 fragment per key frame, synced) │
│ ▼ │
│ dash_*.mp4 │
│ │
│ ┌─────────────┐ CarPropertyManager ┌──────────────────┐ │
│ │ VttWriter │ <───────────────────── │ Telemetry Poll │ │
│ └──────┬──────┘ └──────────────────┘ │
│ │ Cues (1 Hz) │
│ ▼ │
│ dash_*.vtt │
└──────────────────────────────────────────────────────────────┘
Core Components
EvsClient.java: Interfaces withbdstar.render.engineover Binder, requesting thedvrfunction and supplying a surface.DashRecorder.java: ConfiguresMediaCodec, receives raw video frames, writes each segment as a fragmented MP4 (FragmentedMp4), and handles file rotation.Vtt.java: Samples vehicle telemetry once per second and formats synchronized WebVTT subtitle tracks.DashReceiver.java: BroadcastReceiver handling external start/stop triggers and system lifecycle events.
Control Interface
The recorder is controlled via explicit broadcast intents:
# Start recording
am broadcast -a com.geely.modehelper.DASHCAM --ei on 1
# Stop recording
am broadcast -a com.geely.modehelper.DASHCAM --ei on 0
Operational Lifecycle
- Auto-Start: Triggers once on system boot after the first successful vehicle property read.
- Continuous Operation: Records continuously across all vehicle states, including
PARK(monitoring parked vehicle surroundings). - Close Before Sleep: The unit suspends rather than shutting down. When the car goes off it first broadcasts
ACTION_SHUTDOWN_HU(vendor) and the cameras stop at that moment; the screen goes off about three seconds later. On the first of these, or on CarPowerManagerSUSPEND_ENTER, the open segment closes at once (a fragmented MP4 needs no key frame to end), and the next one starts at the first key frame when pictures return.SHUTDOWN_ENTERstops the recorder cleanly. - Self-Restart: If the recording loop stops on its own (EVS not ready, a codec error), ModeHelper restarts it while recording is wanted: at once, then after 30 s, 1, 2, 4, 8 and at most 10 minutes. Turning the dashcam off is never undone.
- Status File:
dashcam/recorder.stateholdsstate, the open segment’sstem, abeatrefreshed every 5 s, and the lasterror. Drive Assist reads it to show the live segment as recording and to refuse recovering it. - Graceful Teardown: Intercepts
Intent.ACTION_SHUTDOWNto write the last fragment and the segment index before the process dies.adb rebootbypasses the framework shutdown, so it never sends this broadcast and always leaves the active segment as an orphan.
2. Video Pipeline & Storage Budget
The native EVS engine renders a composite 2x2 camera quad (front, rear, left mirror, right mirror) directly into the hardware encoder’s input surface.
Video Encoding Specifications
| Parameter | Configuration | Notes |
|---|---|---|
| Resolution | 1920 x 800 | 4 camera views in 2x2 layout (~960x400 per camera) |
| Codec | H.264 / AVC | video/avc, Baseline profile |
| Bitrate | 16.0 Mbps | Target only; no bitrate mode is set, so the encoder default applies |
| Frame Rate | 25.0 fps | Native EVS camera sensor stream rate |
| Keyframe Interval | 1.0 s (IFRAME_SEC = 1) |
Enables fast seeking and bounds crash data loss |
| Segment Length | 300 seconds (5 min) | ~600 MB per segment (measured) |
| Timestamp Model | Monotonic segment elapsed | Eliminates EVS hardware timestamp jitter and muxer rejections |
Storage Directory & Permissions
Video clips and subtitle sidecars are stored in Drive Assist’s external files directory:
/sdcard/Android/data/com.geely.drivemem/files/dashcam/
- On Android 9 (API 28),
modehelper(running asuid system) writes directly to this path, while Drive Assist reads it with zero runtime storage permissions (READ_EXTERNAL_STORAGEis not required). - Held clips protected from eviction are stored in
/files/dashcam/keep/.
Ring Buffer Budget Management
DashRecorder.enforceBudget runs on the close thread after each segment
closes, while the next one records; the live segment is never touched. The
logic is SegmentFiles.repair then SegmentFiles.evict:
- A dead fragmented
.mp4.tmpis repaired into a clip (see section 4). An older build’s.mp4.tmp(nomoov) is deleted once 10 minutes cold if a.h264beside it holds the same frames. Subtitles and thumbnails with no video left beside them are deleted once 10 minutes cold. - Every file in
dashcam/andkeep/counts against the budget (default 10 GB, about 85 minutes at 16 Mbit/s; set from the Recordings panel). - Over budget, whole segments go oldest first: closed clips (
.mp4) and orphans (a.h264, or a.mp4.tmpthat could not be repaired, untouched for 10 minutes), each with all its sidecars. - Never evicted: anything in
keep/, and any clip with a<stem>.holdmarker beside it. Held clips still count against the budget.
A segment marked for keeping (a parked-monitoring event, or Hold on a clip
that is still recording) leaves a <stem>.hold marker. The recorder moves
that clip into keep/ the moment the segment closes
(SegmentFiles.keepIfHeld).
3. WebVTT Telemetry Sidecar (.vtt)
Each video segment is accompanied by a synchronized WebVTT subtitle track containing frame-accurate telemetry sampled at 1 Hz.
Sample Track
WEBVTT
00:00:00.000 --> 00:00:01.000
2026-09-26 09:03:32 · 62 km/h · D · 24.5° · NNE 22° · max 60
00:00:01.000 --> 00:00:02.000
2026-09-26 09:03:33 · 64 km/h · D · 24.5° · NNE 23° · max 60
Data Schema & Sources
| Metric | Source | Property ID / Provider |
|---|---|---|
| Time | System clock | wall-clock time of the sample |
| Speed | VHAL | 291504647 (PERF_VEHICLE_SPEED, km/h) |
| Gear | VHAL | 289408001 (GEAR_SELECTION: 1=N, 2=R, 4=P, 8=D) |
| Ambient Temp | HVAC ECU | 557884279 (AC_AMBIENT_TEMP, °C) |
| Heading | Android GNSS | GPS bearing, only above 3 km/h |
| Speed limit | VHAL | camera sign, then navigation limit, then navigation speed; only plausible values (5-200) |
Coordinates are not written to the sidecar.
Synchronization: Video timestamps and cue times both come from the
segment’s own clock (SystemClock.uptimeMillis() since the segment started),
not from encoder timestamps, so they stay in step. The file is flushed after
every cue, so a segment that never closes keeps all its telemetry.
4. Crash Resilience: Fragmented MP4
A normal MP4 keeps its index (moov) at the end, written only when the file
closes; a crash leaves nothing playable. The recorder therefore writes a
fragmented MP4 (FragmentedMp4):
- Index first: the
moovis written at the start and describes no samples. The video follows as onemoof+mdatfragment per key frame (one a second), each forced to storage as it is written. At every moment the.mp4.tmpis a playable video up to its last fragment. A crash or a power cut loses at most about one second. Each frame is written once. - Seeking: Android 9’s
MPEG4Extractorseeks a fragmented file only through a segment index (sidx) placed before the first fragment. Room for one is reserved after themoov(afreebox). At close, the index and the total duration are filled in by scanning the fragment headers: small reads, no rewrite. - Clean close: the file is renamed
.mp4.tmp->.mp4, its subtitles.vtt.tmp->.vtt, and a thumbnail is made. - Self-repair: a
.mp4.tmpwhose writer died (not the live segment, a minute cold) is repaired at the next ring-buffer pass: the torn last fragment is cut, the index is written, and it becomes a normal clip with its thumbnail (SegmentFiles.repair). There is nothing to recover by hand.
Checked on the car on 2026-09-26: a hard kill of the recorder mid-segment left a file that was repaired into a 116 s clip with every frame decodable and working seek.
Older recordings (.h264 orphans)
Builds before this change wrote a raw .h264 beside a MediaMuxer file, and
a crash left an orphan .h264. Those are still handled:
- In-app Recovery: the Clips screen’s Recover button remuxes an
orphan’s
.h264into an.mp4without re-encoding, keeps its subtitles, and makes its thumbnail, in the separate:cliprecoveryprocess. Android 9’sMPEG4Writerneeds four-byte start codes (00 00 00 01) in both the samples and the SPS/PPS;ClipRecovery.sampleandClipRecovery.csdproduce them. - Desktop Recovery:
./tools/recover-dashcam.sh /path/to/dash_*.h264(needs FFmpeg) writesdash_….recovered.mp4beside each orphan and never changes the source.
Gallery State Representation
- Active Recording: Identified by recent mtime (<15 s). Displayed with an active indicator; deletion and playback actions are disabled.
- Recoverable Crash Fragment (older recordings only): an orphaned
.h264file with no closed.mp4and not the live segment. Displayed with an “Unclosed / Recoverable” status and direct recovery/delete options. - The live segment is named in
recorder.state; it is always shown as recording, even if its files look cold.
5. Home Assistant Integration & Remote Access (planned, not implemented)
[!NOTE] Nothing in this section exists in the code yet: there is no embedded HTTP server and no
drivemem/<vin>/dashcam/vtttopic. It describes an intended design.
To prevent saturating Home Assistant server storage, video files remain on the vehicle’s local ring buffer while metadata is offloaded over lightweight channels.
Vehicle (IHU629G) Home Assistant
┌───────────────────────┐ ┌────────────────────────┐
│ MqttReporter │ ─── WebVTT Sidecar ──> │ File Sensor / Track │
│ │ (18 KB via MQTT) │ (Searchable telemetry) │
│ │ │ │
│ Embedded HTTP Server │ <── HTTP GET Video ─── │ Browser Video Player │
│ (Port 8080) │ (Stream on demand) │ (<video> + <track>) │
└───────────────────────┘ └────────────────────────┘
- Lightweight Telemetry Sync: Completed
.vttsidecars (~18 KB) are published to Home Assistant via MQTT topicdrivemem/<vin>/dashcam/vtt. - On-Demand Video Serving: Drive Assist hosts a lightweight local HTTP daemon. When an operator selects a clip in Home Assistant, the browser streams video directly from the car over LAN/Wi-Fi.
- Network Gating: Background offloading is permitted only when
AdbGate.isHome(ctx)confirms connection to the driver’s authenticated home Wi-Fi gateway.
6. Dashcam Gallery & Playback UI
The gallery is implemented as an embedded tab within TelemetryActivity (Settings/Config):
- No Dedicated Activity Stack: Embedded in the settings view to preserve system navigation bar behavior.
- Clip Pinning (
keep/): Operators can tap “Hold” to move clips to the protected directory, preventing automatic ring buffer eviction. - Custom WebVTT Rendering: Android’s native
MediaPlayerdoes not render WebVTT subtitles directly.ClipPlayerActivityparses the.vttsidecar and renders a custom HUD overlay matching Drive Assist styling.