aboutsummaryrefslogtreecommitdiffhomepage
path: root/src/encoder
diff options
context:
space:
mode:
authorVictor Nova <lostfreeman@gmail.com>2026-01-27 10:27:36 -0500
committerdec05eba <dec05eba@protonmail.com>2026-01-27 17:43:41 +0100
commit144b4815262e86eb521776123d5ff9b7f7f4750d (patch)
treee18e92e908ba0cf26b6eb0cb81a32a0f2fc6b091 /src/encoder
parentf4ee71a094f9c6e0739199704268b79d6f3177ef (diff)
Add -write-first-frame-ts switch
Add -write-first-frame-ts switch that creates a .ts file next to the output file with values from CLOCK_MONOTONIC and CLOCK_REALTIME corresponding to the first frame to be able to synchronize video with other timestamped data.
Diffstat (limited to 'src/encoder')
-rw-r--r--src/encoder/encoder.c70
1 files changed, 70 insertions, 0 deletions
diff --git a/src/encoder/encoder.c b/src/encoder/encoder.c
index 5aa3959..09d17ac 100644
--- a/src/encoder/encoder.c
+++ b/src/encoder/encoder.c
@@ -3,10 +3,37 @@
#include <string.h>
#include <stdio.h>
+#include <stdlib.h>
+#include <inttypes.h>
+#include <time.h>
+#include <errno.h>
#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h>
+static uint64_t clock_gettime_microseconds(clockid_t clock_id) {
+ struct timespec ts;
+ ts.tv_sec = 0;
+ ts.tv_nsec = 0;
+ clock_gettime(clock_id, &ts);
+ return (uint64_t)ts.tv_sec * 1000000ULL + (uint64_t)ts.tv_nsec / 1000ULL;
+}
+
+static void gsr_write_first_frame_timestamp_file(const char *filepath) {
+ const uint64_t evdev_compatible_ts = clock_gettime_microseconds(CLOCK_MONOTONIC);
+ const uint64_t unix_time_microsec = clock_gettime_microseconds(CLOCK_REALTIME);
+
+ FILE *file = fopen(filepath, "w");
+ if(!file) {
+ fprintf(stderr, "gsr warning: failed to open timestamp file '%s': %s\n", filepath, strerror(errno));
+ return;
+ }
+
+ fputs("monotonic_microsec\trealtime_microsec\n", file);
+ fprintf(file, "%" PRIu64 "\t%" PRIu64 "\n", evdev_compatible_ts, unix_time_microsec);
+ fclose(file);
+}
+
bool gsr_encoder_init(gsr_encoder *self, gsr_replay_storage replay_storage, size_t replay_buffer_num_packets, double replay_buffer_time, const char *replay_directory) {
memset(self, 0, sizeof(*self));
self->num_recording_destinations = 0;
@@ -39,6 +66,16 @@ bool gsr_encoder_init(gsr_encoder *self, gsr_replay_storage replay_storage, size
}
void gsr_encoder_deinit(gsr_encoder *self) {
+ if(self->file_write_mutex_created)
+ pthread_mutex_lock(&self->file_write_mutex);
+ for(size_t i = 0; i < self->num_recording_destinations; ++i) {
+ free(self->recording_destinations[i].first_frame_ts_filepath);
+ self->recording_destinations[i].first_frame_ts_filepath = NULL;
+ self->recording_destinations[i].first_frame_ts_written = false;
+ }
+ if(self->file_write_mutex_created)
+ pthread_mutex_unlock(&self->file_write_mutex);
+
if(self->replay_buffer) {
pthread_mutex_lock(&self->replay_mutex);
gsr_replay_buffer_destroy(self->replay_buffer);
@@ -94,6 +131,11 @@ void gsr_encoder_receive_packets(gsr_encoder *self, AVCodecContext *codec_contex
else if(!recording_destination->has_received_keyframe)
continue;
+ if(recording_destination->first_frame_ts_filepath && !recording_destination->first_frame_ts_written) {
+ gsr_write_first_frame_timestamp_file(recording_destination->first_frame_ts_filepath);
+ recording_destination->first_frame_ts_written = true;
+ }
+
av_packet->pts = pts - recording_destination->start_pts;
av_packet->dts = pts - recording_destination->start_pts;
@@ -148,6 +190,8 @@ size_t gsr_encoder_add_recording_destination(gsr_encoder *self, AVCodecContext *
recording_destination->stream = stream;
recording_destination->start_pts = start_pts;
recording_destination->has_received_keyframe = false;
+ recording_destination->first_frame_ts_filepath = NULL;
+ recording_destination->first_frame_ts_written = false;
++self->recording_destination_id_counter;
++self->num_recording_destinations;
@@ -161,6 +205,9 @@ bool gsr_encoder_remove_recording_destination(gsr_encoder *self, size_t id) {
pthread_mutex_lock(&self->file_write_mutex);
for(size_t i = 0; i < self->num_recording_destinations; ++i) {
if(self->recording_destinations[i].id == id) {
+ free(self->recording_destinations[i].first_frame_ts_filepath);
+ self->recording_destinations[i].first_frame_ts_filepath = NULL;
+ self->recording_destinations[i].first_frame_ts_written = false;
self->recording_destinations[i] = self->recording_destinations[self->num_recording_destinations - 1];
--self->num_recording_destinations;
found = true;
@@ -170,3 +217,26 @@ bool gsr_encoder_remove_recording_destination(gsr_encoder *self, size_t id) {
pthread_mutex_unlock(&self->file_write_mutex);
return found;
}
+
+bool gsr_encoder_set_recording_destination_first_frame_ts_filepath(gsr_encoder *self, size_t id, const char *filepath) {
+ if(!filepath)
+ return false;
+
+ bool found = false;
+ pthread_mutex_lock(&self->file_write_mutex);
+ for(size_t i = 0; i < self->num_recording_destinations; ++i) {
+ if(self->recording_destinations[i].id == id) {
+ char *filepath_copy = strdup(filepath);
+ if(!filepath_copy)
+ break;
+
+ free(self->recording_destinations[i].first_frame_ts_filepath);
+ self->recording_destinations[i].first_frame_ts_filepath = filepath_copy;
+ self->recording_destinations[i].first_frame_ts_written = false;
+ found = true;
+ break;
+ }
+ }
+ pthread_mutex_unlock(&self->file_write_mutex);
+ return found;
+}