mirror of
https://repo.dec05eba.com/gpu-screen-recorder
synced 2026-01-31 01:13:06 +09:00
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.
This commit is contained in:
@@ -372,6 +372,14 @@ It's recommended to also use the option
|
||||
when this is set to
|
||||
.B yes
|
||||
to only encode frames when the screen content updates to lower GPU and video encoding usage when the system is idle.
|
||||
.TP
|
||||
.BI \-write\-first\-frame\-ts " yes|no"
|
||||
When enabled, writes a timestamp file with extra extension \fI.ts\fR next to the output video containing:
|
||||
.nf
|
||||
monotonic_microsec realtime_microsec
|
||||
<monotonic_microsec> <realtime_microsec>
|
||||
.fi
|
||||
(default: no). Ignored for livestreaming and when output is piped.
|
||||
.SS Output Options
|
||||
.TP
|
||||
.BI \-o " output"
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
|
||||
typedef struct gsr_egl gsr_egl;
|
||||
|
||||
#define NUM_ARGS 36
|
||||
#define NUM_ARGS 37
|
||||
|
||||
typedef enum {
|
||||
GSR_CAPTURE_SOURCE_TYPE_WINDOW,
|
||||
@@ -97,6 +97,7 @@ typedef struct {
|
||||
bool restore_portal_session;
|
||||
bool restart_replay_on_save;
|
||||
bool overclock;
|
||||
bool write_first_frame_ts;
|
||||
bool is_livestream;
|
||||
bool is_output_piped;
|
||||
bool low_latency_recording;
|
||||
|
||||
@@ -20,6 +20,8 @@ typedef struct {
|
||||
AVStream *stream;
|
||||
int64_t start_pts;
|
||||
bool has_received_keyframe;
|
||||
char *first_frame_ts_filepath;
|
||||
bool first_frame_ts_written;
|
||||
} gsr_encoder_recording_destination;
|
||||
|
||||
typedef struct {
|
||||
@@ -43,5 +45,6 @@ void gsr_encoder_receive_packets(gsr_encoder *self, AVCodecContext *codec_contex
|
||||
/* Returns the id to the recording destination, or -1 on error */
|
||||
size_t gsr_encoder_add_recording_destination(gsr_encoder *self, AVCodecContext *codec_context, AVFormatContext *format_context, AVStream *stream, int64_t start_pts);
|
||||
bool gsr_encoder_remove_recording_destination(gsr_encoder *self, size_t id);
|
||||
bool gsr_encoder_set_recording_destination_first_frame_ts_filepath(gsr_encoder *self, size_t id, const char *filepath);
|
||||
|
||||
#endif /* GSR_ENCODER_H */
|
||||
|
||||
@@ -196,7 +196,7 @@ static void usage_header(void) {
|
||||
"[-bm auto|qp|vbr|cbr] [-cr limited|full] [-tune performance|quality] [-df yes|no] [-sc <script_path>] [-p <plugin_path>] "
|
||||
"[-cursor yes|no] [-keyint <value>] [-restore-portal-session yes|no] [-portal-session-token-filepath filepath] [-encoder gpu|cpu] "
|
||||
"[-fallback-cpu-encoding yes|no] [-o <output_file>] [-ro <output_directory>] [-ffmpeg-opts <options>] [--list-capture-options [card_path]] "
|
||||
"[--list-audio-devices] [--list-application-audio] [--list-v4l2-devices] [-low-power yes|no] [-v yes|no] [-gl-debug yes|no] [--version] [-h|--help]\n", program_name);
|
||||
"[--list-audio-devices] [--list-application-audio] [--list-v4l2-devices] [-write-first-frame-ts yes|no] [-low-power yes|no] [-v yes|no] [-gl-debug yes|no] [--version] [-h|--help]\n", program_name);
|
||||
fflush(stdout);
|
||||
}
|
||||
|
||||
@@ -255,6 +255,7 @@ static bool args_parser_set_values(args_parser *self) {
|
||||
self->restart_replay_on_save = args_get_boolean_by_key(self->args, NUM_ARGS, "-restart-replay-on-save", false);
|
||||
self->overclock = args_get_boolean_by_key(self->args, NUM_ARGS, "-oc", false);
|
||||
self->fallback_cpu_encoding = args_get_boolean_by_key(self->args, NUM_ARGS, "-fallback-cpu-encoding", false);
|
||||
self->write_first_frame_ts = args_get_boolean_by_key(self->args, NUM_ARGS, "-write-first-frame-ts", false);
|
||||
self->low_power = args_get_boolean_by_key(self->args, NUM_ARGS, "-low-power", false);
|
||||
|
||||
self->audio_bitrate = args_get_i64_by_key(self->args, NUM_ARGS, "-ab", 0);
|
||||
@@ -432,6 +433,10 @@ static bool args_parser_set_values(args_parser *self) {
|
||||
|
||||
self->is_output_piped = strcmp(self->filename, "/dev/stdout") == 0;
|
||||
self->low_latency_recording = self->is_livestream || self->is_output_piped;
|
||||
if(self->write_first_frame_ts && (self->is_livestream || self->is_output_piped)) {
|
||||
fprintf(stderr, "gsr warning: -write-first-frame-ts is ignored for livestreaming or when output is piped\n");
|
||||
self->write_first_frame_ts = false;
|
||||
}
|
||||
|
||||
self->replay_recording_directory = args_get_value_by_key(self->args, NUM_ARGS, "-ro");
|
||||
|
||||
@@ -536,6 +541,7 @@ bool args_parser_parse(args_parser *self, int argc, char **argv, const args_hand
|
||||
self->args[arg_index++] = (Arg){ .key = "-ffmpeg-opts", .optional = true, .list = false, .type = ARG_TYPE_STRING };
|
||||
self->args[arg_index++] = (Arg){ .key = "-ffmpeg-video-opts", .optional = true, .list = false, .type = ARG_TYPE_STRING };
|
||||
self->args[arg_index++] = (Arg){ .key = "-ffmpeg-audio-opts", .optional = true, .list = false, .type = ARG_TYPE_STRING };
|
||||
self->args[arg_index++] = (Arg){ .key = "-write-first-frame-ts", .optional = true, .list = false, .type = ARG_TYPE_BOOLEAN };
|
||||
self->args[arg_index++] = (Arg){ .key = "-low-power", .optional = true, .list = false, .type = ARG_TYPE_BOOLEAN };
|
||||
assert(arg_index == NUM_ARGS);
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
11
src/main.cpp
11
src/main.cpp
@@ -3988,7 +3988,11 @@ int main(int argc, char **argv) {
|
||||
|
||||
if(video_stream) {
|
||||
avcodec_parameters_from_context(video_stream->codecpar, video_codec_context);
|
||||
gsr_encoder_add_recording_destination(&encoder, video_codec_context, av_format_context, video_stream, 0);
|
||||
const size_t video_destination_id = gsr_encoder_add_recording_destination(&encoder, video_codec_context, av_format_context, video_stream, 0);
|
||||
if(arg_parser.write_first_frame_ts && video_destination_id != (size_t)-1) {
|
||||
std::string ts_filepath = std::string(arg_parser.filename) + ".ts";
|
||||
gsr_encoder_set_recording_destination_first_frame_ts_filepath(&encoder, video_destination_id, ts_filepath.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
int audio_max_frame_size = 1024;
|
||||
@@ -4527,6 +4531,11 @@ int main(int argc, char **argv) {
|
||||
replay_recording_start_result = start_recording_create_streams(replay_recording_filepath.c_str(), arg_parser, video_codec_context, audio_tracks, hdr, video_sources);
|
||||
if(replay_recording_start_result.av_format_context) {
|
||||
const size_t video_recording_destination_id = gsr_encoder_add_recording_destination(&encoder, video_codec_context, replay_recording_start_result.av_format_context, replay_recording_start_result.video_stream, video_frame->pts);
|
||||
if(arg_parser.write_first_frame_ts && video_recording_destination_id != (size_t)-1) {
|
||||
std::string ts_filepath = replay_recording_filepath + ".ts";
|
||||
gsr_encoder_set_recording_destination_first_frame_ts_filepath(&encoder, video_recording_destination_id, ts_filepath.c_str());
|
||||
}
|
||||
|
||||
if(video_recording_destination_id != (size_t)-1)
|
||||
replay_recording_items.push_back(video_recording_destination_id);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user