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:
140
selfdrive/clearpilot/dashcamd.cc
Normal file
140
selfdrive/clearpilot/dashcamd.cc
Normal 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;
|
||||
}
|
||||
Reference in New Issue
Block a user