dashcamd: GPS subtitle track (.srt) with speed, position, timestamp
Each MP4 segment gets a companion .srt file with 1Hz entries containing speed (MPH), lat/lon coordinates, and UTC timestamp from gpsLocation cereal messages. Falls back to "No GPS" when fix is unavailable. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Binary file not shown.
@@ -39,6 +39,12 @@
|
||||
* - thermald sets DashcamShutdown="1" before device power-off
|
||||
* - dashcamd closes current segment, sets DashcamShutdown="0" (ack), exits
|
||||
* - thermald waits up to 15s for ack, then proceeds with shutdown
|
||||
*
|
||||
* GPS subtitle track:
|
||||
* - Each .mp4 segment has a companion .srt subtitle file
|
||||
* - Updated at most once per second from gpsLocation cereal messages
|
||||
* - Format: "35 MPH | 44.9216°N 93.3260°W | 2026-04-13 05:19:00 UTC"
|
||||
* - Most players auto-detect .srt files alongside .mp4 files
|
||||
*/
|
||||
|
||||
#include <cstdio>
|
||||
@@ -88,6 +94,26 @@ static bool system_time_valid() {
|
||||
return (tm.tm_year + 1900) >= 2024;
|
||||
}
|
||||
|
||||
static std::string make_utc_timestamp() {
|
||||
char buf[32];
|
||||
time_t t = time(NULL);
|
||||
struct tm tm = *gmtime(&t);
|
||||
snprintf(buf, sizeof(buf), "%04d-%02d-%02d %02d:%02d:%02d UTC",
|
||||
tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday,
|
||||
tm.tm_hour, tm.tm_min, tm.tm_sec);
|
||||
return std::string(buf);
|
||||
}
|
||||
|
||||
// Format SRT timestamp: HH:MM:SS,mmm
|
||||
static std::string srt_time(int seconds) {
|
||||
int h = seconds / 3600;
|
||||
int m = (seconds % 3600) / 60;
|
||||
int s = seconds % 60;
|
||||
char buf[16];
|
||||
snprintf(buf, sizeof(buf), "%02d:%02d:%02d,000", h, m, s);
|
||||
return std::string(buf);
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
setpriority(PRIO_PROCESS, 0, -10);
|
||||
|
||||
@@ -116,8 +142,8 @@ int main(int argc, char *argv[]) {
|
||||
int uv_stride = y_stride;
|
||||
LOGW("dashcamd: connected %dx%d, stride=%d", width, height, y_stride);
|
||||
|
||||
// Subscribe to carState (gear, standstill) and deviceState (ignition/started)
|
||||
SubMaster sm({"carState", "deviceState"});
|
||||
// Subscribe to carState (gear), deviceState (ignition), gpsLocation (subtitles)
|
||||
SubMaster sm({"carState", "deviceState", "gpsLocation"});
|
||||
Params params;
|
||||
|
||||
// Trip state
|
||||
@@ -128,6 +154,12 @@ int main(int argc, char *argv[]) {
|
||||
uint64_t segment_start_ts = 0;
|
||||
double idle_timer_start = 0.0;
|
||||
|
||||
// SRT subtitle state
|
||||
FILE *srt_file = nullptr;
|
||||
int srt_index = 0; // subtitle entry counter (1-based)
|
||||
int srt_segment_sec = 0; // seconds elapsed in current segment
|
||||
double last_srt_write = 0; // monotonic time of last SRT write
|
||||
|
||||
// Ignition tracking for off→on detection
|
||||
bool prev_started = false;
|
||||
bool started_initialized = false;
|
||||
@@ -152,15 +184,24 @@ int main(int argc, char *argv[]) {
|
||||
|
||||
encoder = new OmxEncoder(trip_dir.c_str(), width, height, CAMERA_FPS, BITRATE, false, false);
|
||||
|
||||
std::string filename = make_timestamp() + ".mp4";
|
||||
LOGW("dashcamd: opening segment %s", filename.c_str());
|
||||
encoder->encoder_open(filename.c_str());
|
||||
std::string seg_name = make_timestamp();
|
||||
LOGW("dashcamd: opening segment %s", seg_name.c_str());
|
||||
encoder->encoder_open((seg_name + ".mp4").c_str());
|
||||
|
||||
// Open companion SRT file
|
||||
std::string srt_path = trip_dir + "/" + seg_name + ".srt";
|
||||
srt_file = fopen(srt_path.c_str(), "w");
|
||||
srt_index = 0;
|
||||
srt_segment_sec = 0;
|
||||
last_srt_write = 0;
|
||||
|
||||
frame_count = 0;
|
||||
segment_start_ts = nanos_since_boot();
|
||||
state = RECORDING;
|
||||
};
|
||||
|
||||
auto close_trip = [&]() {
|
||||
if (srt_file) { fclose(srt_file); srt_file = nullptr; }
|
||||
if (encoder) {
|
||||
if (state == RECORDING || state == IDLE_TIMEOUT) {
|
||||
encoder->encoder_close();
|
||||
@@ -265,10 +306,19 @@ int main(int argc, char *argv[]) {
|
||||
|
||||
// Segment rotation
|
||||
if (frame_count >= FRAMES_PER_SEGMENT) {
|
||||
if (srt_file) { fclose(srt_file); srt_file = nullptr; }
|
||||
encoder->encoder_close();
|
||||
std::string filename = make_timestamp() + ".mp4";
|
||||
LOGW("dashcamd: opening segment %s", filename.c_str());
|
||||
encoder->encoder_open(filename.c_str());
|
||||
|
||||
std::string seg_name = make_timestamp();
|
||||
LOGW("dashcamd: opening segment %s", seg_name.c_str());
|
||||
encoder->encoder_open((seg_name + ".mp4").c_str());
|
||||
|
||||
std::string srt_path = trip_dir + "/" + seg_name + ".srt";
|
||||
srt_file = fopen(srt_path.c_str(), "w");
|
||||
srt_index = 0;
|
||||
srt_segment_sec = 0;
|
||||
last_srt_write = 0;
|
||||
|
||||
frame_count = 0;
|
||||
segment_start_ts = nanos_since_boot();
|
||||
}
|
||||
@@ -278,9 +328,46 @@ int main(int argc, char *argv[]) {
|
||||
// Feed NV12 frame directly to OMX encoder
|
||||
encoder->encode_frame_nv12(buf->y, y_stride, buf->uv, uv_stride, width, height, ts);
|
||||
frame_count++;
|
||||
|
||||
// Write GPS subtitle at most once per second
|
||||
if (srt_file && (now - last_srt_write) >= 1.0) {
|
||||
last_srt_write = now;
|
||||
srt_index++;
|
||||
|
||||
// Read GPS data
|
||||
double lat = 0, lon = 0, speed_ms = 0;
|
||||
bool has_gps = sm.valid("gpsLocation") && sm["gpsLocation"].getGpsLocation().getHasFix();
|
||||
if (has_gps) {
|
||||
auto gps = sm["gpsLocation"].getGpsLocation();
|
||||
lat = gps.getLatitude();
|
||||
lon = gps.getLongitude();
|
||||
speed_ms = gps.getSpeed();
|
||||
}
|
||||
|
||||
double speed_mph = speed_ms * 2.23694;
|
||||
std::string utc = make_utc_timestamp();
|
||||
std::string t_start = srt_time(srt_segment_sec);
|
||||
std::string t_end = srt_time(srt_segment_sec + 1);
|
||||
srt_segment_sec++;
|
||||
|
||||
if (has_gps) {
|
||||
const char *deg = "\xC2\xB0"; // UTF-8 degree sign
|
||||
fprintf(srt_file, "%d\n%s --> %s\n%.0f MPH | %.4f%s%c %.4f%s%c | %s\n\n",
|
||||
srt_index, t_start.c_str(), t_end.c_str(),
|
||||
speed_mph,
|
||||
std::abs(lat), deg, lat >= 0 ? 'N' : 'S',
|
||||
std::abs(lon), deg, lon >= 0 ? 'E' : 'W',
|
||||
utc.c_str());
|
||||
} else {
|
||||
fprintf(srt_file, "%d\n%s --> %s\nNo GPS | %s\n\n",
|
||||
srt_index, t_start.c_str(), t_end.c_str(), utc.c_str());
|
||||
}
|
||||
fflush(srt_file);
|
||||
}
|
||||
}
|
||||
|
||||
// Clean exit
|
||||
if (srt_file) { fclose(srt_file); srt_file = nullptr; }
|
||||
if (encoder) {
|
||||
if (state == RECORDING || state == IDLE_TIMEOUT) {
|
||||
encoder->encoder_close();
|
||||
|
||||
Reference in New Issue
Block a user