dashcamd v2: native C++ process with direct camera capture

- New dashcamd: connects to camerad via VisionIPC, feeds raw NV12
  frames directly to OMX H.264 encoder. Full 1928x1208 resolution,
  4Mbps, 3-minute MP4 segments. Works regardless of UI state.
- Added encode_frame_nv12() to OmxEncoder — skips RGBA->NV12 conversion
- Suspends recording after 10 minutes of standstill
- Disabled old screen recorder timer in onroad.cc
- Suppress debug button alert (clpDebug event still fires for screen toggle)
- launch_openpilot.sh self-cleans other instances before starting
- Register DashcamDebug param in params.cc and manager.py
- Add dashcamd to build system (SConscript) and process_config
- Updated CLAUDE.md with all session changes
- Added GOALS.md feature roadmap

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-11 08:54:41 +00:00
parent ed9c14616a
commit 4afd25fb5b
15 changed files with 560 additions and 119 deletions

View File

@@ -0,0 +1,140 @@
/*
* CLEARPILOT dashcamd — records raw camera footage to MP4 using OMX H.264 hardware encoder.
*
* Connects to camerad via VisionIPC, receives NV12 frames, and feeds them directly
* to the Qualcomm OMX encoder. Produces 3-minute MP4 segments in /data/media/0/videos/.
*
* Suspends recording after 10 minutes of standstill, resumes when car moves.
*/
#include <cstdio>
#include <ctime>
#include <string>
#include <sys/stat.h>
#include <sys/resource.h>
#include <unistd.h>
#include "cereal/messaging/messaging.h"
#include "cereal/visionipc/visionipc_client.h"
#include "common/params.h"
#include "common/timing.h"
#include "common/swaglog.h"
#include "common/util.h"
#include "selfdrive/frogpilot/screenrecorder/omx_encoder.h"
const std::string VIDEOS_DIR = "/data/media/0/videos";
const int SEGMENT_SECONDS = 180; // 3 minutes
const int CAMERA_FPS = 20;
const int FRAMES_PER_SEGMENT = SEGMENT_SECONDS * CAMERA_FPS;
const int BITRATE = 4 * 1024 * 1024; // 4 Mbps
const double STANDSTILL_TIMEOUT_SECONDS = 600.0; // 10 minutes
ExitHandler do_exit;
static std::string make_filename() {
char buf[64];
time_t t = time(NULL);
struct tm tm = *localtime(&t);
snprintf(buf, sizeof(buf), "%04d%02d%02d-%02d%02d%02d.mp4",
tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday,
tm.tm_hour, tm.tm_min, tm.tm_sec);
return std::string(buf);
}
int main(int argc, char *argv[]) {
setpriority(PRIO_PROCESS, 0, -10);
// Ensure output directory exists
mkdir(VIDEOS_DIR.c_str(), 0755);
LOGW("dashcamd: connecting to camerad road stream");
VisionIpcClient vipc("camerad", VISION_STREAM_ROAD, false);
while (!do_exit && !vipc.connect(false)) {
usleep(100000);
}
if (do_exit) return 0;
int width = vipc.buffers[0].width;
int height = vipc.buffers[0].height;
int y_stride = vipc.buffers[0].stride;
int uv_stride = y_stride;
LOGW("dashcamd: connected %dx%d, stride=%d", width, height, y_stride);
// Subscribe to carState for standstill detection
SubMaster sm({"carState"});
// Create encoder — H.264, no downscale, with MP4 remuxing (h265=false)
OmxEncoder encoder(VIDEOS_DIR.c_str(), width, height, CAMERA_FPS, BITRATE, false, false);
int frame_count = FRAMES_PER_SEGMENT; // force new segment on first frame
uint64_t start_ts = 0;
bool recording = false;
bool suspended = false;
double standstill_start = 0.0;
while (!do_exit) {
VisionBuf *buf = vipc.recv();
if (buf == nullptr) continue;
// Check standstill state
sm.update(0);
bool is_standstill = sm.valid("carState") && sm["carState"].getCarState().getStandstill();
double now = nanos_since_boot() / 1e9;
if (is_standstill) {
if (standstill_start == 0.0) {
standstill_start = now;
}
// Suspend after 10 minutes of continuous standstill
if (!suspended && (now - standstill_start) >= STANDSTILL_TIMEOUT_SECONDS) {
LOGW("dashcamd: suspending — standstill for 10 minutes");
if (recording) {
encoder.encoder_close();
recording = false;
frame_count = FRAMES_PER_SEGMENT;
}
suspended = true;
}
} else {
standstill_start = 0.0;
if (suspended) {
LOGW("dashcamd: resuming — car moving");
suspended = false;
}
}
if (suspended) continue;
// Start new segment if needed
if (frame_count >= FRAMES_PER_SEGMENT) {
if (recording) {
encoder.encoder_close();
}
std::string filename = make_filename();
LOGW("dashcamd: opening segment %s", filename.c_str());
encoder.encoder_open(filename.c_str());
frame_count = 0;
start_ts = nanos_since_boot();
recording = true;
}
uint64_t ts = nanos_since_boot() - start_ts;
// Feed NV12 frame directly to OMX encoder
uint8_t *y_ptr = buf->y;
uint8_t *uv_ptr = buf->uv;
encoder.encode_frame_nv12(y_ptr, y_stride, uv_ptr, uv_stride, width, height, ts);
frame_count++;
}
if (recording) {
encoder.encoder_close();
}
LOGW("dashcamd: stopped");
return 0;
}