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
|
* - thermald sets DashcamShutdown="1" before device power-off
|
||||||
* - dashcamd closes current segment, sets DashcamShutdown="0" (ack), exits
|
* - dashcamd closes current segment, sets DashcamShutdown="0" (ack), exits
|
||||||
* - thermald waits up to 15s for ack, then proceeds with shutdown
|
* - 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>
|
#include <cstdio>
|
||||||
@@ -88,6 +94,26 @@ static bool system_time_valid() {
|
|||||||
return (tm.tm_year + 1900) >= 2024;
|
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[]) {
|
int main(int argc, char *argv[]) {
|
||||||
setpriority(PRIO_PROCESS, 0, -10);
|
setpriority(PRIO_PROCESS, 0, -10);
|
||||||
|
|
||||||
@@ -116,8 +142,8 @@ int main(int argc, char *argv[]) {
|
|||||||
int uv_stride = y_stride;
|
int uv_stride = y_stride;
|
||||||
LOGW("dashcamd: connected %dx%d, stride=%d", width, height, y_stride);
|
LOGW("dashcamd: connected %dx%d, stride=%d", width, height, y_stride);
|
||||||
|
|
||||||
// Subscribe to carState (gear, standstill) and deviceState (ignition/started)
|
// Subscribe to carState (gear), deviceState (ignition), gpsLocation (subtitles)
|
||||||
SubMaster sm({"carState", "deviceState"});
|
SubMaster sm({"carState", "deviceState", "gpsLocation"});
|
||||||
Params params;
|
Params params;
|
||||||
|
|
||||||
// Trip state
|
// Trip state
|
||||||
@@ -128,6 +154,12 @@ int main(int argc, char *argv[]) {
|
|||||||
uint64_t segment_start_ts = 0;
|
uint64_t segment_start_ts = 0;
|
||||||
double idle_timer_start = 0.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
|
// Ignition tracking for off→on detection
|
||||||
bool prev_started = false;
|
bool prev_started = false;
|
||||||
bool started_initialized = 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);
|
encoder = new OmxEncoder(trip_dir.c_str(), width, height, CAMERA_FPS, BITRATE, false, false);
|
||||||
|
|
||||||
std::string filename = make_timestamp() + ".mp4";
|
std::string seg_name = make_timestamp();
|
||||||
LOGW("dashcamd: opening segment %s", filename.c_str());
|
LOGW("dashcamd: opening segment %s", seg_name.c_str());
|
||||||
encoder->encoder_open(filename.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;
|
frame_count = 0;
|
||||||
segment_start_ts = nanos_since_boot();
|
segment_start_ts = nanos_since_boot();
|
||||||
state = RECORDING;
|
state = RECORDING;
|
||||||
};
|
};
|
||||||
|
|
||||||
auto close_trip = [&]() {
|
auto close_trip = [&]() {
|
||||||
|
if (srt_file) { fclose(srt_file); srt_file = nullptr; }
|
||||||
if (encoder) {
|
if (encoder) {
|
||||||
if (state == RECORDING || state == IDLE_TIMEOUT) {
|
if (state == RECORDING || state == IDLE_TIMEOUT) {
|
||||||
encoder->encoder_close();
|
encoder->encoder_close();
|
||||||
@@ -265,10 +306,19 @@ int main(int argc, char *argv[]) {
|
|||||||
|
|
||||||
// Segment rotation
|
// Segment rotation
|
||||||
if (frame_count >= FRAMES_PER_SEGMENT) {
|
if (frame_count >= FRAMES_PER_SEGMENT) {
|
||||||
|
if (srt_file) { fclose(srt_file); srt_file = nullptr; }
|
||||||
encoder->encoder_close();
|
encoder->encoder_close();
|
||||||
std::string filename = make_timestamp() + ".mp4";
|
|
||||||
LOGW("dashcamd: opening segment %s", filename.c_str());
|
std::string seg_name = make_timestamp();
|
||||||
encoder->encoder_open(filename.c_str());
|
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;
|
frame_count = 0;
|
||||||
segment_start_ts = nanos_since_boot();
|
segment_start_ts = nanos_since_boot();
|
||||||
}
|
}
|
||||||
@@ -278,9 +328,46 @@ int main(int argc, char *argv[]) {
|
|||||||
// Feed NV12 frame directly to OMX encoder
|
// Feed NV12 frame directly to OMX encoder
|
||||||
encoder->encode_frame_nv12(buf->y, y_stride, buf->uv, uv_stride, width, height, ts);
|
encoder->encode_frame_nv12(buf->y, y_stride, buf->uv, uv_stride, width, height, ts);
|
||||||
frame_count++;
|
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
|
// Clean exit
|
||||||
|
if (srt_file) { fclose(srt_file); srt_file = nullptr; }
|
||||||
if (encoder) {
|
if (encoder) {
|
||||||
if (state == RECORDING || state == IDLE_TIMEOUT) {
|
if (state == RECORDING || state == IDLE_TIMEOUT) {
|
||||||
encoder->encoder_close();
|
encoder->encoder_close();
|
||||||
|
|||||||
Reference in New Issue
Block a user