aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--include/args_parser.h46
-rw-r--r--include/ffmpeg_utils.h14
-rw-r--r--include/recorder/audio_codec.h23
-rw-r--r--include/recorder/settings.h63
-rw-r--r--include/recorder/video_codec.h18
-rw-r--r--meson.build3
-rw-r--r--src/args_parser.c194
-rw-r--r--src/ffmpeg_utils.c12
-rw-r--r--src/main.cpp955
-rw-r--r--src/recorder/audio_codec.c220
-rw-r--r--src/recorder/video_codec.c429
11 files changed, 1047 insertions, 930 deletions
diff --git a/include/args_parser.h b/include/args_parser.h
index e8c68d8..9e388ec 100644
--- a/include/args_parser.h
+++ b/include/args_parser.h
@@ -5,6 +5,7 @@
#include <stdint.h>
#include "defs.h"
#include "vec2.h"
+#include "recorder/settings.h"
typedef struct gsr_egl gsr_egl;
@@ -68,50 +69,7 @@ typedef struct {
typedef struct {
Arg args[NUM_ARGS];
-
- gsr_video_encoder_hardware video_encoder;
- gsr_pixel_format pixel_format;
- gsr_framerate_mode framerate_mode;
- gsr_color_range color_range;
- gsr_tune tune;
- gsr_video_codec video_codec;
- gsr_audio_codec audio_codec;
- gsr_bitrate_mode bitrate_mode;
- gsr_video_quality video_quality;
- gsr_replay_storage replay_storage;
-
- const char *capture_source;
- const char *container_format;
- const char *filename;
- const char *replay_recording_directory;
- const char *portal_session_token_filepath;
- const char *recording_saved_script;
- const char *ffmpeg_opts;
- const char *ffmpeg_video_opts;
- const char *ffmpeg_audio_opts;
- bool verbose;
- bool gl_debug;
- bool fallback_cpu_encoding;
- bool low_power;
- bool exclude_metadata;
- bool record_cursor;
- bool date_folders;
- bool restore_portal_session;
- bool restart_replay_on_save;
- bool write_first_frame_ts;
- bool is_replaying;
- bool is_livestream;
- bool is_output_piped;
- bool low_latency_recording;
- bool very_old_gpu;
- int64_t video_bitrate;
- int64_t audio_bitrate;
- int64_t fps;
- int64_t replay_buffer_size_secs;
- double keyint;
- vec2i output_resolution;
- vec2i region_size;
- vec2i region_position;
+ gsr_recorder_settings settings;
} args_parser;
/* |argv| is stored as a reference */
diff --git a/include/ffmpeg_utils.h b/include/ffmpeg_utils.h
new file mode 100644
index 0000000..373122c
--- /dev/null
+++ b/include/ffmpeg_utils.h
@@ -0,0 +1,14 @@
+#ifndef GSR_FFMPEG_UTILS_H
+#define GSR_FFMPEG_UTILS_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+const char* gsr_av_error_to_string(int err);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* GSR_FFMPEG_UTILS_H */
diff --git a/include/recorder/audio_codec.h b/include/recorder/audio_codec.h
new file mode 100644
index 0000000..d432abd
--- /dev/null
+++ b/include/recorder/audio_codec.h
@@ -0,0 +1,23 @@
+#ifndef GSR_RECORDER_AUDIO_CODEC_H
+#define GSR_RECORDER_AUDIO_CODEC_H
+
+#include <stdbool.h>
+#include <stdint.h>
+#include "../defs.h"
+#include "../sound.h"
+
+#include <libavcodec/avcodec.h>
+
+#define GSR_AUDIO_SAMPLE_RATE 48000
+
+gsr_audio_format audio_codec_context_get_audio_format(const AVCodecContext *audio_codec_context);
+enum AVSampleFormat audio_format_to_sample_format(const gsr_audio_format audio_format);
+
+AVCodecContext* create_audio_codec_context(int fps, gsr_audio_codec audio_codec, bool mix_audio, int64_t audio_bitrate);
+bool open_audio(AVCodecContext *audio_codec_context, const char *ffmpeg_audio_opts);
+AVFrame* create_audio_frame(AVCodecContext *audio_codec_context);
+
+double audio_codec_get_desired_delay(gsr_audio_codec audio_codec, int fps);
+int audio_codec_get_frame_size(gsr_audio_codec audio_codec);
+
+#endif /* GSR_RECORDER_AUDIO_CODEC_H */
diff --git a/include/recorder/settings.h b/include/recorder/settings.h
new file mode 100644
index 0000000..8bb7f77
--- /dev/null
+++ b/include/recorder/settings.h
@@ -0,0 +1,63 @@
+#ifndef GSR_RECORDER_SETTINGS_H
+#define GSR_RECORDER_SETTINGS_H
+
+#include <stdbool.h>
+#include <stdint.h>
+#include "../defs.h"
+#include "../vec2.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+typedef struct {
+ gsr_video_encoder_hardware video_encoder;
+ gsr_pixel_format pixel_format;
+ gsr_framerate_mode framerate_mode;
+ gsr_color_range color_range;
+ gsr_tune tune;
+ gsr_video_codec video_codec;
+ gsr_audio_codec audio_codec;
+ gsr_bitrate_mode bitrate_mode;
+ gsr_video_quality video_quality;
+ gsr_replay_storage replay_storage;
+
+ const char *capture_source;
+ const char *container_format;
+ const char *filename;
+ const char *replay_recording_directory;
+ const char *portal_session_token_filepath;
+ const char *recording_saved_script;
+ const char *ffmpeg_opts;
+ const char *ffmpeg_video_opts;
+ const char *ffmpeg_audio_opts;
+ bool verbose;
+ bool gl_debug;
+ bool fallback_cpu_encoding;
+ bool low_power;
+ bool exclude_metadata;
+ bool record_cursor;
+ bool date_folders;
+ bool restore_portal_session;
+ bool restart_replay_on_save;
+ bool write_first_frame_ts;
+ bool is_replaying;
+ bool is_livestream;
+ bool is_output_piped;
+ bool low_latency_recording;
+ bool very_old_gpu;
+ int64_t video_bitrate;
+ int64_t audio_bitrate;
+ int64_t fps;
+ int64_t replay_buffer_size_secs;
+ double keyint;
+ vec2i output_resolution;
+ vec2i region_size;
+ vec2i region_position;
+} gsr_recorder_settings;
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* GSR_RECORDER_SETTINGS_H */
diff --git a/include/recorder/video_codec.h b/include/recorder/video_codec.h
new file mode 100644
index 0000000..9347a9e
--- /dev/null
+++ b/include/recorder/video_codec.h
@@ -0,0 +1,18 @@
+#ifndef GSR_RECORDER_VIDEO_CODEC_H
+#define GSR_RECORDER_VIDEO_CODEC_H
+
+#include <stdbool.h>
+#include "../defs.h"
+#include "../egl.h"
+#include "settings.h"
+
+#include <libavcodec/avcodec.h>
+
+int video_quality_to_h264_equivalent_qp(gsr_video_quality video_quality);
+enum AVPixelFormat get_pixel_format(gsr_video_codec video_codec, gsr_gpu_vendor vendor, bool use_software_video_encoder);
+
+AVCodecContext* create_video_codec_context(enum AVPixelFormat pix_fmt, const AVCodec *codec, const gsr_egl *egl, const gsr_recorder_settings *settings, int width, int height);
+bool open_video_software(AVCodecContext *codec_context, const gsr_recorder_settings *settings);
+bool open_video_hardware(AVCodecContext *codec_context, bool low_power, const gsr_egl *egl, const gsr_recorder_settings *settings);
+
+#endif /* GSR_RECORDER_VIDEO_CODEC_H */
diff --git a/meson.build b/meson.build
index ebe8440..220ecbf 100644
--- a/meson.build
+++ b/meson.build
@@ -31,6 +31,9 @@ src = [
'src/replay_buffer/replay_buffer_ram.c',
'src/replay_buffer/replay_buffer_disk.c',
'src/log.c',
+ 'src/ffmpeg_utils.c',
+ 'src/recorder/audio_codec.c',
+ 'src/recorder/video_codec.c',
'src/egl.c',
'src/cuda.c',
'src/window_texture.c',
diff --git a/src/args_parser.c b/src/args_parser.c
index 039dfca..b105f84 100644
--- a/src/args_parser.c
+++ b/src/args_parser.c
@@ -253,104 +253,104 @@ static bool file_is_pipe_or_char_device(const char *filepath) {
}
static bool args_parser_set_values(args_parser *self) {
- self->video_encoder = (gsr_video_encoder_hardware)args_get_enum_by_key(self->args, NUM_ARGS, "-encoder", GSR_VIDEO_ENCODER_HW_GPU);
- self->pixel_format = (gsr_pixel_format)args_get_enum_by_key(self->args, NUM_ARGS, "-pixfmt", GSR_PIXEL_FORMAT_YUV420);
- self->framerate_mode = (gsr_framerate_mode)args_get_enum_by_key(self->args, NUM_ARGS, "-fm", GSR_FRAMERATE_MODE_VARIABLE);
- self->color_range = (gsr_color_range)args_get_enum_by_key(self->args, NUM_ARGS, "-cr", GSR_COLOR_RANGE_LIMITED);
- self->tune = (gsr_tune)args_get_enum_by_key(self->args, NUM_ARGS, "-tune", GSR_TUNE_PERFORMANCE);
- self->video_codec = (gsr_video_codec)args_get_enum_by_key(self->args, NUM_ARGS, "-k", GSR_VIDEO_CODEC_AUTO);
- self->audio_codec = (gsr_audio_codec)args_get_enum_by_key(self->args, NUM_ARGS, "-ac", GSR_AUDIO_CODEC_OPUS);
- self->bitrate_mode = (gsr_bitrate_mode)args_get_enum_by_key(self->args, NUM_ARGS, "-bm", GSR_BITRATE_MODE_AUTO);
- self->replay_storage = (gsr_replay_storage)args_get_enum_by_key(self->args, NUM_ARGS, "-replay-storage", GSR_REPLAY_STORAGE_RAM);
-
- self->capture_source = args_get_value_by_key(self->args, NUM_ARGS, "-w");
- self->verbose = args_get_boolean_by_key(self->args, NUM_ARGS, "-v", true);
- self->gl_debug = args_get_boolean_by_key(self->args, NUM_ARGS, "-gl-debug", false);
- self->record_cursor = args_get_boolean_by_key(self->args, NUM_ARGS, "-cursor", true);
- self->date_folders = args_get_boolean_by_key(self->args, NUM_ARGS, "-df", false);
- self->restore_portal_session = args_get_boolean_by_key(self->args, NUM_ARGS, "-restore-portal-session", false);
- self->restart_replay_on_save = args_get_boolean_by_key(self->args, NUM_ARGS, "-restart-replay-on-save", false);
+ self->settings.video_encoder = (gsr_video_encoder_hardware)args_get_enum_by_key(self->args, NUM_ARGS, "-encoder", GSR_VIDEO_ENCODER_HW_GPU);
+ self->settings.pixel_format = (gsr_pixel_format)args_get_enum_by_key(self->args, NUM_ARGS, "-pixfmt", GSR_PIXEL_FORMAT_YUV420);
+ self->settings.framerate_mode = (gsr_framerate_mode)args_get_enum_by_key(self->args, NUM_ARGS, "-fm", GSR_FRAMERATE_MODE_VARIABLE);
+ self->settings.color_range = (gsr_color_range)args_get_enum_by_key(self->args, NUM_ARGS, "-cr", GSR_COLOR_RANGE_LIMITED);
+ self->settings.tune = (gsr_tune)args_get_enum_by_key(self->args, NUM_ARGS, "-tune", GSR_TUNE_PERFORMANCE);
+ self->settings.video_codec = (gsr_video_codec)args_get_enum_by_key(self->args, NUM_ARGS, "-k", GSR_VIDEO_CODEC_AUTO);
+ self->settings.audio_codec = (gsr_audio_codec)args_get_enum_by_key(self->args, NUM_ARGS, "-ac", GSR_AUDIO_CODEC_OPUS);
+ self->settings.bitrate_mode = (gsr_bitrate_mode)args_get_enum_by_key(self->args, NUM_ARGS, "-bm", GSR_BITRATE_MODE_AUTO);
+ self->settings.replay_storage = (gsr_replay_storage)args_get_enum_by_key(self->args, NUM_ARGS, "-replay-storage", GSR_REPLAY_STORAGE_RAM);
+
+ self->settings.capture_source = args_get_value_by_key(self->args, NUM_ARGS, "-w");
+ self->settings.verbose = args_get_boolean_by_key(self->args, NUM_ARGS, "-v", true);
+ self->settings.gl_debug = args_get_boolean_by_key(self->args, NUM_ARGS, "-gl-debug", false);
+ self->settings.record_cursor = args_get_boolean_by_key(self->args, NUM_ARGS, "-cursor", true);
+ self->settings.date_folders = args_get_boolean_by_key(self->args, NUM_ARGS, "-df", false);
+ self->settings.restore_portal_session = args_get_boolean_by_key(self->args, NUM_ARGS, "-restore-portal-session", false);
+ self->settings.restart_replay_on_save = args_get_boolean_by_key(self->args, NUM_ARGS, "-restart-replay-on-save", false);
const bool 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->exclude_metadata = args_get_boolean_by_key(self->args, NUM_ARGS, "-exclude-metadata", false);
+ self->settings.fallback_cpu_encoding = args_get_boolean_by_key(self->args, NUM_ARGS, "-fallback-cpu-encoding", false);
+ self->settings.write_first_frame_ts = args_get_boolean_by_key(self->args, NUM_ARGS, "-write-first-frame-ts", false);
+ self->settings.low_power = args_get_boolean_by_key(self->args, NUM_ARGS, "-low-power", false);
+ self->settings.exclude_metadata = args_get_boolean_by_key(self->args, NUM_ARGS, "-exclude-metadata", false);
- self->audio_bitrate = args_get_i64_by_key(self->args, NUM_ARGS, "-ab", 0);
- self->audio_bitrate *= 1000LL;
+ self->settings.audio_bitrate = args_get_i64_by_key(self->args, NUM_ARGS, "-ab", 0);
+ self->settings.audio_bitrate *= 1000LL;
- self->keyint = args_get_double_by_key(self->args, NUM_ARGS, "-keyint", 2.0);
+ self->settings.keyint = args_get_double_by_key(self->args, NUM_ARGS, "-keyint", 2.0);
if(overclock) {
gsr_log(GSR_LOG_LEVEL_INFO, "the overclock option (-oc) is deprecated and no longer has any effect as it's no longer needed (on GPUs that are 12 years old or younger)");
}
- if(self->audio_codec == GSR_AUDIO_CODEC_FLAC) {
+ if(self->settings.audio_codec == GSR_AUDIO_CODEC_FLAC) {
gsr_log(GSR_LOG_LEVEL_WARNING, "flac audio codec is temporary disabled, using opus audio codec instead");
- self->audio_codec = GSR_AUDIO_CODEC_OPUS;
+ self->settings.audio_codec = GSR_AUDIO_CODEC_OPUS;
}
- self->portal_session_token_filepath = args_get_value_by_key(self->args, NUM_ARGS, "-portal-session-token-filepath");
- if(self->portal_session_token_filepath) {
- int len = strlen(self->portal_session_token_filepath);
- if(len > 0 && self->portal_session_token_filepath[len - 1] == '/') {
- gsr_log(GSR_LOG_LEVEL_ERROR, "-portal-session-token-filepath should be a path to a file but it ends with a /: %s", self->portal_session_token_filepath);
+ self->settings.portal_session_token_filepath = args_get_value_by_key(self->args, NUM_ARGS, "-portal-session-token-filepath");
+ if(self->settings.portal_session_token_filepath) {
+ int len = strlen(self->settings.portal_session_token_filepath);
+ if(len > 0 && self->settings.portal_session_token_filepath[len - 1] == '/') {
+ gsr_log(GSR_LOG_LEVEL_ERROR, "-portal-session-token-filepath should be a path to a file but it ends with a /: %s", self->settings.portal_session_token_filepath);
return false;
}
}
- self->recording_saved_script = args_get_value_by_key(self->args, NUM_ARGS, "-sc");
- if(self->recording_saved_script) {
+ self->settings.recording_saved_script = args_get_value_by_key(self->args, NUM_ARGS, "-sc");
+ if(self->settings.recording_saved_script) {
struct stat buf;
- if(stat(self->recording_saved_script, &buf) == -1 || !S_ISREG(buf.st_mode)) {
- gsr_log(GSR_LOG_LEVEL_ERROR, "Script \"%s\" either doesn't exist or it's not a file", self->recording_saved_script);
+ if(stat(self->settings.recording_saved_script, &buf) == -1 || !S_ISREG(buf.st_mode)) {
+ gsr_log(GSR_LOG_LEVEL_ERROR, "Script \"%s\" either doesn't exist or it's not a file", self->settings.recording_saved_script);
usage();
return false;
}
if(!(buf.st_mode & S_IXUSR)) {
- gsr_log(GSR_LOG_LEVEL_ERROR, "Script \"%s\" is not executable", self->recording_saved_script);
+ gsr_log(GSR_LOG_LEVEL_ERROR, "Script \"%s\" is not executable", self->settings.recording_saved_script);
usage();
return false;
}
}
const char *quality_str = args_get_value_by_key(self->args, NUM_ARGS, "-q");
- self->video_quality = GSR_VIDEO_QUALITY_VERY_HIGH;
- self->video_bitrate = 0;
+ self->settings.video_quality = GSR_VIDEO_QUALITY_VERY_HIGH;
+ self->settings.video_bitrate = 0;
- if(self->bitrate_mode == GSR_BITRATE_MODE_CBR) {
+ if(self->settings.bitrate_mode == GSR_BITRATE_MODE_CBR) {
if(!quality_str) {
gsr_log(GSR_LOG_LEVEL_ERROR, "option '-q' is required when using '-bm cbr' option");
usage();
return false;
}
- if(sscanf(quality_str, "%" PRIi64, &self->video_bitrate) != 1) {
+ if(sscanf(quality_str, "%" PRIi64, &self->settings.video_bitrate) != 1) {
gsr_log(GSR_LOG_LEVEL_ERROR, "-q argument \"%s\" is not an integer value. When using '-bm cbr' option '-q' is expected to be an integer value", quality_str);
usage();
return false;
}
- if(self->video_bitrate < 0) {
- gsr_log(GSR_LOG_LEVEL_ERROR, "-q is expected to be 0 or larger, got %" PRIi64, self->video_bitrate);
+ if(self->settings.video_bitrate < 0) {
+ gsr_log(GSR_LOG_LEVEL_ERROR, "-q is expected to be 0 or larger, got %" PRIi64, self->settings.video_bitrate);
usage();
return false;
}
- self->video_bitrate *= 1000LL;
+ self->settings.video_bitrate *= 1000LL;
} else {
if(!quality_str)
quality_str = "very_high";
if(strcmp(quality_str, "medium") == 0) {
- self->video_quality = GSR_VIDEO_QUALITY_MEDIUM;
+ self->settings.video_quality = GSR_VIDEO_QUALITY_MEDIUM;
} else if(strcmp(quality_str, "high") == 0) {
- self->video_quality = GSR_VIDEO_QUALITY_HIGH;
+ self->settings.video_quality = GSR_VIDEO_QUALITY_HIGH;
} else if(strcmp(quality_str, "very_high") == 0) {
- self->video_quality = GSR_VIDEO_QUALITY_VERY_HIGH;
+ self->settings.video_quality = GSR_VIDEO_QUALITY_VERY_HIGH;
} else if(strcmp(quality_str, "ultra") == 0) {
- self->video_quality = GSR_VIDEO_QUALITY_ULTRA;
+ self->settings.video_quality = GSR_VIDEO_QUALITY_ULTRA;
} else {
gsr_log(GSR_LOG_LEVEL_ERROR, "-q should either be 'medium', 'high', 'very_high' or 'ultra', got: '%s'", quality_str);
usage();
@@ -358,118 +358,118 @@ static bool args_parser_set_values(args_parser *self) {
}
}
- self->output_resolution = (vec2i){0, 0};
+ self->settings.output_resolution = (vec2i){0, 0};
const char *output_resolution_str = args_get_value_by_key(self->args, NUM_ARGS, "-s");
if(output_resolution_str) {
- if(sscanf(output_resolution_str, "%dx%d", &self->output_resolution.x, &self->output_resolution.y) != 2) {
+ if(sscanf(output_resolution_str, "%dx%d", &self->settings.output_resolution.x, &self->settings.output_resolution.y) != 2) {
gsr_log(GSR_LOG_LEVEL_ERROR, "invalid value for option -s '%s', expected a value in format WxH", output_resolution_str);
usage();
return false;
}
- if(self->output_resolution.x < 0 || self->output_resolution.y < 0) {
+ if(self->settings.output_resolution.x < 0 || self->settings.output_resolution.y < 0) {
gsr_log(GSR_LOG_LEVEL_ERROR, "invalid value for option -s '%s', expected width and height to be greater or equal to 0", output_resolution_str);
usage();
return false;
}
}
- self->region_size = (vec2i){0, 0};
- self->region_position = (vec2i){0, 0};
+ self->settings.region_size = (vec2i){0, 0};
+ self->settings.region_position = (vec2i){0, 0};
const char *region_str = args_get_value_by_key(self->args, NUM_ARGS, "-region");
if(region_str) {
- if(sscanf(region_str, "%dx%d+%d+%d", &self->region_size.x, &self->region_size.y, &self->region_position.x, &self->region_position.y) != 4) {
+ if(sscanf(region_str, "%dx%d+%d+%d", &self->settings.region_size.x, &self->settings.region_size.y, &self->settings.region_position.x, &self->settings.region_position.y) != 4) {
gsr_log(GSR_LOG_LEVEL_ERROR, "invalid value for option -region '%s', expected a value in format WxH+X+Y", region_str);
usage();
return false;
}
- if(self->region_size.x < 0 || self->region_size.y < 0) {
+ if(self->settings.region_size.x < 0 || self->settings.region_size.y < 0) {
gsr_log(GSR_LOG_LEVEL_ERROR, "invalid value for option -region '%s', expected width and height to be greater or equal to 0", region_str);
usage();
return false;
}
}
- self->fps = args_get_i64_by_key(self->args, NUM_ARGS, "-f", 60);
- self->replay_buffer_size_secs = args_get_i64_by_key(self->args, NUM_ARGS, "-r", -1);
- if(self->replay_buffer_size_secs != -1)
- self->replay_buffer_size_secs += (int64_t)(self->keyint + 0.5); // Add a few seconds to account of lost packets because of non-keyframe packets skipped
+ self->settings.fps = args_get_i64_by_key(self->args, NUM_ARGS, "-f", 60);
+ self->settings.replay_buffer_size_secs = args_get_i64_by_key(self->args, NUM_ARGS, "-r", -1);
+ if(self->settings.replay_buffer_size_secs != -1)
+ self->settings.replay_buffer_size_secs += (int64_t)(self->settings.keyint + 0.5); // Add a few seconds to account of lost packets because of non-keyframe packets skipped
- self->container_format = args_get_value_by_key(self->args, NUM_ARGS, "-c");
- if(self->container_format && strcmp(self->container_format, "mkv") == 0)
- self->container_format = "matroska";
+ self->settings.container_format = args_get_value_by_key(self->args, NUM_ARGS, "-c");
+ if(self->settings.container_format && strcmp(self->settings.container_format, "mkv") == 0)
+ self->settings.container_format = "matroska";
- self->is_replaying = self->replay_buffer_size_secs != -1;
- self->is_livestream = false;
- self->filename = args_get_value_by_key(self->args, NUM_ARGS, "-o");
- if(self->filename) {
- self->is_livestream = is_livestream_path(self->filename);
- if(self->is_livestream) {
- if(self->is_replaying) {
+ self->settings.is_replaying = self->settings.replay_buffer_size_secs != -1;
+ self->settings.is_livestream = false;
+ self->settings.filename = args_get_value_by_key(self->args, NUM_ARGS, "-o");
+ if(self->settings.filename) {
+ self->settings.is_livestream = is_livestream_path(self->settings.filename);
+ if(self->settings.is_livestream) {
+ if(self->settings.is_replaying) {
gsr_log(GSR_LOG_LEVEL_ERROR, "replay mode is not applicable to live streaming");
return false;
}
} else {
- if(!self->is_replaying) {
+ if(!self->settings.is_replaying) {
char directory_buf[PATH_MAX];
- snprintf(directory_buf, sizeof(directory_buf), "%s", self->filename);
+ snprintf(directory_buf, sizeof(directory_buf), "%s", self->settings.filename);
char *directory = dirname(directory_buf);
if(strcmp(directory, ".") != 0 && strcmp(directory, "/") != 0) {
if(create_directory_recursive(directory) != 0) {
- gsr_log(GSR_LOG_LEVEL_ERROR, "failed to create directory for output file: %s", self->filename);
+ gsr_log(GSR_LOG_LEVEL_ERROR, "failed to create directory for output file: %s", self->settings.filename);
return false;
}
}
} else {
- if(!self->container_format) {
+ if(!self->settings.container_format) {
gsr_log(GSR_LOG_LEVEL_ERROR, "option -c is required when using option -r");
usage();
return false;
}
struct stat buf;
- if(stat(self->filename, &buf) != -1 && !S_ISDIR(buf.st_mode)) {
- gsr_log(GSR_LOG_LEVEL_ERROR, "File \"%s\" exists but it's not a directory", self->filename);
+ if(stat(self->settings.filename, &buf) != -1 && !S_ISDIR(buf.st_mode)) {
+ gsr_log(GSR_LOG_LEVEL_ERROR, "File \"%s\" exists but it's not a directory", self->settings.filename);
usage();
return false;
}
}
}
} else {
- if(!self->is_replaying) {
- self->filename = "/dev/stdout";
+ if(!self->settings.is_replaying) {
+ self->settings.filename = "/dev/stdout";
} else {
gsr_log(GSR_LOG_LEVEL_ERROR, "Option -o is required when using option -r");
usage();
return false;
}
- if(!self->container_format) {
+ if(!self->settings.container_format) {
gsr_log(GSR_LOG_LEVEL_ERROR, "option -c is required when not using option -o");
usage();
return false;
}
}
- self->is_output_piped = file_is_pipe_or_char_device(self->filename);
- self->low_latency_recording = self->is_livestream || self->is_output_piped;
- if(self->write_first_frame_ts && (self->is_livestream || self->is_output_piped)) {
+ self->settings.is_output_piped = file_is_pipe_or_char_device(self->settings.filename);
+ self->settings.low_latency_recording = self->settings.is_livestream || self->settings.is_output_piped;
+ if(self->settings.write_first_frame_ts && (self->settings.is_livestream || self->settings.is_output_piped)) {
gsr_log(GSR_LOG_LEVEL_WARNING, "-write-first-frame-ts is ignored for livestreaming or when output is piped");
- self->write_first_frame_ts = false;
+ self->settings.write_first_frame_ts = false;
}
- self->replay_recording_directory = args_get_value_by_key(self->args, NUM_ARGS, "-ro");
+ self->settings.replay_recording_directory = args_get_value_by_key(self->args, NUM_ARGS, "-ro");
- if(self->is_livestream && self->recording_saved_script) {
+ if(self->settings.is_livestream && self->settings.recording_saved_script) {
gsr_log(GSR_LOG_LEVEL_WARNING, "live stream detected, -sc script is ignored");
- self->recording_saved_script = NULL;
+ self->settings.recording_saved_script = NULL;
}
- self->ffmpeg_opts = args_get_value_by_key(self->args, NUM_ARGS, "-ffmpeg-opts");
- self->ffmpeg_video_opts = args_get_value_by_key(self->args, NUM_ARGS, "-ffmpeg-video-opts");
- self->ffmpeg_audio_opts = args_get_value_by_key(self->args, NUM_ARGS, "-ffmpeg-audio-opts");
+ self->settings.ffmpeg_opts = args_get_value_by_key(self->args, NUM_ARGS, "-ffmpeg-opts");
+ self->settings.ffmpeg_video_opts = args_get_value_by_key(self->args, NUM_ARGS, "-ffmpeg-video-opts");
+ self->settings.ffmpeg_audio_opts = args_get_value_by_key(self->args, NUM_ARGS, "-ffmpeg-audio-opts");
return true;
}
@@ -690,33 +690,33 @@ void args_parser_deinit(args_parser *self) {
bool args_parser_validate_with_gl_info(args_parser *self, gsr_egl *egl) {
const bool wayland = gsr_window_get_display_server(egl->window) == GSR_DISPLAY_SERVER_WAYLAND;
- if(self->bitrate_mode == (gsr_bitrate_mode)GSR_BITRATE_MODE_AUTO) {
+ if(self->settings.bitrate_mode == (gsr_bitrate_mode)GSR_BITRATE_MODE_AUTO) {
// QP is broken on steam deck, see https://github.com/ValveSoftware/SteamOS/issues/1609
- self->bitrate_mode = egl->gpu_info.is_steam_deck ? GSR_BITRATE_MODE_VBR : GSR_BITRATE_MODE_QP;
+ self->settings.bitrate_mode = egl->gpu_info.is_steam_deck ? GSR_BITRATE_MODE_VBR : GSR_BITRATE_MODE_QP;
}
- if(egl->gpu_info.is_steam_deck && self->bitrate_mode == GSR_BITRATE_MODE_QP) {
+ if(egl->gpu_info.is_steam_deck && self->settings.bitrate_mode == GSR_BITRATE_MODE_QP) {
gsr_log(GSR_LOG_LEVEL_WARNING, "qp bitrate mode is not supported on Steam Deck because of Steam Deck driver bugs. Using vbr instead");
- self->bitrate_mode = GSR_BITRATE_MODE_VBR;
+ self->settings.bitrate_mode = GSR_BITRATE_MODE_VBR;
}
- if(self->video_encoder == GSR_VIDEO_ENCODER_HW_CPU && self->bitrate_mode == GSR_BITRATE_MODE_VBR) {
+ if(self->settings.video_encoder == GSR_VIDEO_ENCODER_HW_CPU && self->settings.bitrate_mode == GSR_BITRATE_MODE_VBR) {
gsr_log(GSR_LOG_LEVEL_WARNING, "bitrate mode has been forcefully set to qp because software encoding option doesn't support vbr option");
- self->bitrate_mode = GSR_BITRATE_MODE_QP;
+ self->settings.bitrate_mode = GSR_BITRATE_MODE_QP;
}
if(egl->gpu_info.is_steam_deck) {
gsr_log(GSR_LOG_LEVEL_WARNING, "steam deck has multiple driver issues. One of them has been reported here: https://github.com/ValveSoftware/SteamOS/issues/1609\nIf you have issues with GPU Screen Recorder on steam deck that you don't have on a desktop computer then report the issue to Valve and/or AMD.");
}
- self->very_old_gpu = false;
+ self->settings.very_old_gpu = false;
if(egl->gpu_info.vendor == GSR_GPU_VENDOR_NVIDIA && egl->gpu_info.gpu_version != 0 && egl->gpu_info.gpu_version < 900) {
gsr_log(GSR_LOG_LEVEL_INFO, "your gpu appears to be very old (older than maxwell architecture). Switching to lower preset");
- self->very_old_gpu = true;
+ self->settings.very_old_gpu = true;
}
- if(video_codec_is_hdr(self->video_codec) && !wayland) {
- gsr_log(GSR_LOG_LEVEL_ERROR, "hdr video codec option %s is not available on X11", video_codec_to_string(self->video_codec));
+ if(video_codec_is_hdr(self->settings.video_codec) && !wayland) {
+ gsr_log(GSR_LOG_LEVEL_ERROR, "hdr video codec option %s is not available on X11", video_codec_to_string(self->settings.video_codec));
usage();
return false;
}
diff --git a/src/ffmpeg_utils.c b/src/ffmpeg_utils.c
new file mode 100644
index 0000000..ce239a1
--- /dev/null
+++ b/src/ffmpeg_utils.c
@@ -0,0 +1,12 @@
+#include "../include/ffmpeg_utils.h"
+
+#include <string.h>
+#include <libavutil/error.h>
+
+static _Thread_local char av_error_buffer[AV_ERROR_MAX_STRING_SIZE];
+
+const char* gsr_av_error_to_string(int err) {
+ if(av_strerror(err, av_error_buffer, sizeof(av_error_buffer)) < 0)
+ strcpy(av_error_buffer, "Unknown error");
+ return av_error_buffer;
+}
diff --git a/src/main.cpp b/src/main.cpp
index 6862b99..b618a3b 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -1,4 +1,7 @@
extern "C" {
+#include "../include/ffmpeg_utils.h"
+#include "../include/recorder/audio_codec.h"
+#include "../include/recorder/video_codec.h"
#include "../include/capture/nvfbc.h"
#include "../include/capture/xcomposite.h"
#include "../include/capture/ximage.h"
@@ -91,12 +94,8 @@ struct MergedAudioInputs {
// TODO: Remove LIBAVUTIL_VERSION_MAJOR checks in the future when ubuntu, pop os LTS etc update ffmpeg to >= 5.0
-static const int AUDIO_SAMPLE_RATE = 48000;
-
static const int VIDEO_STREAM_INDEX = 0;
-static thread_local char av_error_buffer[AV_ERROR_MAX_STRING_SIZE];
-
typedef struct {
const gsr_window *window;
} MonitorOutputCallbackUserdata;
@@ -150,12 +149,6 @@ static void get_monitor_by_position_callback(const gsr_monitor *monitor, void *u
}
}
-static char* av_error_to_string(int err) {
- if(av_strerror(err, av_error_buffer, sizeof(av_error_buffer)) < 0)
- strcpy(av_error_buffer, "Unknown error");
- return av_error_buffer;
-}
-
static int x11_error_handler(Display*, XErrorEvent*) {
return 0;
}
@@ -164,595 +157,9 @@ static int x11_io_error_handler(Display*) {
return 0;
}
-static AVCodecID audio_codec_get_id(gsr_audio_codec audio_codec) {
- switch(audio_codec) {
- case GSR_AUDIO_CODEC_AAC: return AV_CODEC_ID_AAC;
- case GSR_AUDIO_CODEC_OPUS: return AV_CODEC_ID_OPUS;
- case GSR_AUDIO_CODEC_FLAC: return AV_CODEC_ID_FLAC;
- }
- assert(false);
- return AV_CODEC_ID_AAC;
-}
-
-static AVSampleFormat audio_codec_get_sample_format(AVCodecContext *audio_codec_context, gsr_audio_codec audio_codec, const AVCodec *codec, bool mix_audio) {
- (void)audio_codec_context;
- switch(audio_codec) {
- case GSR_AUDIO_CODEC_AAC: {
- return AV_SAMPLE_FMT_FLTP;
- }
- case GSR_AUDIO_CODEC_OPUS: {
- bool supports_s16 = false;
- bool supports_flt = false;
-
- #if LIBAVCODEC_VERSION_INT < AV_VERSION_INT(61, 15, 0)
- for(size_t i = 0; codec->sample_fmts && codec->sample_fmts[i] != AV_SAMPLE_FMT_NONE; ++i) {
- if(codec->sample_fmts[i] == AV_SAMPLE_FMT_S16) {
- supports_s16 = true;
- } else if(codec->sample_fmts[i] == AV_SAMPLE_FMT_FLT) {
- supports_flt = true;
- }
- }
- #else
- const enum AVSampleFormat *sample_fmts = NULL;
- if(avcodec_get_supported_config(audio_codec_context, codec, AV_CODEC_CONFIG_SAMPLE_FORMAT, 0, (const void**)&sample_fmts, NULL) >= 0) {
- if(sample_fmts) {
- for(size_t i = 0; sample_fmts[i] != AV_SAMPLE_FMT_NONE; ++i) {
- if(sample_fmts[i] == AV_SAMPLE_FMT_S16) {
- supports_s16 = true;
- } else if(sample_fmts[i] == AV_SAMPLE_FMT_FLT) {
- supports_flt = true;
- }
- }
- } else {
- // What a dumb API. It returns NULL if all formats are supported
- supports_s16 = true;
- supports_flt = true;
- }
- }
- #endif
-
- // Amix only works with float audio
- if(mix_audio)
- supports_s16 = false;
-
- if(!supports_s16 && !supports_flt) {
- gsr_log(GSR_LOG_LEVEL_WARNING, "opus audio codec is chosen but your ffmpeg version does not support s16/flt sample format and performance might be slightly worse.");
- fprintf(stderr, " You can either rebuild ffmpeg with libopus instead of the built-in opus, use the flatpak version of gpu screen recorder or record with aac audio codec instead (-ac aac).\n");
- fprintf(stderr, " Falling back to fltp audio sample format instead.\n");
- }
-
- if(supports_s16)
- return AV_SAMPLE_FMT_S16;
- else if(supports_flt)
- return AV_SAMPLE_FMT_FLT;
- else
- return AV_SAMPLE_FMT_FLTP;
- }
- case GSR_AUDIO_CODEC_FLAC: {
- return AV_SAMPLE_FMT_S32;
- }
- }
- assert(false);
- return AV_SAMPLE_FMT_FLTP;
-}
-
-static int64_t audio_codec_get_get_bitrate(gsr_audio_codec audio_codec) {
- switch(audio_codec) {
- case GSR_AUDIO_CODEC_AAC: return 160000;
- case GSR_AUDIO_CODEC_OPUS: return 128000;
- case GSR_AUDIO_CODEC_FLAC: return 128000;
- }
- assert(false);
- return 128000;
-}
-
-static gsr_audio_format audio_codec_context_get_audio_format(const AVCodecContext *audio_codec_context) {
- switch(audio_codec_context->sample_fmt) {
- case AV_SAMPLE_FMT_FLT: return GSR_AUDIO_FORMAT_F32;
- case AV_SAMPLE_FMT_FLTP: return GSR_AUDIO_FORMAT_S32;
- case AV_SAMPLE_FMT_S16: return GSR_AUDIO_FORMAT_S16;
- case AV_SAMPLE_FMT_S32: return GSR_AUDIO_FORMAT_S32;
- default: return GSR_AUDIO_FORMAT_S16;
- }
-}
-
-static AVSampleFormat audio_format_to_sample_format(const gsr_audio_format audio_format) {
- switch(audio_format) {
- case GSR_AUDIO_FORMAT_S16: return AV_SAMPLE_FMT_S16;
- case GSR_AUDIO_FORMAT_S32: return AV_SAMPLE_FMT_S32;
- case GSR_AUDIO_FORMAT_F32: return AV_SAMPLE_FMT_FLT;
- }
- assert(false);
- return AV_SAMPLE_FMT_S16;
-}
-
-static AVCodecContext* create_audio_codec_context(int fps, gsr_audio_codec audio_codec, bool mix_audio, int64_t audio_bitrate) {
- (void)fps;
- const AVCodec *codec = avcodec_find_encoder(audio_codec_get_id(audio_codec));
- if (!codec) {
- gsr_log(GSR_LOG_LEVEL_ERROR, "Could not find %s audio encoder", audio_codec_get_name(audio_codec));
- _exit(1);
- }
-
- AVCodecContext *codec_context = avcodec_alloc_context3(codec);
-
- assert(codec->type == AVMEDIA_TYPE_AUDIO);
- codec_context->codec_id = codec->id;
- codec_context->sample_fmt = audio_codec_get_sample_format(codec_context, audio_codec, codec, mix_audio);
- codec_context->bit_rate = audio_bitrate == 0 ? audio_codec_get_get_bitrate(audio_codec) : audio_bitrate;
- codec_context->sample_rate = AUDIO_SAMPLE_RATE;
- if(audio_codec == GSR_AUDIO_CODEC_AAC) {
-#if LIBAVCODEC_VERSION_MAJOR < 62
- codec_context->profile = FF_PROFILE_AAC_LOW;
-#else
- codec_context->profile = AV_PROFILE_AAC_LOW;
-#endif
- }
-#if LIBAVCODEC_VERSION_MAJOR < 60
- codec_context->channel_layout = AV_CH_LAYOUT_STEREO;
- codec_context->channels = 2;
-#else
- av_channel_layout_default(&codec_context->ch_layout, 2);
-#endif
-
- codec_context->time_base.num = 1;
- codec_context->time_base.den = codec_context->sample_rate;
- codec_context->thread_count = 1;
- codec_context->flags |= AV_CODEC_FLAG_GLOBAL_HEADER;
-
- return codec_context;
-}
-
-static int video_quality_to_h264_equivalent_qp(gsr_video_quality video_quality) {
- switch(video_quality) {
- case GSR_VIDEO_QUALITY_MEDIUM: return 35;
- case GSR_VIDEO_QUALITY_HIGH: return 30;
- case GSR_VIDEO_QUALITY_VERY_HIGH: return 25;
- case GSR_VIDEO_QUALITY_ULTRA: return 22;
- }
- return 22;
-}
-
/* AV1/VP9 use the 0-255 qindex quantizer scale and VP8 uses 0-127. Unlike h264 qp the quantizer step size
is not exponential in qindex, so the mapping to an equivalent h264 qp is not linear in the quantizer range.
qindex = h264 qp * 4 matches the qindex = crf * 4 scale used by libvpx/libaom, where crf is roughly equivalent to h264 qp. */
-static int video_quality_to_codec_quality_value(AVCodecID codec_id, gsr_video_quality video_quality) {
- const int h264_qp = video_quality_to_h264_equivalent_qp(video_quality);
- switch(codec_id) {
- case AV_CODEC_ID_H264:
- case AV_CODEC_ID_HEVC:
- return h264_qp;
- case AV_CODEC_ID_AV1:
- case AV_CODEC_ID_VP9:
- return h264_qp * 4;
- case AV_CODEC_ID_VP8:
- return h264_qp * 2;
- default:
- return h264_qp;
- }
-}
-
-static int vbr_get_quality_parameter(AVCodecContext *codec_context, gsr_video_quality video_quality, bool hdr) {
- // 8 bit / 10 bit = 80%
- const float qp_multiply = hdr ? 8.0f/10.0f : 1.0f;
- return video_quality_to_codec_quality_value(codec_context->codec_id, video_quality) * qp_multiply;
-}
-
-static AVCodecContext *create_video_codec_context(AVPixelFormat pix_fmt, const AVCodec *codec, const gsr_egl &egl, const args_parser &arg_parser, int width, int height) {
- const bool use_software_video_encoder = arg_parser.video_encoder == GSR_VIDEO_ENCODER_HW_CPU;
- const bool hdr = video_codec_is_hdr(arg_parser.video_codec);
- AVCodecContext *codec_context = avcodec_alloc_context3(codec);
-
- //double fps_ratio = (double)fps / 30.0;
-
- assert(codec->type == AVMEDIA_TYPE_VIDEO);
- codec_context->codec_id = codec->id;
- codec_context->width = width;
- codec_context->height = height;
- // Timebase: This is the fundamental unit of time (in seconds) in terms
- // of which frame timestamps are represented. For fixed-fps content,
- // timebase should be 1/framerate and timestamp increments should be
- // identical to 1
- codec_context->time_base.num = 1;
- codec_context->time_base.den = arg_parser.framerate_mode == GSR_FRAMERATE_MODE_CONSTANT ? arg_parser.fps : AV_TIME_BASE;
- codec_context->framerate.num = arg_parser.fps;
- codec_context->framerate.den = 1;
- codec_context->sample_aspect_ratio.num = 0;
- codec_context->sample_aspect_ratio.den = 0;
- if(arg_parser.low_latency_recording) {
- codec_context->flags |= (AV_CODEC_FLAG_CLOSED_GOP | AV_CODEC_FLAG_LOW_DELAY);
- codec_context->flags2 |= AV_CODEC_FLAG2_FAST;
- //codec_context->gop_size = std::numeric_limits<int>::max();
- //codec_context->keyint_min = std::numeric_limits<int>::max();
- codec_context->gop_size = arg_parser.fps * arg_parser.keyint;
- } else {
- // High values reduce file size but increases time it takes to seek
- codec_context->gop_size = arg_parser.fps * arg_parser.keyint;
- }
- codec_context->max_b_frames = 0;
- codec_context->pix_fmt = pix_fmt;
- codec_context->color_range = arg_parser.color_range == GSR_COLOR_RANGE_LIMITED ? AVCOL_RANGE_MPEG : AVCOL_RANGE_JPEG;
- if(hdr) {
- codec_context->color_primaries = AVCOL_PRI_BT2020;
- codec_context->color_trc = AVCOL_TRC_SMPTE2084;
- codec_context->colorspace = AVCOL_SPC_BT2020_NCL;
- } else {
- codec_context->color_primaries = AVCOL_PRI_BT709;
- codec_context->color_trc = AVCOL_TRC_BT709;
- codec_context->colorspace = AVCOL_SPC_BT709;
- }
- //codec_context->chroma_sample_location = AVCHROMA_LOC_CENTER;
- // Can't use this because it's fucking broken in ffmpeg 8 or new mesa. It produces garbage output
- //if(codec->id == AV_CODEC_ID_HEVC)
- // codec_context->codec_tag = MKTAG('h', 'v', 'c', '1'); // QuickTime on MacOS requires this or the video wont be playable
-
- if(arg_parser.bitrate_mode == GSR_BITRATE_MODE_CBR) {
- codec_context->bit_rate = arg_parser.video_bitrate;
- codec_context->rc_max_rate = codec_context->bit_rate;
- //codec_context->rc_min_rate = codec_context->bit_rate;
- codec_context->rc_buffer_size = codec_context->bit_rate;//codec_context->bit_rate / 10;
- codec_context->rc_initial_buffer_occupancy = 0;//codec_context->bit_rate;//codec_context->bit_rate * 1000;
- } else if(arg_parser.bitrate_mode == GSR_BITRATE_MODE_VBR) {
- const int quality = vbr_get_quality_parameter(codec_context, arg_parser.video_quality, hdr);
- switch(arg_parser.video_quality) {
- case GSR_VIDEO_QUALITY_MEDIUM:
- codec_context->qmin = quality;
- codec_context->qmax = quality;
- codec_context->bit_rate = 100000;//4500000 + (codec_context->width * codec_context->height)*0.75;
- break;
- case GSR_VIDEO_QUALITY_HIGH:
- codec_context->qmin = quality;
- codec_context->qmax = quality;
- codec_context->bit_rate = 100000;//10000000-9000000 + (codec_context->width * codec_context->height)*0.75;
- break;
- case GSR_VIDEO_QUALITY_VERY_HIGH:
- codec_context->qmin = quality;
- codec_context->qmax = quality;
- codec_context->bit_rate = 100000;//10000000-9000000 + (codec_context->width * codec_context->height)*0.75;
- break;
- case GSR_VIDEO_QUALITY_ULTRA:
- codec_context->qmin = quality;
- codec_context->qmax = quality;
- codec_context->bit_rate = 100000;//10000000-9000000 + (codec_context->width * codec_context->height)*0.75;
- break;
- }
-
- codec_context->rc_max_rate = codec_context->bit_rate;
- //codec_context->rc_min_rate = codec_context->bit_rate;
- codec_context->rc_buffer_size = codec_context->bit_rate;//codec_context->bit_rate / 10;
- codec_context->rc_initial_buffer_occupancy = codec_context->bit_rate;//codec_context->bit_rate * 1000;
- } else {
- //codec_context->rc_buffer_size = 50000 * 1000;
- }
- //codec_context->profile = FF_PROFILE_H264_MAIN;
- if (codec_context->codec_id == AV_CODEC_ID_MPEG1VIDEO)
- codec_context->mb_decision = 2;
-
- const bool uses_vaapi_encoder = !use_software_video_encoder && egl.gpu_info.vendor != GSR_GPU_VENDOR_NVIDIA && !video_codec_is_vulkan(arg_parser.video_codec);
- if(uses_vaapi_encoder && arg_parser.bitrate_mode != GSR_BITRATE_MODE_CBR) {
- // 8 bit / 10 bit = 80%, and increase it even more
- const float quality_multiply = hdr ? (8.0f/10.0f * 0.7f) : 1.0f;
- codec_context->global_quality = video_quality_to_codec_quality_value(codec_context->codec_id, arg_parser.video_quality) * quality_multiply;
- }
-
- av_opt_set_int(codec_context->priv_data, "b_ref_mode", 0, 0);
- //av_opt_set_int(codec_context->priv_data, "cbr", true, 0);
-
- if(egl.gpu_info.vendor != GSR_GPU_VENDOR_NVIDIA || video_codec_is_vulkan(arg_parser.video_codec)) {
- // TODO: More options, better options
- //codec_context->bit_rate = codec_context->width * codec_context->height;
- switch(arg_parser.bitrate_mode) {
- case GSR_BITRATE_MODE_QP: {
- if(video_codec_is_vulkan(arg_parser.video_codec))
- av_opt_set(codec_context->priv_data, "rc_mode", "cqp", 0);
- else if(egl.gpu_info.vendor == GSR_GPU_VENDOR_NVIDIA)
- av_opt_set(codec_context->priv_data, "rc", "constqp", 0);
- else
- av_opt_set(codec_context->priv_data, "rc_mode", "CQP", 0);
- break;
- }
- case GSR_BITRATE_MODE_VBR: {
- if(video_codec_is_vulkan(arg_parser.video_codec))
- av_opt_set(codec_context->priv_data, "rc_mode", "vbr", 0);
- else if(egl.gpu_info.vendor == GSR_GPU_VENDOR_NVIDIA)
- av_opt_set(codec_context->priv_data, "rc", "vbr", 0);
- else
- av_opt_set(codec_context->priv_data, "rc_mode", "VBR", 0);
- break;
- }
- case GSR_BITRATE_MODE_CBR: {
- if(video_codec_is_vulkan(arg_parser.video_codec))
- av_opt_set(codec_context->priv_data, "rc_mode", "cbr", 0);
- else if(egl.gpu_info.vendor == GSR_GPU_VENDOR_NVIDIA)
- av_opt_set(codec_context->priv_data, "rc", "cbr", 0);
- else
- av_opt_set(codec_context->priv_data, "rc_mode", "CBR", 0);
- break;
- }
- }
- //codec_context->global_quality = 4;
- //codec_context->compression_level = 2;
- }
-
- //av_opt_set(codec_context->priv_data, "bsf", "hevc_metadata=colour_primaries=9:transfer_characteristics=16:matrix_coefficients=9", 0);
-
- if(arg_parser.tune == GSR_TUNE_QUALITY)
- codec_context->max_b_frames = 2;
-
- codec_context->flags |= AV_CODEC_FLAG_GLOBAL_HEADER;
-
- return codec_context;
-}
-
-static void open_audio(AVCodecContext *audio_codec_context, const char *ffmpeg_audio_opts) {
- AVDictionary *options = nullptr;
- av_dict_set(&options, "strict", "experimental", 0);
-
- if(ffmpeg_audio_opts)
- av_dict_parse_string(&options, ffmpeg_audio_opts, "=", ";", 0);
-
- int ret;
- ret = avcodec_open2(audio_codec_context, audio_codec_context->codec, &options);
- if(ret < 0) {
- fprintf(stderr, "failed to open codec, reason: %s\n", av_error_to_string(ret));
- _exit(1);
- }
-}
-
-static AVFrame* create_audio_frame(AVCodecContext *audio_codec_context) {
- AVFrame *frame = av_frame_alloc();
- if(!frame) {
- fprintf(stderr, "failed to allocate audio frame\n");
- _exit(1);
- }
-
- frame->sample_rate = audio_codec_context->sample_rate;
- frame->nb_samples = audio_codec_context->frame_size;
- frame->format = audio_codec_context->sample_fmt;
-#if LIBAVCODEC_VERSION_MAJOR < 60
- frame->channels = audio_codec_context->channels;
- frame->channel_layout = audio_codec_context->channel_layout;
-#else
- av_channel_layout_copy(&frame->ch_layout, &audio_codec_context->ch_layout);
-#endif
-
- int ret = av_frame_get_buffer(frame, 0);
- if(ret < 0) {
- fprintf(stderr, "failed to allocate audio data buffers, reason: %s\n", av_error_to_string(ret));
- _exit(1);
- }
-
- return frame;
-}
-
-static void dict_set_profile(AVCodecContext *codec_context, gsr_gpu_vendor vendor, gsr_color_depth color_depth, gsr_video_codec video_codec, AVDictionary **options) {
- #if LIBAVCODEC_VERSION_INT < AV_VERSION_INT(61, 17, 100)
- if(codec_context->codec_id == AV_CODEC_ID_H264) {
- // TODO: Only for vaapi
- //if(color_depth == GSR_COLOR_DEPTH_10_BITS)
- // av_dict_set(options, "profile", "high10", 0);
- //else
- av_dict_set(options, "profile", "high", 0);
- } else if(codec_context->codec_id == AV_CODEC_ID_AV1) {
- if(vendor == GSR_GPU_VENDOR_NVIDIA) {
- if(color_depth == GSR_COLOR_DEPTH_10_BITS)
- av_dict_set_int(options, "highbitdepth", 1, 0);
- } else {
- av_dict_set(options, "profile", "main", 0); // TODO: use professional instead?
- }
- } else if(codec_context->codec_id == AV_CODEC_ID_HEVC) {
- if(color_depth == GSR_COLOR_DEPTH_10_BITS)
- av_dict_set(options, "profile", "main10", 0);
- else
- av_dict_set(options, "profile", "main", 0);
- }
- #else
- const bool use_nvidia_values = vendor == GSR_GPU_VENDOR_NVIDIA && !video_codec_is_vulkan(video_codec);
- if(codec_context->codec_id == AV_CODEC_ID_H264) {
- // TODO: Only for vaapi
- //if(color_depth == GSR_COLOR_DEPTH_10_BITS)
- // av_dict_set_int(options, "profile", AV_PROFILE_H264_HIGH_10, 0);
- //else
- av_dict_set_int(options, "profile", use_nvidia_values ? 2 : AV_PROFILE_H264_HIGH, 0);
- } else if(codec_context->codec_id == AV_CODEC_ID_AV1) {
- if(use_nvidia_values) {
- if(color_depth == GSR_COLOR_DEPTH_10_BITS)
- av_dict_set_int(options, "highbitdepth", 1, 0);
- } else {
- av_dict_set_int(options, "profile", AV_PROFILE_AV1_MAIN, 0); // TODO: use professional instead?
- }
- } else if(codec_context->codec_id == AV_CODEC_ID_HEVC) {
- if(color_depth == GSR_COLOR_DEPTH_10_BITS)
- av_dict_set_int(options, "profile", use_nvidia_values ? 1 : AV_PROFILE_HEVC_MAIN_10, 0);
- else
- av_dict_set_int(options, "profile", use_nvidia_values ? 0 : AV_PROFILE_HEVC_MAIN, 0);
- }
- #endif
-}
-
-static void video_software_set_qp(AVCodecContext *codec_context, gsr_video_quality video_quality, bool hdr, AVDictionary **options) {
- // 8 bit / 10 bit = 80%
- const float qp_multiply = hdr ? 8.0f/10.0f : 1.0f;
- av_dict_set_int(options, "qp", video_quality_to_codec_quality_value(codec_context->codec_id, video_quality) * qp_multiply, 0);
-}
-
-static void open_video_software(AVCodecContext *codec_context, const args_parser &arg_parser) {
- const bool hdr = video_codec_is_hdr(arg_parser.video_codec);
- AVDictionary *options = nullptr;
-
- if(arg_parser.bitrate_mode == GSR_BITRATE_MODE_QP)
- video_software_set_qp(codec_context, arg_parser.video_quality, hdr, &options);
-
- av_dict_set(&options, "preset", "veryfast", 0);
- av_dict_set(&options, "tune", "film", 0);
- av_dict_set_int(&options, "forced-idr", 1, 0);
-
- if(codec_context->codec_id == AV_CODEC_ID_H264) {
- av_dict_set(&options, "coder", "cabac", 0); // TODO: cavlc is faster than cabac but worse compression. Which to use?
- }
-
- av_dict_set(&options, "strict", "experimental", 0);
-
- if(arg_parser.ffmpeg_video_opts)
- av_dict_parse_string(&options, arg_parser.ffmpeg_video_opts, "=", ";", 0);
-
- int ret = avcodec_open2(codec_context, codec_context->codec, &options);
- if (ret < 0) {
- gsr_log(GSR_LOG_LEVEL_ERROR, "Could not open video codec: %s", av_error_to_string(ret));
- _exit(1);
- }
-}
-
-static void video_set_rc(gsr_video_codec video_codec, gsr_gpu_vendor vendor, gsr_bitrate_mode bitrate_mode, AVDictionary **options) {
- switch(bitrate_mode) {
- case GSR_BITRATE_MODE_QP: {
- if(video_codec_is_vulkan(video_codec))
- av_dict_set(options, "rc_mode", "cqp", 0);
- else if(vendor == GSR_GPU_VENDOR_NVIDIA)
- av_dict_set(options, "rc", "constqp", 0);
- else
- av_dict_set(options, "rc_mode", "CQP", 0);
- break;
- }
- case GSR_BITRATE_MODE_VBR: {
- if(video_codec_is_vulkan(video_codec))
- av_dict_set(options, "rc_mode", "vbr", 0);
- else if(vendor == GSR_GPU_VENDOR_NVIDIA)
- av_dict_set(options, "rc", "vbr", 0);
- else
- av_dict_set(options, "rc_mode", "VBR", 0);
- break;
- }
- case GSR_BITRATE_MODE_CBR: {
- if(video_codec_is_vulkan(video_codec))
- av_dict_set(options, "rc_mode", "cbr", 0);
- else if(vendor == GSR_GPU_VENDOR_NVIDIA)
- av_dict_set(options, "rc", "cbr", 0);
- else
- av_dict_set(options, "rc_mode", "CBR", 0);
- break;
- }
- }
-}
-
-static void video_hardware_set_qp(AVCodecContext *codec_context, gsr_video_quality video_quality, bool hdr, AVDictionary **options) {
- // 8 bit / 10 bit = 80%
- const float qp_multiply = hdr ? 8.0f/10.0f : 1.0f;
- av_dict_set_int(options, "qp", video_quality_to_codec_quality_value(codec_context->codec_id, video_quality) * qp_multiply, 0);
-}
-
-static void open_video_hardware(AVCodecContext *codec_context, bool low_power, const gsr_egl &egl, const args_parser &arg_parser) {
- const gsr_color_depth color_depth = video_codec_to_bit_depth(arg_parser.video_codec);
- const bool hdr = video_codec_is_hdr(arg_parser.video_codec);
- AVDictionary *options = nullptr;
-
- if(arg_parser.bitrate_mode == GSR_BITRATE_MODE_QP)
- video_hardware_set_qp(codec_context, arg_parser.video_quality, hdr, &options);
-
- video_set_rc(arg_parser.video_codec, egl.gpu_info.vendor, arg_parser.bitrate_mode, &options);
-
- // TODO: Enable multipass
-
- dict_set_profile(codec_context, egl.gpu_info.vendor, color_depth, arg_parser.video_codec, &options);
-
- if(video_codec_is_vulkan(arg_parser.video_codec)) {
- av_dict_set_int(&options, "async_depth", 3, 0);
- av_dict_set(&options, "tune", "ll", 0); // Low latency
- av_dict_set(&options, "usage", arg_parser.is_livestream ? "stream" : "record", 0);
- av_dict_set(&options, "content", "rendered", 0); // Game or 3D content
-
- if(codec_context->codec_id == AV_CODEC_ID_H264) {
- // Removed because it causes stutter in games for some people
- //av_dict_set_int(&options, "quality", 5, 0); // quality preset
- } else if(codec_context->codec_id == AV_CODEC_ID_AV1) {
- av_dict_set(&options, "tier", "main", 0);
- } else if(codec_context->codec_id == AV_CODEC_ID_HEVC) {
- if(hdr)
- av_dict_set(&options, "sei", "hdr", 0);
- }
- } else if(egl.gpu_info.vendor == GSR_GPU_VENDOR_NVIDIA) {
- // TODO: These dont seem to be necessary
- // av_dict_set_int(&options, "zerolatency", 1, 0);
- // if(codec_context->codec_id == AV_CODEC_ID_AV1) {
- // av_dict_set(&options, "tune", "ll", 0);
- // } else if(codec_context->codec_id == AV_CODEC_ID_H264 || codec_context->codec_id == AV_CODEC_ID_HEVC) {
- // av_dict_set(&options, "preset", "llhq", 0);
- // av_dict_set(&options, "tune", "ll", 0);
- // }
- av_dict_set(&options, "tune", "ll", 0);
- av_dict_set_int(&options, "forced-idr", 1, 0);
-
- switch(arg_parser.tune) {
- case GSR_TUNE_PERFORMANCE:
- //av_dict_set(&options, "multipass", "qres", 0);
- break;
- case GSR_TUNE_QUALITY:
- av_dict_set(&options, "multipass", "fullres", 0);
- av_dict_set(&options, "preset", "p6", 0);
- av_dict_set_int(&options, "rc-lookahead", 0, 0);
- break;
- }
-
- if(codec_context->codec_id == AV_CODEC_ID_H264) {
- // TODO: h264 10bit?
- // TODO:
- // switch(pixel_format) {
- // case GSR_PIXEL_FORMAT_YUV420:
- // av_dict_set_int(&options, "profile", AV_PROFILE_H264_HIGH, 0);
- // break;
- // case GSR_PIXEL_FORMAT_YUV444:
- // av_dict_set_int(&options, "profile", AV_PROFILE_H264_HIGH_444, 0);
- // break;
- // }
- } else if(codec_context->codec_id == AV_CODEC_ID_AV1) {
- switch(arg_parser.pixel_format) {
- case GSR_PIXEL_FORMAT_YUV420:
- av_dict_set(&options, "rgb_mode", "yuv420", 0);
- break;
- case GSR_PIXEL_FORMAT_YUV444:
- av_dict_set(&options, "rgb_mode", "yuv444", 0);
- break;
- }
- } else if(codec_context->codec_id == AV_CODEC_ID_HEVC) {
- //av_dict_set(&options, "pix_fmt", "yuv420p16le", 0);
- }
- } else {
- // TODO: More quality options
- if(low_power)
- av_dict_set_int(&options, "low_power", 1, 0);
- // Improves performance but increases vram.
- // TODO: Might need a different async_depth for optimal performance on different amd/intel gpus
- av_dict_set_int(&options, "async_depth", 3, 0);
-
- if(codec_context->codec_id == AV_CODEC_ID_H264) {
- // Removed because it causes stutter in games for some people
- //av_dict_set_int(&options, "quality", 5, 0); // quality preset
- } else if(codec_context->codec_id == AV_CODEC_ID_AV1) {
- av_dict_set(&options, "tier", "main", 0);
- } else if(codec_context->codec_id == AV_CODEC_ID_HEVC) {
- if(hdr)
- av_dict_set(&options, "sei", "hdr", 0);
- }
-
- // TODO: vp8/vp9 10bit
- }
-
- if(codec_context->codec_id == AV_CODEC_ID_H264) {
- av_dict_set(&options, "coder", "cabac", 0); // TODO: cavlc is faster than cabac but worse compression. Which to use?
- }
-
- av_dict_set(&options, "strict", "experimental", 0);
-
- if(arg_parser.ffmpeg_video_opts)
- av_dict_parse_string(&options, arg_parser.ffmpeg_video_opts, "=", ";", 0);
-
- int ret = avcodec_open2(codec_context, codec_context->codec, &options);
- if (ret < 0) {
- gsr_log(GSR_LOG_LEVEL_ERROR, "Could not open video codec: %s", av_error_to_string(ret));
- _exit(1);
- }
-}
-
static const int save_replay_seconds_full = -1;
static sig_atomic_t running = 1;
@@ -860,22 +267,6 @@ static void run_recording_saved_script_async(const char *script_file, const char
}
}
-static double audio_codec_get_desired_delay(gsr_audio_codec audio_codec, int fps) {
- const double fps_inv = 1.0 / (double)fps;
- const double base = 0.01 + 1.0/165.0;
- switch(audio_codec) {
- case GSR_AUDIO_CODEC_OPUS:
- return std::max(0.0, base - fps_inv);
- case GSR_AUDIO_CODEC_AAC:
- return std::max(0.0, (base + 0.008) * 2.0 - fps_inv);
- case GSR_AUDIO_CODEC_FLAC:
- // TODO: Test
- return std::max(0.0, base - fps_inv);
- }
- assert(false);
- return std::max(0.0, base - fps_inv);
-}
-
struct AudioDeviceData {
SoundDevice sound_device;
AudioInput audio_input;
@@ -1013,7 +404,7 @@ struct VideoSource {
static RecordingStartResult start_recording_create_streams(const char *filename, const args_parser &arg_parser, AVCodecContext *video_codec_context, const std::vector<AudioTrack> &audio_tracks, bool hdr, std::vector<VideoSource> &video_sources) {
AVFormatContext *av_format_context;
- avformat_alloc_output_context2(&av_format_context, nullptr, arg_parser.container_format, filename);
+ avformat_alloc_output_context2(&av_format_context, nullptr, arg_parser.settings.container_format, filename);
set_format_context_options(av_format_context);
AVStream *video_stream = create_stream(av_format_context, video_codec_context);
@@ -1024,7 +415,7 @@ static RecordingStartResult start_recording_create_streams(const char *filename,
for(const AudioTrack &audio_track : audio_tracks) {
AVStream *audio_stream = create_stream(av_format_context, audio_track.codec_context);
- if(!audio_track.name.empty() && !arg_parser.exclude_metadata)
+ if(!audio_track.name.empty() && !arg_parser.settings.exclude_metadata)
av_dict_set(&audio_stream->metadata, "title", audio_track.name.c_str(), 0);
avcodec_parameters_from_context(audio_stream->codecpar, audio_track.codec_context);
result.audio_inputs.push_back({&audio_track, audio_stream});
@@ -1032,20 +423,20 @@ static RecordingStartResult start_recording_create_streams(const char *filename,
const int open_ret = avio_open(&av_format_context->pb, filename, AVIO_FLAG_WRITE);
if(open_ret < 0) {
- gsr_log(GSR_LOG_LEVEL_ERROR, "start_recording_create_streams: could not open '%s': %s", filename, av_error_to_string(open_ret));
+ gsr_log(GSR_LOG_LEVEL_ERROR, "start_recording_create_streams: could not open '%s': %s", filename, gsr_av_error_to_string(open_ret));
return result;
}
AVDictionary *options = nullptr;
av_dict_set(&options, "strict", "experimental", 0);
- if(arg_parser.ffmpeg_opts)
- av_dict_parse_string(&options, arg_parser.ffmpeg_opts, "=", ";", 0);
+ if(arg_parser.settings.ffmpeg_opts)
+ av_dict_parse_string(&options, arg_parser.settings.ffmpeg_opts, "=", ";", 0);
const int header_write_ret = avformat_write_header(av_format_context, &options);
av_dict_free(&options);
if(header_write_ret < 0) {
- gsr_log(GSR_LOG_LEVEL_ERROR, "start_recording_create_streams: error occurred when writing header to output file: %s", av_error_to_string(header_write_ret));
+ gsr_log(GSR_LOG_LEVEL_ERROR, "start_recording_create_streams: error occurred when writing header to output file: %s", gsr_av_error_to_string(header_write_ret));
avio_close(av_format_context->pb);
avformat_free_context(av_format_context);
return result;
@@ -1141,7 +532,7 @@ static bool save_replay_async(AVCodecContext *video_codec_context, int video_str
audio_pts_offsets.push_back(AudioPtsOffset{audio_pts_offset, audio_track.stream_index});
}
- std::string output_filepath = create_new_recording_filepath_from_timestamp(arg_parser.filename, "Replay", file_extension, date_folders);
+ std::string output_filepath = create_new_recording_filepath_from_timestamp(arg_parser.settings.filename, "Replay", file_extension, date_folders);
RecordingStartResult recording_start_result = start_recording_create_streams(output_filepath.c_str(), arg_parser, video_codec_context, audio_tracks, hdr, video_sources);
if(!recording_start_result.av_format_context) {
pthread_mutex_lock(&encoder->replay_mutex);
@@ -1218,7 +609,7 @@ static bool save_replay_async(AVCodecContext *video_codec_context, int video_str
const int ret = av_write_frame(recording_start_result.av_format_context, &av_packet);
if(ret < 0)
- gsr_log(GSR_LOG_LEVEL_ERROR, "Failed to write frame index %d to muxer, reason: %s (%d)", av_packet.stream_index, av_error_to_string(ret), ret);
+ gsr_log(GSR_LOG_LEVEL_ERROR, "Failed to write frame index %d to muxer, reason: %s (%d)", av_packet.stream_index, gsr_av_error_to_string(ret), ret);
free(replay_packet_data);
@@ -1421,10 +812,10 @@ fail:
}
static gsr_video_encoder* create_video_encoder(gsr_egl *egl, const args_parser &arg_parser) {
- const gsr_color_depth color_depth = video_codec_to_bit_depth(arg_parser.video_codec);
+ const gsr_color_depth color_depth = video_codec_to_bit_depth(arg_parser.settings.video_codec);
gsr_video_encoder *video_encoder = nullptr;
- if(arg_parser.video_encoder == GSR_VIDEO_ENCODER_HW_CPU) {
+ if(arg_parser.settings.video_encoder == GSR_VIDEO_ENCODER_HW_CPU) {
gsr_video_encoder_software_params params;
params.egl = egl;
params.color_depth = color_depth;
@@ -1432,7 +823,7 @@ static gsr_video_encoder* create_video_encoder(gsr_egl *egl, const args_parser &
return video_encoder;
}
- if(video_codec_is_vulkan(arg_parser.video_codec)) {
+ if(video_codec_is_vulkan(arg_parser.settings.video_codec)) {
gsr_video_encoder_vulkan_params params;
params.egl = egl;
params.color_depth = color_depth;
@@ -2020,8 +1411,8 @@ static gsr_capture* create_monitor_capture(const args_parser &arg_parser, gsr_eg
ximage_params.egl = egl;
ximage_params.cursor = &x11_cursor;
ximage_params.display_to_capture = capture_source.name.c_str();
- ximage_params.record_cursor = arg_parser.record_cursor;
- ximage_params.output_resolution = arg_parser.output_resolution;
+ ximage_params.record_cursor = arg_parser.settings.record_cursor;
+ ximage_params.output_resolution = arg_parser.settings.output_resolution;
ximage_params.region_size = capture_source.region_size;
ximage_params.region_position = capture_source.region_pos;
return gsr_capture_ximage_create(&ximage_params);
@@ -2047,10 +1438,10 @@ static gsr_capture* create_monitor_capture(const args_parser &arg_parser, gsr_eg
kms_params.kms_response = &kms_response;
kms_params.kde_night_light = kde_night_light;
kms_params.display_to_capture = capture_source.name.c_str();
- kms_params.record_cursor = arg_parser.record_cursor;
- kms_params.hdr = video_codec_is_hdr(arg_parser.video_codec);
- kms_params.fps = arg_parser.fps;
- kms_params.output_resolution = arg_parser.output_resolution;
+ kms_params.record_cursor = arg_parser.settings.record_cursor;
+ kms_params.hdr = video_codec_is_hdr(arg_parser.settings.video_codec);
+ kms_params.fps = arg_parser.settings.fps;
+ kms_params.output_resolution = arg_parser.settings.output_resolution;
kms_params.region_size = capture_source.region_size;
kms_params.region_position = capture_source.region_pos;
return gsr_capture_kms_create(&kms_params);
@@ -2066,10 +1457,10 @@ static gsr_capture* create_monitor_capture(const args_parser &arg_parser, gsr_eg
memset(&nvfbc_params, 0, sizeof(nvfbc_params));
nvfbc_params.egl = egl;
nvfbc_params.display_to_capture = capture_source_real;
- nvfbc_params.fps = arg_parser.fps;
+ nvfbc_params.fps = arg_parser.settings.fps;
nvfbc_params.direct_capture = direct_capture;
- nvfbc_params.record_cursor = arg_parser.record_cursor;
- nvfbc_params.output_resolution = arg_parser.output_resolution;
+ nvfbc_params.record_cursor = arg_parser.settings.record_cursor;
+ nvfbc_params.output_resolution = arg_parser.settings.output_resolution;
nvfbc_params.region_size = capture_source.region_size;
nvfbc_params.region_position = capture_source.region_pos;
return gsr_capture_nvfbc_create(&nvfbc_params);
@@ -2126,8 +1517,8 @@ static gsr_capture* create_capture_impl(const args_parser &arg_parser, gsr_egl *
_exit(2);
}
- if(arg_parser.output_resolution.x <= 0 || arg_parser.output_resolution.y <= 0) {
- gsr_log(GSR_LOG_LEVEL_ERROR, "invalid value for option -s '%dx%d' when using -w focused option. expected width and height to be greater than 0", arg_parser.output_resolution.x, arg_parser.output_resolution.y);
+ if(arg_parser.settings.output_resolution.x <= 0 || arg_parser.settings.output_resolution.y <= 0) {
+ gsr_log(GSR_LOG_LEVEL_ERROR, "invalid value for option -s '%dx%d' when using -w focused option. expected width and height to be greater than 0", arg_parser.settings.output_resolution.x, arg_parser.settings.output_resolution.y);
args_parser_print_usage();
_exit(1);
}
@@ -2144,10 +1535,10 @@ static gsr_capture* create_capture_impl(const args_parser &arg_parser, gsr_egl *
gsr_capture_portal_params portal_params;
memset(&portal_params, 0, sizeof(portal_params));
portal_params.egl = egl;
- portal_params.record_cursor = arg_parser.record_cursor;
- portal_params.restore_portal_session = arg_parser.restore_portal_session;
- portal_params.portal_session_token_filepath = arg_parser.portal_session_token_filepath;
- portal_params.output_resolution = arg_parser.output_resolution;
+ portal_params.record_cursor = arg_parser.settings.record_cursor;
+ portal_params.restore_portal_session = arg_parser.settings.restore_portal_session;
+ portal_params.portal_session_token_filepath = arg_parser.settings.portal_session_token_filepath;
+ portal_params.output_resolution = arg_parser.settings.output_resolution;
capture = gsr_capture_portal_create(&portal_params);
if(!capture)
_exit(1);
@@ -2169,7 +1560,7 @@ static gsr_capture* create_capture_impl(const args_parser &arg_parser, gsr_egl *
gsr_capture_v4l2_params v4l2_params;
memset(&v4l2_params, 0, sizeof(v4l2_params));
v4l2_params.egl = egl;
- v4l2_params.output_resolution = arg_parser.output_resolution;
+ v4l2_params.output_resolution = arg_parser.settings.output_resolution;
v4l2_params.device_path = capture_source.name.c_str();
v4l2_params.pixfmt = capture_source.v4l2_pixfmt;
v4l2_params.camera_fps = capture_source.camera_fps;
@@ -2192,8 +1583,8 @@ static gsr_capture* create_capture_impl(const args_parser &arg_parser, gsr_egl *
xcomposite_params.cursor = &x11_cursor;
xcomposite_params.window = capture_source.window_id;
xcomposite_params.follow_focused = follow_focused;
- xcomposite_params.record_cursor = arg_parser.record_cursor;
- xcomposite_params.output_resolution = arg_parser.output_resolution;
+ xcomposite_params.record_cursor = arg_parser.settings.record_cursor;
+ xcomposite_params.output_resolution = arg_parser.settings.output_resolution;
capture = gsr_capture_xcomposite_create(&xcomposite_params);
if(!capture)
_exit(1);
@@ -2241,7 +1632,7 @@ static std::vector<VideoSource> create_video_sources(const args_parser &arg_pars
for(CaptureSource &capture_source : capture_sources) {
gsr_capture_metadata capture_metadata;
memset(&capture_metadata, 0, sizeof(capture_metadata));
- capture_metadata.fps = arg_parser.fps;
+ capture_metadata.fps = arg_parser.settings.fps;
capture_metadata.halign = capture_source.halign;
capture_metadata.valign = capture_source.valign;
capture_metadata.flip = (gsr_flip)capture_source.flip;
@@ -2340,13 +1731,13 @@ static void load_plugins(gsr_plugins *plugins, args_parser &arg_parser, gsr_egl
assert(plugin_arg);
if(plugin_arg->num_values > 0) {
- const gsr_color_depth color_depth = video_codec_to_bit_depth(arg_parser.video_codec);
+ const gsr_color_depth color_depth = video_codec_to_bit_depth(arg_parser.settings.video_codec);
assert(color_depth == GSR_COLOR_DEPTH_8_BITS || color_depth == GSR_COLOR_DEPTH_10_BITS);
const gsr_plugin_init_params plugin_init_params = {
(unsigned int)video_size.x,
(unsigned int)video_size.y,
- (unsigned int)arg_parser.fps,
+ (unsigned int)arg_parser.settings.fps,
color_depth == GSR_COLOR_DEPTH_8_BITS ? GSR_PLUGIN_COLOR_DEPTH_8_BITS : GSR_PLUGIN_COLOR_DEPTH_10_BITS,
egl->context_type == GSR_GL_CONTEXT_TYPE_GLX ? GSR_PLUGIN_GRAPHICS_API_GLX : GSR_PLUGIN_GRAPHICS_API_EGL_ES,
};
@@ -2363,9 +1754,9 @@ static void load_plugins(gsr_plugins *plugins, args_parser &arg_parser, gsr_egl
// TODO: 10-bit and hdr.
static void capture_image_to_file(args_parser &arg_parser, gsr_egl *egl, gsr_window *window, gsr_image_format image_format, std::vector<CaptureSource> &capture_sources) {
- const int image_quality = video_quality_to_image_quality_value(arg_parser.video_quality);
+ const int image_quality = video_quality_to_image_quality_value(arg_parser.settings.video_quality);
const gsr_color_range color_range = image_format_to_color_range(image_format, image_quality);
- arg_parser.fps = 60; // We want to capture an image as soon as possible
+ arg_parser.settings.fps = 60; // We want to capture an image as soon as possible
vec2i video_size = {0, 0};
std::vector<VideoSource> video_sources = create_video_sources(arg_parser, egl, true, capture_sources, video_size);
@@ -2412,7 +1803,7 @@ static void capture_image_to_file(args_parser &arg_parser, gsr_egl *egl, gsr_win
while(running) {
while(gsr_window_process_event(window)) {
- if(x11_cursor_display && arg_parser.record_cursor)
+ if(x11_cursor_display && arg_parser.settings.record_cursor)
gsr_cursor_on_event(&x11_cursor, gsr_window_get_event_data(window));
for(VideoSource &video_source : video_sources) {
@@ -2420,7 +1811,7 @@ static void capture_image_to_file(args_parser &arg_parser, gsr_egl *egl, gsr_win
}
}
- if(x11_cursor_display && arg_parser.record_cursor)
+ if(x11_cursor_display && arg_parser.settings.record_cursor)
gsr_cursor_tick(&x11_cursor, DefaultRootWindow(x11_cursor_display));
gsr_capture_kms_cleanup_kms_fds();
@@ -2477,13 +1868,13 @@ static void capture_image_to_file(args_parser &arg_parser, gsr_egl *egl, gsr_win
gsr_egl_swap_buffers(egl);
if(!should_stop_error) {
- if(!gsr_image_writer_write_to_file(&image_writer, arg_parser.filename, image_format, image_quality)) {
- gsr_log(GSR_LOG_LEVEL_ERROR, "capture_image_to_file: failed to write opengl texture to image output file %s", arg_parser.filename);
+ if(!gsr_image_writer_write_to_file(&image_writer, arg_parser.settings.filename, image_format, image_quality)) {
+ gsr_log(GSR_LOG_LEVEL_ERROR, "capture_image_to_file: failed to write opengl texture to image output file %s", arg_parser.settings.filename);
_exit(1);
}
- if(arg_parser.recording_saved_script)
- run_recording_saved_script_async(arg_parser.recording_saved_script, arg_parser.filename, "screenshot");
+ if(arg_parser.settings.recording_saved_script)
+ run_recording_saved_script_async(arg_parser.settings.recording_saved_script, arg_parser.settings.filename, "screenshot");
}
gsr_plugins_deinit(&plugins);
@@ -2494,17 +1885,6 @@ static void capture_image_to_file(args_parser &arg_parser, gsr_egl *egl, gsr_win
_exit(should_stop_error ? 3 : 0);
}
-static AVPixelFormat get_pixel_format(gsr_video_codec video_codec, gsr_gpu_vendor vendor, bool use_software_video_encoder) {
- if(use_software_video_encoder) {
- return AV_PIX_FMT_NV12;
- } else {
- if(video_codec_is_vulkan(video_codec))
- return AV_PIX_FMT_VULKAN;
- else
- return vendor == GSR_GPU_VENDOR_NVIDIA ? AV_PIX_FMT_CUDA : AV_PIX_FMT_VAAPI;
- }
-}
-
static void match_app_audio_input_to_available_apps(const std::vector<AudioInput> &requested_audio_inputs, const std::vector<std::string> &app_audio_names) {
for(const AudioInput &request_audio_input : requested_audio_inputs) {
if(request_audio_input.type != AudioInputType::APPLICATION || request_audio_input.inverted)
@@ -2872,8 +2252,8 @@ static bool parse_capture_source_arg_callback(const char *sub, size_t size, void
capture_source_size = capture_source_end - sub;
CaptureSource capture_source;
- capture_source.region_pos = parse_userdata->arg_parser->region_position;
- capture_source.region_size = parse_userdata->arg_parser->region_size;
+ capture_source.region_pos = parse_userdata->arg_parser->settings.region_position;
+ capture_source.region_size = parse_userdata->arg_parser->settings.region_size;
if(gsr_string_starts_with(sub, capture_source_size, "monitor:")) {
capture_source.type = GSR_CAPTURE_SOURCE_TYPE_MONITOR;
@@ -3181,24 +2561,24 @@ static void print_codec_error(gsr_video_codec video_codec) {
}
static void force_cpu_encoding(args_parser *args_parser) {
- args_parser->video_codec = GSR_VIDEO_CODEC_H264;
- args_parser->video_encoder = GSR_VIDEO_ENCODER_HW_CPU;
- if(args_parser->bitrate_mode == GSR_BITRATE_MODE_VBR) {
+ args_parser->settings.video_codec = GSR_VIDEO_CODEC_H264;
+ args_parser->settings.video_encoder = GSR_VIDEO_ENCODER_HW_CPU;
+ if(args_parser->settings.bitrate_mode == GSR_BITRATE_MODE_VBR) {
gsr_log(GSR_LOG_LEVEL_WARNING, "bitrate mode has been forcefully set to qp because software encoding option doesn't support vbr option");
- args_parser->bitrate_mode = GSR_BITRATE_MODE_QP;
+ args_parser->settings.bitrate_mode = GSR_BITRATE_MODE_QP;
}
}
static const AVCodec* pick_video_codec(gsr_egl *egl, args_parser *args_parser, bool use_fallback_codec, bool *low_power, gsr_supported_video_codecs *supported_video_codecs) {
// TODO: software encoder for hevc, av1, vp8 and vp9
*low_power = false;
- const AVCodec *video_codec_f = get_av_codec_if_supported(args_parser->video_codec, egl, args_parser->video_encoder == GSR_VIDEO_ENCODER_HW_CPU, supported_video_codecs);
+ const AVCodec *video_codec_f = get_av_codec_if_supported(args_parser->settings.video_codec, egl, args_parser->settings.video_encoder == GSR_VIDEO_ENCODER_HW_CPU, supported_video_codecs);
- if(!video_codec_f && use_fallback_codec && args_parser->video_encoder != GSR_VIDEO_ENCODER_HW_CPU) {
- switch(args_parser->video_codec) {
+ if(!video_codec_f && use_fallback_codec && args_parser->settings.video_encoder != GSR_VIDEO_ENCODER_HW_CPU) {
+ switch(args_parser->settings.video_codec) {
case GSR_VIDEO_CODEC_H264: {
gsr_log(GSR_LOG_LEVEL_ERROR, "selected video codec h264 is not supported by your hardware");
- if(args_parser->fallback_cpu_encoding) {
+ if(args_parser->settings.fallback_cpu_encoding) {
gsr_log(GSR_LOG_LEVEL_WARNING, "gpu encoding is not available on your system, trying cpu encoding instead because -fallback-cpu-encoding is enabled. Install the proper vaapi drivers on your system (if supported) if you experience performance issues");
force_cpu_encoding(args_parser);
}
@@ -3208,14 +2588,14 @@ static const AVCodec* pick_video_codec(gsr_egl *egl, args_parser *args_parser, b
case GSR_VIDEO_CODEC_HEVC_HDR:
case GSR_VIDEO_CODEC_HEVC_10BIT: {
gsr_log(GSR_LOG_LEVEL_WARNING, "selected video codec hevc is not supported by your hardware, trying h264 instead");
- args_parser->video_codec = GSR_VIDEO_CODEC_H264;
+ args_parser->settings.video_codec = GSR_VIDEO_CODEC_H264;
return pick_video_codec(egl, args_parser, true, low_power, supported_video_codecs);
}
case GSR_VIDEO_CODEC_AV1:
case GSR_VIDEO_CODEC_AV1_HDR:
case GSR_VIDEO_CODEC_AV1_10BIT: {
gsr_log(GSR_LOG_LEVEL_WARNING, "selected video codec av1 is not supported by your hardware, trying h264 instead");
- args_parser->video_codec = GSR_VIDEO_CODEC_H264;
+ args_parser->settings.video_codec = GSR_VIDEO_CODEC_H264;
return pick_video_codec(egl, args_parser, true, low_power, supported_video_codecs);
}
case GSR_VIDEO_CODEC_VP8:
@@ -3224,11 +2604,11 @@ static const AVCodec* pick_video_codec(gsr_egl *egl, args_parser *args_parser, b
break;
case GSR_VIDEO_CODEC_H264_VULKAN: {
gsr_log(GSR_LOG_LEVEL_WARNING, "selected video codec h264_vulkan is not supported by your hardware, trying h264 instead");
- args_parser->video_codec = GSR_VIDEO_CODEC_H264;
+ args_parser->settings.video_codec = GSR_VIDEO_CODEC_H264;
// Need to do a query again because this time it's without vulkan
- if(!get_supported_video_codecs(egl, args_parser->video_codec, false, true, supported_video_codecs)) {
+ if(!get_supported_video_codecs(egl, args_parser->settings.video_codec, false, true, supported_video_codecs)) {
gsr_log(GSR_LOG_LEVEL_ERROR, "failed to query for supported video codecs");
- print_codec_error(args_parser->video_codec);
+ print_codec_error(args_parser->settings.video_codec);
_exit(11);
}
return pick_video_codec(egl, args_parser, true, low_power, supported_video_codecs);
@@ -3237,11 +2617,11 @@ static const AVCodec* pick_video_codec(gsr_egl *egl, args_parser *args_parser, b
case GSR_VIDEO_CODEC_HEVC_HDR_VULKAN:
case GSR_VIDEO_CODEC_HEVC_10BIT_VULKAN: {
gsr_log(GSR_LOG_LEVEL_WARNING, "selected video codec hevc_vulkan is not supported by your hardware, trying hevc instead");
- args_parser->video_codec = GSR_VIDEO_CODEC_HEVC;
+ args_parser->settings.video_codec = GSR_VIDEO_CODEC_HEVC;
// Need to do a query again because this time it's without vulkan
- if(!get_supported_video_codecs(egl, args_parser->video_codec, false, true, supported_video_codecs)) {
+ if(!get_supported_video_codecs(egl, args_parser->settings.video_codec, false, true, supported_video_codecs)) {
gsr_log(GSR_LOG_LEVEL_ERROR, "failed to query for supported video codecs");
- print_codec_error(args_parser->video_codec);
+ print_codec_error(args_parser->settings.video_codec);
_exit(11);
}
return pick_video_codec(egl, args_parser, true, low_power, supported_video_codecs);
@@ -3250,26 +2630,26 @@ static const AVCodec* pick_video_codec(gsr_egl *egl, args_parser *args_parser, b
case GSR_VIDEO_CODEC_AV1_HDR_VULKAN:
case GSR_VIDEO_CODEC_AV1_10BIT_VULKAN: {
gsr_log(GSR_LOG_LEVEL_WARNING, "selected video codec av1_vulkan is not supported by your hardware, trying av1 instead");
- args_parser->video_codec = GSR_VIDEO_CODEC_AV1;
+ args_parser->settings.video_codec = GSR_VIDEO_CODEC_AV1;
// Need to do a query again because this time it's without vulkan
- if(!get_supported_video_codecs(egl, args_parser->video_codec, false, true, supported_video_codecs)) {
+ if(!get_supported_video_codecs(egl, args_parser->settings.video_codec, false, true, supported_video_codecs)) {
gsr_log(GSR_LOG_LEVEL_ERROR, "failed to query for supported video codecs");
- print_codec_error(args_parser->video_codec);
+ print_codec_error(args_parser->settings.video_codec);
_exit(11);
}
return pick_video_codec(egl, args_parser, true, low_power, supported_video_codecs);
}
}
- video_codec_f = get_av_codec_if_supported(args_parser->video_codec, egl, args_parser->video_encoder == GSR_VIDEO_ENCODER_HW_CPU, supported_video_codecs);
+ video_codec_f = get_av_codec_if_supported(args_parser->settings.video_codec, egl, args_parser->settings.video_encoder == GSR_VIDEO_ENCODER_HW_CPU, supported_video_codecs);
}
if(!video_codec_f) {
- print_codec_error(args_parser->video_codec);
+ print_codec_error(args_parser->settings.video_codec);
_exit(54);
}
- *low_power = video_codec_only_supports_low_power_mode(*supported_video_codecs, args_parser->video_codec);
+ *low_power = video_codec_only_supports_low_power_mode(*supported_video_codecs, args_parser->settings.video_codec);
return video_codec_f;
}
@@ -3294,27 +2674,27 @@ static gsr_video_codec select_appropriate_video_codec_automatically(vec2i video_
static const AVCodec* select_video_codec_with_fallback(vec2i video_size, args_parser *args_parser, const char *file_extension, gsr_egl *egl, bool *low_power) {
gsr_supported_video_codecs supported_video_codecs_non_vulkan;
- get_supported_video_codecs(egl, args_parser->video_codec, args_parser->video_encoder == GSR_VIDEO_ENCODER_HW_CPU, true, &supported_video_codecs_non_vulkan);
+ get_supported_video_codecs(egl, args_parser->settings.video_codec, args_parser->settings.video_encoder == GSR_VIDEO_ENCODER_HW_CPU, true, &supported_video_codecs_non_vulkan);
gsr_supported_video_codecs supported_video_codecs_vulkan = supported_video_codecs_non_vulkan;
set_supported_video_codecs_ffmpeg(&supported_video_codecs_non_vulkan, &supported_video_codecs_vulkan, egl->gpu_info.vendor);
- gsr_supported_video_codecs *supported_video_codecs = video_codec_is_vulkan(args_parser->video_codec)
+ gsr_supported_video_codecs *supported_video_codecs = video_codec_is_vulkan(args_parser->settings.video_codec)
? &supported_video_codecs_vulkan
: &supported_video_codecs_non_vulkan;
- const bool video_codec_auto = args_parser->video_codec == (gsr_video_codec)GSR_VIDEO_CODEC_AUTO;
+ const bool video_codec_auto = args_parser->settings.video_codec == (gsr_video_codec)GSR_VIDEO_CODEC_AUTO;
if(video_codec_auto) {
if(strcmp(file_extension, "webm") == 0) {
gsr_log(GSR_LOG_LEVEL_INFO, "using vp8 encoder because a codec was not specified and the file extension is .webm");
- args_parser->video_codec = GSR_VIDEO_CODEC_VP8;
- } else if(args_parser->video_encoder == GSR_VIDEO_ENCODER_HW_CPU) {
+ args_parser->settings.video_codec = GSR_VIDEO_CODEC_VP8;
+ } else if(args_parser->settings.video_encoder == GSR_VIDEO_ENCODER_HW_CPU) {
gsr_log(GSR_LOG_LEVEL_INFO, "using h264 encoder because a codec was not specified");
- args_parser->video_codec = GSR_VIDEO_CODEC_H264;
- } else if(args_parser->video_encoder != GSR_VIDEO_ENCODER_HW_CPU) {
- args_parser->video_codec = select_appropriate_video_codec_automatically(video_size, &supported_video_codecs_non_vulkan);
- if(args_parser->video_codec == (gsr_video_codec)-1) {
- if(args_parser->fallback_cpu_encoding) {
+ args_parser->settings.video_codec = GSR_VIDEO_CODEC_H264;
+ } else if(args_parser->settings.video_encoder != GSR_VIDEO_ENCODER_HW_CPU) {
+ args_parser->settings.video_codec = select_appropriate_video_codec_automatically(video_size, &supported_video_codecs_non_vulkan);
+ if(args_parser->settings.video_codec == (gsr_video_codec)-1) {
+ if(args_parser->settings.fallback_cpu_encoding) {
gsr_log(GSR_LOG_LEVEL_WARNING, "gpu encoding is not available on your system or your gpu doesn't support recording at the resolution you are trying to record, trying cpu encoding instead because -fallback-cpu-encoding is enabled. Install the proper vaapi drivers on your system (if supported) if you experience performance issues");
force_cpu_encoding(args_parser);
} else {
@@ -3327,22 +2707,22 @@ static const AVCodec* select_video_codec_with_fallback(vec2i video_size, args_pa
}
if(LIBAVFORMAT_VERSION_INT < AV_VERSION_INT(60, 10, 100) && strcmp(file_extension, "flv") == 0) {
- if(args_parser->video_codec != GSR_VIDEO_CODEC_H264) {
- args_parser->video_codec = GSR_VIDEO_CODEC_H264;
+ if(args_parser->settings.video_codec != GSR_VIDEO_CODEC_H264) {
+ args_parser->settings.video_codec = GSR_VIDEO_CODEC_H264;
gsr_log(GSR_LOG_LEVEL_WARNING, "hevc/av1 is not compatible with flv in your outdated version of ffmpeg, falling back to h264 instead.");
}
} else if(strcmp(file_extension, "m3u8") == 0) {
- if(video_codec_is_av1(args_parser->video_codec)) {
- args_parser->video_codec = GSR_VIDEO_CODEC_HEVC;
+ if(video_codec_is_av1(args_parser->settings.video_codec)) {
+ args_parser->settings.video_codec = GSR_VIDEO_CODEC_HEVC;
gsr_log(GSR_LOG_LEVEL_WARNING, "av1 is not compatible with hls (m3u8), falling back to hevc instead.");
}
}
const AVCodec *codec = pick_video_codec(egl, args_parser, true, low_power, supported_video_codecs);
- const vec2i codec_max_resolution = codec_get_max_resolution(args_parser->video_codec, args_parser->video_encoder == GSR_VIDEO_ENCODER_HW_CPU, supported_video_codecs);
+ const vec2i codec_max_resolution = codec_get_max_resolution(args_parser->settings.video_codec, args_parser->settings.video_encoder == GSR_VIDEO_ENCODER_HW_CPU, supported_video_codecs);
if(!codec_supports_resolution(codec_max_resolution, video_size)) {
- const char *video_codec_name = video_codec_to_string(args_parser->video_codec);
+ const char *video_codec_name = video_codec_to_string(args_parser->settings.video_codec);
gsr_log(GSR_LOG_LEVEL_ERROR, "The max resolution for video codec %s is %dx%d while you are trying to capture at resolution %dx%d. Change capture resolution or video codec and try again", video_codec_name, codec_max_resolution.x, codec_max_resolution.y, video_size.x, video_size.y);
_exit(53);
}
@@ -3374,6 +2754,8 @@ static std::vector<AudioDeviceData> create_device_audio_inputs(const std::vector
}
audio_device.frame = create_audio_frame(audio_codec_context);
+ if(!audio_device.frame)
+ _exit(1);
audio_device.frame->pts = -audio_codec_context->frame_size * num_audio_frames_shift;
audio_track_audio_devices.push_back(std::move(audio_device));
@@ -3385,6 +2767,8 @@ static std::vector<AudioDeviceData> create_device_audio_inputs(const std::vector
static AudioDeviceData create_application_audio_audio_input(const MergedAudioInputs &merged_audio_inputs, AVCodecContext *audio_codec_context, int num_channels, double num_audio_frames_shift, gsr_pipewire_audio *pipewire_audio) {
AudioDeviceData audio_device;
audio_device.frame = create_audio_frame(audio_codec_context);
+ if(!audio_device.frame)
+ _exit(1);
audio_device.frame->pts = -audio_codec_context->frame_size * num_audio_frames_shift;
char random_str[8];
@@ -3461,30 +2845,18 @@ static void av_write_header(AVFormatContext *av_format_context, const char *ffmp
const int ret = avformat_write_header(av_format_context, &options);
if(ret < 0)
- fprintf(stderr, "Error occurred when writing header to output file: %s\n", av_error_to_string(ret));
+ fprintf(stderr, "Error occurred when writing header to output file: %s\n", gsr_av_error_to_string(ret));
av_dict_free(&options);
}
-static int audio_codec_get_frame_size(gsr_audio_codec audio_codec) {
- switch(audio_codec) {
- case GSR_AUDIO_CODEC_AAC: return 1024;
- case GSR_AUDIO_CODEC_OPUS: return 960;
- case GSR_AUDIO_CODEC_FLAC:
- assert(false);
- return 1024;
- }
- assert(false);
- return 1024;
-}
-
static size_t calculate_estimated_replay_buffer_packets(int64_t replay_buffer_size_secs, int fps, gsr_audio_codec audio_codec, const std::vector<MergedAudioInputs> &audio_inputs) {
if(replay_buffer_size_secs == -1)
return 0;
int audio_fps = 0;
if(!audio_inputs.empty())
- audio_fps = AUDIO_SAMPLE_RATE / audio_codec_get_frame_size(audio_codec);
+ audio_fps = GSR_AUDIO_SAMPLE_RATE / audio_codec_get_frame_size(audio_codec);
return replay_buffer_size_secs * (fps + audio_fps * audio_inputs.size());
}
@@ -3566,7 +2938,7 @@ static void validate_args_with_capture_sources(args_parser &arg_parser, const st
}
}
- if(!arg_parser.restore_portal_session && is_capturing_type(capture_sources, GSR_CAPTURE_SOURCE_TYPE_PORTAL))
+ if(!arg_parser.settings.restore_portal_session && is_capturing_type(capture_sources, GSR_CAPTURE_SOURCE_TYPE_PORTAL))
gsr_log(GSR_LOG_LEVEL_INFO, "option '-w portal' was used without '-restore-portal-session yes'. The previous screencast session will be ignored");
}
@@ -3670,7 +3042,7 @@ int main(int argc, char **argv) {
if(!args_parser_parse(&arg_parser, argc, argv, &arg_handlers, NULL))
_exit(1);
- if(!arg_parser.low_power) {
+ if(!arg_parser.settings.low_power) {
// Forces low latency encoding mode. Use this environment variable until vaapi supports setting this as a parameter.
// The downside of this is that it always uses maximum power, which is not ideal for replay mode that runs on system startup.
// This option was added in mesa 24.1.4, released in july 17, 2024.
@@ -3679,7 +3051,7 @@ int main(int argc, char **argv) {
setenv("AMD_DEBUG", "lowlatencyenc", true);
}
- std::vector<CaptureSource> capture_sources = parse_capture_source_arg(arg_parser.capture_source, arg_parser);
+ std::vector<CaptureSource> capture_sources = parse_capture_source_arg(arg_parser.settings.capture_source, arg_parser);
if(capture_sources.empty()) {
gsr_log(GSR_LOG_LEVEL_ERROR, "option -w can't be empty. You need to capture video from at least one source");
args_parser_print_usage();
@@ -3766,19 +3138,19 @@ int main(int argc, char **argv) {
disable_prime_run();
}
- if(video_codec_is_hdr(arg_parser.video_codec)) {
+ if(video_codec_is_hdr(arg_parser.settings.video_codec)) {
gsr_log(GSR_LOG_LEVEL_WARNING, "portal capture option doesn't support hdr yet (PipeWire doesn't support hdr), the video will be tonemapped from hdr to sdr");
- arg_parser.video_codec = hdr_video_codec_to_sdr_video_codec(arg_parser.video_codec);
+ arg_parser.settings.video_codec = hdr_video_codec_to_sdr_video_codec(arg_parser.settings.video_codec);
}
}
gsr_egl egl;
- if(!gsr_egl_load(&egl, window, is_capturing_monitor_or_region(capture_sources), arg_parser.gl_debug)) {
+ if(!gsr_egl_load(&egl, window, is_capturing_monitor_or_region(capture_sources), arg_parser.settings.gl_debug)) {
gsr_log(GSR_LOG_LEVEL_ERROR, "failed to load opengl");
_exit(1);
}
- gsr_shader_enable_debug_output(arg_parser.gl_debug);
+ gsr_shader_enable_debug_output(arg_parser.settings.gl_debug);
#ifndef NDEBUG
gsr_shader_enable_debug_output(true);
#endif
@@ -3799,7 +3171,7 @@ int main(int argc, char **argv) {
memset(&x11_cursor, 0, sizeof(x11_cursor));
x11_cursor_display = NULL;
- if(gsr_window_get_display_server(window) == GSR_DISPLAY_SERVER_X11 && arg_parser.record_cursor) {
+ if(gsr_window_get_display_server(window) == GSR_DISPLAY_SERVER_X11 && arg_parser.settings.record_cursor) {
x11_cursor_display = (Display*)gsr_window_get_display(egl.window);
gsr_cursor_init(&x11_cursor, &egl, x11_cursor_display);
}
@@ -3810,7 +3182,7 @@ int main(int argc, char **argv) {
// }
gsr_image_format image_format;
- if(get_image_format_from_filename(arg_parser.filename, &image_format)) {
+ if(get_image_format_from_filename(arg_parser.settings.filename, &image_format)) {
if(audio_input_arg->num_values > 0) {
gsr_log(GSR_LOG_LEVEL_ERROR, "can't record audio (-a) when taking a screenshot");
_exit(1);
@@ -3822,10 +3194,10 @@ int main(int argc, char **argv) {
AVFormatContext *av_format_context;
// The output format is automatically guessed by the file extension
- avformat_alloc_output_context2(&av_format_context, nullptr, arg_parser.container_format, arg_parser.filename);
+ avformat_alloc_output_context2(&av_format_context, nullptr, arg_parser.settings.container_format, arg_parser.settings.filename);
if (!av_format_context) {
- if(arg_parser.container_format) {
- gsr_log(GSR_LOG_LEVEL_ERROR, "Container format '%s' (argument -c) is not valid", arg_parser.container_format);
+ if(arg_parser.settings.container_format) {
+ gsr_log(GSR_LOG_LEVEL_ERROR, "Container format '%s' (argument -c) is not valid", arg_parser.settings.container_format);
} else {
gsr_log(GSR_LOG_LEVEL_ERROR, "Failed to deduce container format from file extension. Use the '-c' option to specify container format");
args_parser_print_usage();
@@ -3846,20 +3218,20 @@ int main(int argc, char **argv) {
}
if(file_extension.empty())
- file_extension = arg_parser.container_format ? arg_parser.container_format : "";
+ file_extension = arg_parser.settings.container_format ? arg_parser.settings.container_format : "";
- const bool force_no_audio_offset = arg_parser.is_livestream || arg_parser.is_output_piped || (file_extension != "mp4" && file_extension != "mkv" && file_extension != "webm");
- const double target_fps = 1.0 / (double)arg_parser.fps;
+ const bool force_no_audio_offset = arg_parser.settings.is_livestream || arg_parser.settings.is_output_piped || (file_extension != "mp4" && file_extension != "mkv" && file_extension != "webm");
+ const double target_fps = 1.0 / (double)arg_parser.settings.fps;
const bool uses_amix = merged_audio_inputs_should_use_amix(requested_audio_inputs);
- arg_parser.audio_codec = select_audio_codec_with_fallback(arg_parser.audio_codec, file_extension, uses_amix);
+ arg_parser.settings.audio_codec = select_audio_codec_with_fallback(arg_parser.settings.audio_codec, file_extension, uses_amix);
vec2i video_size = {0, 0};
std::vector<VideoSource> video_sources = create_video_sources(arg_parser, &egl, false, capture_sources, video_size);
// (Some?) livestreaming services require at least one audio track to work.
// If not audio is provided then create one silent audio track.
- if(arg_parser.is_livestream && requested_audio_inputs.empty()) {
+ if(arg_parser.settings.is_livestream && requested_audio_inputs.empty()) {
gsr_log(GSR_LOG_LEVEL_INFO, "live streaming but no audio track was added. Adding a silent audio track");
MergedAudioInputs mai;
mai.audio_inputs.push_back({""});
@@ -3869,7 +3241,7 @@ int main(int argc, char **argv) {
AVStream *video_stream = nullptr;
std::vector<AudioTrack> audio_tracks;
- if(arg_parser.video_encoder == GSR_VIDEO_ENCODER_HW_CPU && arg_parser.video_codec != (gsr_video_codec)GSR_VIDEO_CODEC_AUTO && arg_parser.video_codec != GSR_VIDEO_CODEC_H264) {
+ if(arg_parser.settings.video_encoder == GSR_VIDEO_ENCODER_HW_CPU && arg_parser.settings.video_codec != (gsr_video_codec)GSR_VIDEO_CODEC_AUTO && arg_parser.settings.video_codec != GSR_VIDEO_CODEC_H264) {
gsr_log(GSR_LOG_LEVEL_ERROR, "-encoder cpu was specified but a codec other than h264 was specified. -encoder cpu supports only h264 at the moment");
_exit(1);
}
@@ -3877,9 +3249,9 @@ int main(int argc, char **argv) {
bool low_power = false;
const AVCodec *video_codec_f = select_video_codec_with_fallback(video_size, &arg_parser, file_extension.c_str(), &egl, &low_power);
- const enum AVPixelFormat video_pix_fmt = get_pixel_format(arg_parser.video_codec, egl.gpu_info.vendor, arg_parser.video_encoder == GSR_VIDEO_ENCODER_HW_CPU);
- AVCodecContext *video_codec_context = create_video_codec_context(video_pix_fmt, video_codec_f, egl, arg_parser, video_size.x, video_size.y);
- if(!arg_parser.is_replaying)
+ const enum AVPixelFormat video_pix_fmt = get_pixel_format(arg_parser.settings.video_codec, egl.gpu_info.vendor, arg_parser.settings.video_encoder == GSR_VIDEO_ENCODER_HW_CPU);
+ AVCodecContext *video_codec_context = create_video_codec_context(video_pix_fmt, video_codec_f, &egl, &arg_parser.settings, video_size.x, video_size.y);
+ if(!arg_parser.settings.is_replaying)
video_stream = create_stream(av_format_context, video_codec_context);
AVFrame *video_frame = av_frame_alloc();
@@ -3896,9 +3268,9 @@ int main(int argc, char **argv) {
video_frame->colorspace = video_codec_context->colorspace;
video_frame->chroma_location = video_codec_context->chroma_sample_location;
- const size_t estimated_replay_buffer_packets = calculate_estimated_replay_buffer_packets(arg_parser.replay_buffer_size_secs, arg_parser.fps, arg_parser.audio_codec, requested_audio_inputs);
+ const size_t estimated_replay_buffer_packets = calculate_estimated_replay_buffer_packets(arg_parser.settings.replay_buffer_size_secs, arg_parser.settings.fps, arg_parser.settings.audio_codec, requested_audio_inputs);
gsr_encoder encoder;
- if(!gsr_encoder_init(&encoder, arg_parser.replay_storage, estimated_replay_buffer_packets, arg_parser.replay_buffer_size_secs, arg_parser.filename)) {
+ if(!gsr_encoder_init(&encoder, arg_parser.settings.replay_storage, estimated_replay_buffer_packets, arg_parser.settings.replay_buffer_size_secs, arg_parser.settings.filename)) {
gsr_log(GSR_LOG_LEVEL_ERROR, "failed to create encoder");
_exit(1);
}
@@ -3929,7 +3301,7 @@ int main(int argc, char **argv) {
gsr_color_conversion_params color_conversion_params;
memset(&color_conversion_params, 0, sizeof(color_conversion_params));
- color_conversion_params.color_range = arg_parser.color_range;
+ color_conversion_params.color_range = arg_parser.settings.color_range;
color_conversion_params.egl = &egl;
color_conversion_params.load_external_image_shader = any_video_sources_uses_external_image(video_sources);
gsr_video_encoder_get_textures(video_encoder, color_conversion_params.destination_textures, color_conversion_params.destination_textures_size, &color_conversion_params.num_destination_textures, &color_conversion_params.destination_color);
@@ -3944,17 +3316,19 @@ int main(int argc, char **argv) {
gsr_color_conversion *output_color_conversion = plugins.num_plugins > 0 ? &plugins.color_conversion : &color_conversion;
- if(arg_parser.video_encoder == GSR_VIDEO_ENCODER_HW_CPU) {
- open_video_software(video_codec_context, arg_parser);
+ if(arg_parser.settings.video_encoder == GSR_VIDEO_ENCODER_HW_CPU) {
+ if(!open_video_software(video_codec_context, &arg_parser.settings))
+ _exit(1);
} else {
- open_video_hardware(video_codec_context, low_power, egl, arg_parser);
+ if(!open_video_hardware(video_codec_context, low_power, &egl, &arg_parser.settings))
+ _exit(1);
}
if(video_stream) {
avcodec_parameters_from_context(video_stream->codecpar, video_codec_context);
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";
+ if(arg_parser.settings.write_first_frame_ts && video_destination_id != (size_t)-1) {
+ std::string ts_filepath = std::string(arg_parser.settings.filename) + ".ts";
gsr_encoder_set_recording_destination_first_frame_ts_filepath(&encoder, video_destination_id, ts_filepath.c_str());
}
}
@@ -3963,19 +3337,22 @@ int main(int argc, char **argv) {
int audio_stream_index = VIDEO_STREAM_INDEX + 1;
for(const MergedAudioInputs &merged_audio_inputs : requested_audio_inputs) {
const bool use_amix = audio_inputs_should_use_amix(merged_audio_inputs.audio_inputs);
- AVCodecContext *audio_codec_context = create_audio_codec_context(arg_parser.fps, arg_parser.audio_codec, use_amix, arg_parser.audio_bitrate);
+ AVCodecContext *audio_codec_context = create_audio_codec_context(arg_parser.settings.fps, arg_parser.settings.audio_codec, use_amix, arg_parser.settings.audio_bitrate);
+ if(!audio_codec_context)
+ _exit(1);
AVStream *audio_stream = nullptr;
- if(!arg_parser.is_replaying) {
+ if(!arg_parser.settings.is_replaying) {
audio_stream = create_stream(av_format_context, audio_codec_context);
if(gsr_encoder_add_recording_destination(&encoder, audio_codec_context, av_format_context, audio_stream, 0) == (size_t)-1)
gsr_log(GSR_LOG_LEVEL_ERROR, "added too many audio sources");
}
- if(audio_stream && !merged_audio_inputs.track_name.empty() && !arg_parser.exclude_metadata)
+ if(audio_stream && !merged_audio_inputs.track_name.empty() && !arg_parser.settings.exclude_metadata)
av_dict_set(&audio_stream->metadata, "title", merged_audio_inputs.track_name.c_str(), 0);
- open_audio(audio_codec_context, arg_parser.ffmpeg_audio_opts);
+ if(!open_audio(audio_codec_context, arg_parser.settings.ffmpeg_audio_opts))
+ _exit(1);
if(audio_stream)
avcodec_parameters_from_context(audio_stream->codecpar, audio_codec_context);
@@ -4003,7 +3380,7 @@ int main(int argc, char **argv) {
const double audio_fps = (double)audio_codec_context->sample_rate / (double)audio_codec_context->frame_size;
const double timeout_sec = 1000.0 / audio_fps / 1000.0;
- const double audio_startup_time_seconds = force_no_audio_offset ? 0 : audio_codec_get_desired_delay(arg_parser.audio_codec, arg_parser.fps);// * ((double)audio_codec_context->frame_size / 1024.0);
+ const double audio_startup_time_seconds = force_no_audio_offset ? 0 : audio_codec_get_desired_delay(arg_parser.settings.audio_codec, arg_parser.settings.fps);// * ((double)audio_codec_context->frame_size / 1024.0);
const double num_audio_frames_shift = audio_startup_time_seconds / timeout_sec;
std::vector<AudioDeviceData> audio_track_audio_devices;
@@ -4032,16 +3409,16 @@ int main(int argc, char **argv) {
//av_dump_format(av_format_context, 0, filename, 1);
- if(!arg_parser.is_replaying && !(output_format->flags & AVFMT_NOFILE)) {
- const int ret = avio_open(&av_format_context->pb, arg_parser.filename, AVIO_FLAG_WRITE);
+ if(!arg_parser.settings.is_replaying && !(output_format->flags & AVFMT_NOFILE)) {
+ const int ret = avio_open(&av_format_context->pb, arg_parser.settings.filename, AVIO_FLAG_WRITE);
if(ret < 0) {
- gsr_log(GSR_LOG_LEVEL_ERROR, "Could not open '%s': %s", arg_parser.filename, av_error_to_string(ret));
+ gsr_log(GSR_LOG_LEVEL_ERROR, "Could not open '%s': %s", arg_parser.settings.filename, gsr_av_error_to_string(ret));
_exit(1);
}
}
- if(!arg_parser.is_replaying)
- av_write_header(av_format_context, arg_parser.ffmpeg_opts);
+ if(!arg_parser.settings.is_replaying)
+ av_write_header(av_format_context, arg_parser.settings.ffmpeg_opts);
double fps_start_time = clock_get_monotonic_seconds();
//double frame_timer_start = fps_start_time;
@@ -4282,14 +3659,14 @@ int main(int argc, char **argv) {
int64_t video_prev_pts = 0;
bool hdr_metadata_set = false;
- const bool hdr = video_codec_is_hdr(arg_parser.video_codec);
+ const bool hdr = video_codec_is_hdr(arg_parser.settings.video_codec);
bool use_damage_tracking = false;
gsr_damage damage;
memset(&damage, 0, sizeof(damage));
- if(arg_parser.framerate_mode == GSR_FRAMERATE_MODE_CONTENT && is_capturing_damage_tracked_target(capture_sources)) {
+ if(arg_parser.settings.framerate_mode == GSR_FRAMERATE_MODE_CONTENT && is_capturing_damage_tracked_target(capture_sources)) {
if(gsr_window_get_display_server(window) == GSR_DISPLAY_SERVER_X11) {
- gsr_damage_init(&damage, &egl, &x11_cursor, arg_parser.record_cursor);
+ gsr_damage_init(&damage, &egl, &x11_cursor, arg_parser.settings.record_cursor);
use_damage_tracking = true;
for(const CaptureSource &capture_source : capture_sources) {
@@ -4313,7 +3690,7 @@ int main(int argc, char **argv) {
while(running) {
while(gsr_window_process_event(window)) {
- if(x11_cursor_display && arg_parser.record_cursor)
+ if(x11_cursor_display && arg_parser.settings.record_cursor)
gsr_cursor_on_event(&x11_cursor, gsr_window_get_event_data(window));
gsr_damage_on_event(&damage, gsr_window_get_event_data(window));
@@ -4322,7 +3699,7 @@ int main(int argc, char **argv) {
}
}
- if(x11_cursor_display && arg_parser.record_cursor)
+ if(x11_cursor_display && arg_parser.settings.record_cursor)
gsr_cursor_tick(&x11_cursor, DefaultRootWindow(x11_cursor_display));
gsr_damage_tick(&damage);
@@ -4363,7 +3740,7 @@ int main(int argc, char **argv) {
damaged |= gsr_plugins_is_damaged(&plugins);
// TODO: Readd wayland sync warning when removing this
- if(arg_parser.framerate_mode != GSR_FRAMERATE_MODE_CONTENT)
+ if(arg_parser.settings.framerate_mode != GSR_FRAMERATE_MODE_CONTENT)
damaged = true;
if(damaged)
@@ -4374,7 +3751,7 @@ int main(int argc, char **argv) {
//const double frame_timer_elapsed = time_now - frame_timer_start;
const double elapsed = time_now - fps_start_time;
if (elapsed >= 1.0) {
- if(arg_parser.verbose) {
+ if(arg_parser.settings.verbose) {
fprintf(stderr, "update fps: %d, damage fps: %d\n", fps_counter, damage_fps_counter);
}
fps_start_time = time_now;
@@ -4446,14 +3823,14 @@ int main(int argc, char **argv) {
gsr_video_encoder_copy_textures_to_frame(video_encoder, video_frame, output_color_conversion);
for(VideoSource &video_source : video_sources) {
- if(hdr && !hdr_metadata_set && !arg_parser.is_replaying && add_hdr_metadata_to_video_stream(video_source.capture, video_stream))
+ if(hdr && !hdr_metadata_set && !arg_parser.settings.is_replaying && add_hdr_metadata_to_video_stream(video_source.capture, video_stream))
hdr_metadata_set = true;
}
// TODO: Check if duplicate frame can be saved just by writing it with a different pts instead of sending it again
- const int num_frames_to_encode = arg_parser.framerate_mode == GSR_FRAMERATE_MODE_CONSTANT ? num_missed_frames : 1;
+ const int num_frames_to_encode = arg_parser.settings.framerate_mode == GSR_FRAMERATE_MODE_CONSTANT ? num_missed_frames : 1;
for(int i = 0; i < num_frames_to_encode; ++i) {
- if(arg_parser.framerate_mode == GSR_FRAMERATE_MODE_CONSTANT) {
+ if(arg_parser.settings.framerate_mode == GSR_FRAMERATE_MODE_CONSTANT) {
video_frame->pts = video_pts_counter + i;
} else {
video_frame->pts = (this_video_frame_time - record_start_time) * (double)AV_TIME_BASE;
@@ -4472,7 +3849,7 @@ int main(int argc, char **argv) {
// TODO: Move to separate thread because this could write to network (for example when livestreaming)
gsr_encoder_receive_packets(&encoder, video_codec_context, video_frame->pts, VIDEO_STREAM_INDEX);
} else {
- gsr_log(GSR_LOG_LEVEL_ERROR, "avcodec_send_frame failed, error: %s", av_error_to_string(ret));
+ gsr_log(GSR_LOG_LEVEL_ERROR, "avcodec_send_frame failed, error: %s", gsr_av_error_to_string(ret));
}
if(force_iframe_frame) {
@@ -4484,7 +3861,7 @@ int main(int argc, char **argv) {
video_pts_counter += num_missed_frames;
}
- if(toggle_pause == 1 && !arg_parser.is_replaying) {
+ if(toggle_pause == 1 && !arg_parser.settings.is_replaying) {
const bool new_paused_state = !paused;
if(new_paused_state) {
paused_time_start = clock_get_monotonic_seconds();
@@ -4498,23 +3875,23 @@ int main(int argc, char **argv) {
paused = !paused;
}
- if(toggle_replay_recording && !arg_parser.replay_recording_directory) {
+ if(toggle_replay_recording && !arg_parser.settings.replay_recording_directory) {
toggle_replay_recording = 0;
printf("gsr error: Unable to start recording since the -ro option was not specified\n");
fflush(stdout);
}
- if(toggle_replay_recording && arg_parser.replay_recording_directory) {
+ if(toggle_replay_recording && arg_parser.settings.replay_recording_directory) {
toggle_replay_recording = 0;
const bool new_replay_recording_state = !replay_recording;
if(new_replay_recording_state) {
std::lock_guard<std::mutex> lock(audio_filter_mutex);
replay_recording_items.clear();
- replay_recording_filepath = create_new_recording_filepath_from_timestamp(arg_parser.replay_recording_directory, "Video", file_extension, arg_parser.date_folders);
+ replay_recording_filepath = create_new_recording_filepath_from_timestamp(arg_parser.settings.replay_recording_directory, "Video", file_extension, arg_parser.settings.date_folders);
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) {
+ if(arg_parser.settings.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());
}
@@ -4545,8 +3922,8 @@ int main(int argc, char **argv) {
fprintf(stderr, "Stopped recording\n");
puts(replay_recording_filepath.c_str());
fflush(stdout);
- if(arg_parser.recording_saved_script)
- run_recording_saved_script_async(arg_parser.recording_saved_script, replay_recording_filepath.c_str(), "regular");
+ if(arg_parser.settings.recording_saved_script)
+ run_recording_saved_script_async(arg_parser.settings.recording_saved_script, replay_recording_filepath.c_str(), "regular");
} else {
printf("gsr error: Failed to save recording\n");
fflush(stdout);
@@ -4566,25 +3943,25 @@ int main(int argc, char **argv) {
} else {
puts(save_replay_output_filepath.c_str());
fflush(stdout);
- if(arg_parser.recording_saved_script)
- run_recording_saved_script_async(arg_parser.recording_saved_script, save_replay_output_filepath.c_str(), "replay");
+ if(arg_parser.settings.recording_saved_script)
+ run_recording_saved_script_async(arg_parser.settings.recording_saved_script, save_replay_output_filepath.c_str(), "replay");
}
}
- if(save_replay_seconds != 0 && !save_replay_thread.valid() && arg_parser.is_replaying) {
+ if(save_replay_seconds != 0 && !save_replay_thread.valid() && arg_parser.settings.is_replaying) {
int current_save_replay_seconds = save_replay_seconds;
if(current_save_replay_seconds > 0)
- current_save_replay_seconds += arg_parser.keyint;
+ current_save_replay_seconds += arg_parser.settings.keyint;
save_replay_seconds = 0;
save_replay_output_filepath.clear();
- const bool replay_start_result = save_replay_async(video_codec_context, VIDEO_STREAM_INDEX, audio_tracks, &encoder, arg_parser, file_extension, arg_parser.date_folders, hdr, video_sources, current_save_replay_seconds);
+ const bool replay_start_result = save_replay_async(video_codec_context, VIDEO_STREAM_INDEX, audio_tracks, &encoder, arg_parser, file_extension, arg_parser.settings.date_folders, hdr, video_sources, current_save_replay_seconds);
if(!replay_start_result) {
printf("gsr error: Failed to save replay\n");
fflush(stdout);
}
- if(arg_parser.restart_replay_on_save && current_save_replay_seconds == save_replay_seconds_full) {
+ if(arg_parser.settings.restart_replay_on_save && current_save_replay_seconds == save_replay_seconds_full) {
pthread_mutex_lock(&encoder.replay_mutex);
gsr_replay_buffer_clear(encoder.replay_buffer);
pthread_mutex_unlock(&encoder.replay_mutex);
@@ -4605,7 +3982,7 @@ int main(int argc, char **argv) {
else {
if(paused)
av_usleep(20.0 * 1000.0); // 20 milliseconds
- else if(arg_parser.framerate_mode == GSR_FRAMERATE_MODE_CONTENT)
+ else if(arg_parser.settings.framerate_mode == GSR_FRAMERATE_MODE_CONTENT)
av_usleep(2.8 * 1000.0); // 2.8 milliseconds
}
}
@@ -4619,8 +3996,8 @@ int main(int argc, char **argv) {
} else {
puts(save_replay_output_filepath.c_str());
fflush(stdout);
- if(arg_parser.recording_saved_script)
- run_recording_saved_script_async(arg_parser.recording_saved_script, save_replay_output_filepath.c_str(), "replay");
+ if(arg_parser.settings.recording_saved_script)
+ run_recording_saved_script_async(arg_parser.settings.recording_saved_script, save_replay_output_filepath.c_str(), "replay");
}
}
@@ -4636,8 +4013,8 @@ int main(int argc, char **argv) {
fprintf(stderr, "Stopped recording\n");
puts(replay_recording_filepath.c_str());
fflush(stdout);
- if(arg_parser.recording_saved_script)
- run_recording_saved_script_async(arg_parser.recording_saved_script, replay_recording_filepath.c_str(), "regular");
+ if(arg_parser.settings.recording_saved_script)
+ run_recording_saved_script_async(arg_parser.settings.recording_saved_script, replay_recording_filepath.c_str(), "regular");
} else {
printf("gsr error: Failed to save recording\n");
fflush(stdout);
@@ -4655,11 +4032,11 @@ int main(int argc, char **argv) {
amix_thread.join();
// TODO: Replace this with start_recording_create_steams
- if(!arg_parser.is_replaying && av_write_trailer(av_format_context) != 0) {
+ if(!arg_parser.settings.is_replaying && av_write_trailer(av_format_context) != 0) {
//fprintf(stderr, "Failed to write trailer\n");
}
- if(!arg_parser.is_replaying && !(output_format->flags & AVFMT_NOFILE)) {
+ if(!arg_parser.settings.is_replaying && !(output_format->flags & AVFMT_NOFILE)) {
avio_close(av_format_context->pb);
avformat_free_context(av_format_context);
}
@@ -4682,8 +4059,8 @@ int main(int argc, char **argv) {
gsr_kde_night_light_destroy(kde_night_light);
- if(!arg_parser.is_replaying && arg_parser.recording_saved_script)
- run_recording_saved_script_async(arg_parser.recording_saved_script, arg_parser.filename, "regular");
+ if(!arg_parser.settings.is_replaying && arg_parser.settings.recording_saved_script)
+ run_recording_saved_script_async(arg_parser.settings.recording_saved_script, arg_parser.settings.filename, "regular");
if(dpy) {
// TODO: This causes a crash, why? maybe some other library dlclose xlib and that also happened to unload this???
diff --git a/src/recorder/audio_codec.c b/src/recorder/audio_codec.c
new file mode 100644
index 0000000..1b2d6db
--- /dev/null
+++ b/src/recorder/audio_codec.c
@@ -0,0 +1,220 @@
+#include "../../include/recorder/audio_codec.h"
+#include "../../include/ffmpeg_utils.h"
+#include "../../include/log.h"
+
+#include <assert.h>
+#include <math.h>
+#include <stdio.h>
+
+#include <libavutil/opt.h>
+
+enum AVCodecID audio_codec_get_id(gsr_audio_codec audio_codec) {
+ switch(audio_codec) {
+ case GSR_AUDIO_CODEC_AAC: return AV_CODEC_ID_AAC;
+ case GSR_AUDIO_CODEC_OPUS: return AV_CODEC_ID_OPUS;
+ case GSR_AUDIO_CODEC_FLAC: return AV_CODEC_ID_FLAC;
+ }
+ assert(false);
+ return AV_CODEC_ID_AAC;
+}
+
+enum AVSampleFormat audio_codec_get_sample_format(AVCodecContext *audio_codec_context, gsr_audio_codec audio_codec, const AVCodec *codec, bool mix_audio) {
+ (void)audio_codec_context;
+ switch(audio_codec) {
+ case GSR_AUDIO_CODEC_AAC: {
+ return AV_SAMPLE_FMT_FLTP;
+ }
+ case GSR_AUDIO_CODEC_OPUS: {
+ bool supports_s16 = false;
+ bool supports_flt = false;
+
+ #if LIBAVCODEC_VERSION_INT < AV_VERSION_INT(61, 15, 0)
+ for(size_t i = 0; codec->sample_fmts && codec->sample_fmts[i] != AV_SAMPLE_FMT_NONE; ++i) {
+ if(codec->sample_fmts[i] == AV_SAMPLE_FMT_S16) {
+ supports_s16 = true;
+ } else if(codec->sample_fmts[i] == AV_SAMPLE_FMT_FLT) {
+ supports_flt = true;
+ }
+ }
+ #else
+ const enum AVSampleFormat *sample_fmts = NULL;
+ if(avcodec_get_supported_config(audio_codec_context, codec, AV_CODEC_CONFIG_SAMPLE_FORMAT, 0, (const void**)&sample_fmts, NULL) >= 0) {
+ if(sample_fmts) {
+ for(size_t i = 0; sample_fmts[i] != AV_SAMPLE_FMT_NONE; ++i) {
+ if(sample_fmts[i] == AV_SAMPLE_FMT_S16) {
+ supports_s16 = true;
+ } else if(sample_fmts[i] == AV_SAMPLE_FMT_FLT) {
+ supports_flt = true;
+ }
+ }
+ } else {
+ // What a dumb API. It returns NULL if all formats are supported
+ supports_s16 = true;
+ supports_flt = true;
+ }
+ }
+ #endif
+
+ // Amix only works with float audio
+ if(mix_audio)
+ supports_s16 = false;
+
+ if(!supports_s16 && !supports_flt) {
+ gsr_log(GSR_LOG_LEVEL_WARNING, "opus audio codec is chosen but your ffmpeg version does not support s16/flt sample format and performance might be slightly worse.\n"
+ " You can either rebuild ffmpeg with libopus instead of the built-in opus, use the flatpak version of gpu screen recorder or record with aac audio codec instead (-ac aac).\n"
+ " Falling back to fltp audio sample format instead.");
+ }
+
+ if(supports_s16)
+ return AV_SAMPLE_FMT_S16;
+ else if(supports_flt)
+ return AV_SAMPLE_FMT_FLT;
+ else
+ return AV_SAMPLE_FMT_FLTP;
+ }
+ case GSR_AUDIO_CODEC_FLAC: {
+ return AV_SAMPLE_FMT_S32;
+ }
+ }
+ assert(false);
+ return AV_SAMPLE_FMT_FLTP;
+}
+
+int64_t audio_codec_get_get_bitrate(gsr_audio_codec audio_codec) {
+ switch(audio_codec) {
+ case GSR_AUDIO_CODEC_AAC: return 160000;
+ case GSR_AUDIO_CODEC_OPUS: return 128000;
+ case GSR_AUDIO_CODEC_FLAC: return 128000;
+ }
+ assert(false);
+ return 128000;
+}
+
+gsr_audio_format audio_codec_context_get_audio_format(const AVCodecContext *audio_codec_context) {
+ switch(audio_codec_context->sample_fmt) {
+ case AV_SAMPLE_FMT_FLT: return GSR_AUDIO_FORMAT_F32;
+ case AV_SAMPLE_FMT_FLTP: return GSR_AUDIO_FORMAT_S32;
+ case AV_SAMPLE_FMT_S16: return GSR_AUDIO_FORMAT_S16;
+ case AV_SAMPLE_FMT_S32: return GSR_AUDIO_FORMAT_S32;
+ default: return GSR_AUDIO_FORMAT_S16;
+ }
+}
+
+enum AVSampleFormat audio_format_to_sample_format(const gsr_audio_format audio_format) {
+ switch(audio_format) {
+ case GSR_AUDIO_FORMAT_S16: return AV_SAMPLE_FMT_S16;
+ case GSR_AUDIO_FORMAT_S32: return AV_SAMPLE_FMT_S32;
+ case GSR_AUDIO_FORMAT_F32: return AV_SAMPLE_FMT_FLT;
+ }
+ assert(false);
+ return AV_SAMPLE_FMT_S16;
+}
+
+AVCodecContext* create_audio_codec_context(int fps, gsr_audio_codec audio_codec, bool mix_audio, int64_t audio_bitrate) {
+ (void)fps;
+ const AVCodec *codec = avcodec_find_encoder(audio_codec_get_id(audio_codec));
+ if (!codec) {
+ gsr_log(GSR_LOG_LEVEL_ERROR, "Could not find %s audio encoder", audio_codec_get_name(audio_codec));
+ return NULL;
+ }
+
+ AVCodecContext *codec_context = avcodec_alloc_context3(codec);
+
+ assert(codec->type == AVMEDIA_TYPE_AUDIO);
+ codec_context->codec_id = codec->id;
+ codec_context->sample_fmt = audio_codec_get_sample_format(codec_context, audio_codec, codec, mix_audio);
+ codec_context->bit_rate = audio_bitrate == 0 ? audio_codec_get_get_bitrate(audio_codec) : audio_bitrate;
+ codec_context->sample_rate = GSR_AUDIO_SAMPLE_RATE;
+ if(audio_codec == GSR_AUDIO_CODEC_AAC) {
+#if LIBAVCODEC_VERSION_MAJOR < 62
+ codec_context->profile = FF_PROFILE_AAC_LOW;
+#else
+ codec_context->profile = AV_PROFILE_AAC_LOW;
+#endif
+ }
+#if LIBAVCODEC_VERSION_MAJOR < 60
+ codec_context->channel_layout = AV_CH_LAYOUT_STEREO;
+ codec_context->channels = 2;
+#else
+ av_channel_layout_default(&codec_context->ch_layout, 2);
+#endif
+
+ codec_context->time_base.num = 1;
+ codec_context->time_base.den = codec_context->sample_rate;
+ codec_context->thread_count = 1;
+ codec_context->flags |= AV_CODEC_FLAG_GLOBAL_HEADER;
+
+ return codec_context;
+}
+
+bool open_audio(AVCodecContext *audio_codec_context, const char *ffmpeg_audio_opts) {
+ AVDictionary *options = NULL;
+ av_dict_set(&options, "strict", "experimental", 0);
+
+ if(ffmpeg_audio_opts)
+ av_dict_parse_string(&options, ffmpeg_audio_opts, "=", ";", 0);
+
+ int ret;
+ ret = avcodec_open2(audio_codec_context, audio_codec_context->codec, &options);
+ if(ret < 0) {
+ gsr_log(GSR_LOG_LEVEL_ERROR, "failed to open audio codec, reason: %s", gsr_av_error_to_string(ret));
+ return false;
+ }
+
+ return true;
+}
+
+AVFrame* create_audio_frame(AVCodecContext *audio_codec_context) {
+ AVFrame *frame = av_frame_alloc();
+ if(!frame) {
+ gsr_log(GSR_LOG_LEVEL_ERROR, "failed to allocate audio frame");
+ return NULL;
+ }
+
+ frame->sample_rate = audio_codec_context->sample_rate;
+ frame->nb_samples = audio_codec_context->frame_size;
+ frame->format = audio_codec_context->sample_fmt;
+#if LIBAVCODEC_VERSION_MAJOR < 60
+ frame->channels = audio_codec_context->channels;
+ frame->channel_layout = audio_codec_context->channel_layout;
+#else
+ av_channel_layout_copy(&frame->ch_layout, &audio_codec_context->ch_layout);
+#endif
+
+ int ret = av_frame_get_buffer(frame, 0);
+ if(ret < 0) {
+ gsr_log(GSR_LOG_LEVEL_ERROR, "failed to allocate audio data buffers, reason: %s", gsr_av_error_to_string(ret));
+ av_frame_free(&frame);
+ return NULL;
+ }
+
+ return frame;
+}
+
+double audio_codec_get_desired_delay(gsr_audio_codec audio_codec, int fps) {
+ const double fps_inv = 1.0 / (double)fps;
+ const double base = 0.01 + 1.0/165.0;
+ switch(audio_codec) {
+ case GSR_AUDIO_CODEC_OPUS:
+ return fmax(0.0, base - fps_inv);
+ case GSR_AUDIO_CODEC_AAC:
+ return fmax(0.0, (base + 0.008) * 2.0 - fps_inv);
+ case GSR_AUDIO_CODEC_FLAC:
+ // TODO: Test
+ return fmax(0.0, base - fps_inv);
+ }
+ assert(false);
+ return fmax(0.0, base - fps_inv);
+}
+
+int audio_codec_get_frame_size(gsr_audio_codec audio_codec) {
+ switch(audio_codec) {
+ case GSR_AUDIO_CODEC_AAC: return 1024;
+ case GSR_AUDIO_CODEC_OPUS: return 960;
+ case GSR_AUDIO_CODEC_FLAC:
+ assert(false);
+ return 1024;
+ }
+ assert(false);
+ return 1024;
+}
diff --git a/src/recorder/video_codec.c b/src/recorder/video_codec.c
new file mode 100644
index 0000000..1713e77
--- /dev/null
+++ b/src/recorder/video_codec.c
@@ -0,0 +1,429 @@
+#include "../../include/recorder/video_codec.h"
+#include "../../include/ffmpeg_utils.h"
+#include "../../include/log.h"
+
+#include <assert.h>
+#include <math.h>
+
+#include <libavutil/opt.h>
+
+int video_quality_to_h264_equivalent_qp(gsr_video_quality video_quality) {
+ switch(video_quality) {
+ case GSR_VIDEO_QUALITY_MEDIUM: return 35;
+ case GSR_VIDEO_QUALITY_HIGH: return 30;
+ case GSR_VIDEO_QUALITY_VERY_HIGH: return 25;
+ case GSR_VIDEO_QUALITY_ULTRA: return 22;
+ }
+ return 22;
+}
+
+static int video_quality_to_codec_quality_value(enum AVCodecID codec_id, gsr_video_quality video_quality) {
+ const int h264_qp = video_quality_to_h264_equivalent_qp(video_quality);
+ switch(codec_id) {
+ case AV_CODEC_ID_H264:
+ case AV_CODEC_ID_HEVC:
+ return h264_qp;
+ case AV_CODEC_ID_AV1:
+ case AV_CODEC_ID_VP9:
+ return h264_qp * 4;
+ case AV_CODEC_ID_VP8:
+ return h264_qp * 2;
+ default:
+ return h264_qp;
+ }
+}
+
+static int vbr_get_quality_parameter(AVCodecContext *codec_context, gsr_video_quality video_quality, bool hdr) {
+ // 8 bit / 10 bit = 80%
+ const float qp_multiply = hdr ? 8.0f/10.0f : 1.0f;
+ return video_quality_to_codec_quality_value(codec_context->codec_id, video_quality) * qp_multiply;
+}
+
+AVCodecContext *create_video_codec_context(enum AVPixelFormat pix_fmt, const AVCodec *codec, const gsr_egl *egl, const gsr_recorder_settings *settings, int width, int height) {
+ const bool use_software_video_encoder = settings->video_encoder == GSR_VIDEO_ENCODER_HW_CPU;
+ const bool hdr = video_codec_is_hdr(settings->video_codec);
+ AVCodecContext *codec_context = avcodec_alloc_context3(codec);
+
+ //double fps_ratio = (double)fps / 30.0;
+
+ assert(codec->type == AVMEDIA_TYPE_VIDEO);
+ codec_context->codec_id = codec->id;
+ codec_context->width = width;
+ codec_context->height = height;
+ // Timebase: This is the fundamental unit of time (in seconds) in terms
+ // of which frame timestamps are represented. For fixed-fps content,
+ // timebase should be 1/framerate and timestamp increments should be
+ // identical to 1
+ codec_context->time_base.num = 1;
+ codec_context->time_base.den = settings->framerate_mode == GSR_FRAMERATE_MODE_CONSTANT ? settings->fps : AV_TIME_BASE;
+ codec_context->framerate.num = settings->fps;
+ codec_context->framerate.den = 1;
+ codec_context->sample_aspect_ratio.num = 0;
+ codec_context->sample_aspect_ratio.den = 0;
+ if(settings->low_latency_recording) {
+ codec_context->flags |= (AV_CODEC_FLAG_CLOSED_GOP | AV_CODEC_FLAG_LOW_DELAY);
+ codec_context->flags2 |= AV_CODEC_FLAG2_FAST;
+ //codec_context->gop_size = std::numeric_limits<int>::max();
+ //codec_context->keyint_min = std::numeric_limits<int>::max();
+ codec_context->gop_size = settings->fps * settings->keyint;
+ } else {
+ // High values reduce file size but increases time it takes to seek
+ codec_context->gop_size = settings->fps * settings->keyint;
+ }
+ codec_context->max_b_frames = 0;
+ codec_context->pix_fmt = pix_fmt;
+ codec_context->color_range = settings->color_range == GSR_COLOR_RANGE_LIMITED ? AVCOL_RANGE_MPEG : AVCOL_RANGE_JPEG;
+ if(hdr) {
+ codec_context->color_primaries = AVCOL_PRI_BT2020;
+ codec_context->color_trc = AVCOL_TRC_SMPTE2084;
+ codec_context->colorspace = AVCOL_SPC_BT2020_NCL;
+ } else {
+ codec_context->color_primaries = AVCOL_PRI_BT709;
+ codec_context->color_trc = AVCOL_TRC_BT709;
+ codec_context->colorspace = AVCOL_SPC_BT709;
+ }
+ //codec_context->chroma_sample_location = AVCHROMA_LOC_CENTER;
+ // Can't use this because it's fucking broken in ffmpeg 8 or new mesa. It produces garbage output
+ //if(codec->id == AV_CODEC_ID_HEVC)
+ // codec_context->codec_tag = MKTAG('h', 'v', 'c', '1'); // QuickTime on MacOS requires this or the video wont be playable
+
+ if(settings->bitrate_mode == GSR_BITRATE_MODE_CBR) {
+ codec_context->bit_rate = settings->video_bitrate;
+ codec_context->rc_max_rate = codec_context->bit_rate;
+ //codec_context->rc_min_rate = codec_context->bit_rate;
+ codec_context->rc_buffer_size = codec_context->bit_rate;//codec_context->bit_rate / 10;
+ codec_context->rc_initial_buffer_occupancy = 0;//codec_context->bit_rate;//codec_context->bit_rate * 1000;
+ } else if(settings->bitrate_mode == GSR_BITRATE_MODE_VBR) {
+ const int quality = vbr_get_quality_parameter(codec_context, settings->video_quality, hdr);
+ switch(settings->video_quality) {
+ case GSR_VIDEO_QUALITY_MEDIUM:
+ codec_context->qmin = quality;
+ codec_context->qmax = quality;
+ codec_context->bit_rate = 100000;//4500000 + (codec_context->width * codec_context->height)*0.75;
+ break;
+ case GSR_VIDEO_QUALITY_HIGH:
+ codec_context->qmin = quality;
+ codec_context->qmax = quality;
+ codec_context->bit_rate = 100000;//10000000-9000000 + (codec_context->width * codec_context->height)*0.75;
+ break;
+ case GSR_VIDEO_QUALITY_VERY_HIGH:
+ codec_context->qmin = quality;
+ codec_context->qmax = quality;
+ codec_context->bit_rate = 100000;//10000000-9000000 + (codec_context->width * codec_context->height)*0.75;
+ break;
+ case GSR_VIDEO_QUALITY_ULTRA:
+ codec_context->qmin = quality;
+ codec_context->qmax = quality;
+ codec_context->bit_rate = 100000;//10000000-9000000 + (codec_context->width * codec_context->height)*0.75;
+ break;
+ }
+
+ codec_context->rc_max_rate = codec_context->bit_rate;
+ //codec_context->rc_min_rate = codec_context->bit_rate;
+ codec_context->rc_buffer_size = codec_context->bit_rate;//codec_context->bit_rate / 10;
+ codec_context->rc_initial_buffer_occupancy = codec_context->bit_rate;//codec_context->bit_rate * 1000;
+ } else {
+ //codec_context->rc_buffer_size = 50000 * 1000;
+ }
+ //codec_context->profile = FF_PROFILE_H264_MAIN;
+ if (codec_context->codec_id == AV_CODEC_ID_MPEG1VIDEO)
+ codec_context->mb_decision = 2;
+
+ const bool uses_vaapi_encoder = !use_software_video_encoder && egl->gpu_info.vendor != GSR_GPU_VENDOR_NVIDIA && !video_codec_is_vulkan(settings->video_codec);
+ if(uses_vaapi_encoder && settings->bitrate_mode != GSR_BITRATE_MODE_CBR) {
+ // 8 bit / 10 bit = 80%, and increase it even more
+ const float quality_multiply = hdr ? (8.0f/10.0f * 0.7f) : 1.0f;
+ codec_context->global_quality = video_quality_to_codec_quality_value(codec_context->codec_id, settings->video_quality) * quality_multiply;
+ }
+
+ av_opt_set_int(codec_context->priv_data, "b_ref_mode", 0, 0);
+ //av_opt_set_int(codec_context->priv_data, "cbr", true, 0);
+
+ if(egl->gpu_info.vendor != GSR_GPU_VENDOR_NVIDIA || video_codec_is_vulkan(settings->video_codec)) {
+ // TODO: More options, better options
+ //codec_context->bit_rate = codec_context->width * codec_context->height;
+ switch(settings->bitrate_mode) {
+ case GSR_BITRATE_MODE_QP: {
+ if(video_codec_is_vulkan(settings->video_codec))
+ av_opt_set(codec_context->priv_data, "rc_mode", "cqp", 0);
+ else if(egl->gpu_info.vendor == GSR_GPU_VENDOR_NVIDIA)
+ av_opt_set(codec_context->priv_data, "rc", "constqp", 0);
+ else
+ av_opt_set(codec_context->priv_data, "rc_mode", "CQP", 0);
+ break;
+ }
+ case GSR_BITRATE_MODE_VBR: {
+ if(video_codec_is_vulkan(settings->video_codec))
+ av_opt_set(codec_context->priv_data, "rc_mode", "vbr", 0);
+ else if(egl->gpu_info.vendor == GSR_GPU_VENDOR_NVIDIA)
+ av_opt_set(codec_context->priv_data, "rc", "vbr", 0);
+ else
+ av_opt_set(codec_context->priv_data, "rc_mode", "VBR", 0);
+ break;
+ }
+ case GSR_BITRATE_MODE_CBR: {
+ if(video_codec_is_vulkan(settings->video_codec))
+ av_opt_set(codec_context->priv_data, "rc_mode", "cbr", 0);
+ else if(egl->gpu_info.vendor == GSR_GPU_VENDOR_NVIDIA)
+ av_opt_set(codec_context->priv_data, "rc", "cbr", 0);
+ else
+ av_opt_set(codec_context->priv_data, "rc_mode", "CBR", 0);
+ break;
+ }
+ }
+ //codec_context->global_quality = 4;
+ //codec_context->compression_level = 2;
+ }
+
+ //av_opt_set(codec_context->priv_data, "bsf", "hevc_metadata=colour_primaries=9:transfer_characteristics=16:matrix_coefficients=9", 0);
+
+ if(settings->tune == GSR_TUNE_QUALITY)
+ codec_context->max_b_frames = 2;
+
+ codec_context->flags |= AV_CODEC_FLAG_GLOBAL_HEADER;
+
+ return codec_context;
+}
+
+static void dict_set_profile(AVCodecContext *codec_context, gsr_gpu_vendor vendor, gsr_color_depth color_depth, gsr_video_codec video_codec, AVDictionary **options) {
+ #if LIBAVCODEC_VERSION_INT < AV_VERSION_INT(61, 17, 100)
+ if(codec_context->codec_id == AV_CODEC_ID_H264) {
+ // TODO: Only for vaapi
+ //if(color_depth == GSR_COLOR_DEPTH_10_BITS)
+ // av_dict_set(options, "profile", "high10", 0);
+ //else
+ av_dict_set(options, "profile", "high", 0);
+ } else if(codec_context->codec_id == AV_CODEC_ID_AV1) {
+ if(vendor == GSR_GPU_VENDOR_NVIDIA) {
+ if(color_depth == GSR_COLOR_DEPTH_10_BITS)
+ av_dict_set_int(options, "highbitdepth", 1, 0);
+ } else {
+ av_dict_set(options, "profile", "main", 0); // TODO: use professional instead?
+ }
+ } else if(codec_context->codec_id == AV_CODEC_ID_HEVC) {
+ if(color_depth == GSR_COLOR_DEPTH_10_BITS)
+ av_dict_set(options, "profile", "main10", 0);
+ else
+ av_dict_set(options, "profile", "main", 0);
+ }
+ #else
+ const bool use_nvidia_values = vendor == GSR_GPU_VENDOR_NVIDIA && !video_codec_is_vulkan(video_codec);
+ if(codec_context->codec_id == AV_CODEC_ID_H264) {
+ // TODO: Only for vaapi
+ //if(color_depth == GSR_COLOR_DEPTH_10_BITS)
+ // av_dict_set_int(options, "profile", AV_PROFILE_H264_HIGH_10, 0);
+ //else
+ av_dict_set_int(options, "profile", use_nvidia_values ? 2 : AV_PROFILE_H264_HIGH, 0);
+ } else if(codec_context->codec_id == AV_CODEC_ID_AV1) {
+ if(use_nvidia_values) {
+ if(color_depth == GSR_COLOR_DEPTH_10_BITS)
+ av_dict_set_int(options, "highbitdepth", 1, 0);
+ } else {
+ av_dict_set_int(options, "profile", AV_PROFILE_AV1_MAIN, 0); // TODO: use professional instead?
+ }
+ } else if(codec_context->codec_id == AV_CODEC_ID_HEVC) {
+ if(color_depth == GSR_COLOR_DEPTH_10_BITS)
+ av_dict_set_int(options, "profile", use_nvidia_values ? 1 : AV_PROFILE_HEVC_MAIN_10, 0);
+ else
+ av_dict_set_int(options, "profile", use_nvidia_values ? 0 : AV_PROFILE_HEVC_MAIN, 0);
+ }
+ #endif
+}
+
+static void video_software_set_qp(AVCodecContext *codec_context, gsr_video_quality video_quality, bool hdr, AVDictionary **options) {
+ // 8 bit / 10 bit = 80%
+ const float qp_multiply = hdr ? 8.0f/10.0f : 1.0f;
+ av_dict_set_int(options, "qp", video_quality_to_codec_quality_value(codec_context->codec_id, video_quality) * qp_multiply, 0);
+}
+
+bool open_video_software(AVCodecContext *codec_context, const gsr_recorder_settings *settings) {
+ const bool hdr = video_codec_is_hdr(settings->video_codec);
+ AVDictionary *options = NULL;
+
+ if(settings->bitrate_mode == GSR_BITRATE_MODE_QP)
+ video_software_set_qp(codec_context, settings->video_quality, hdr, &options);
+
+ av_dict_set(&options, "preset", "veryfast", 0);
+ av_dict_set(&options, "tune", "film", 0);
+ av_dict_set_int(&options, "forced-idr", 1, 0);
+
+ if(codec_context->codec_id == AV_CODEC_ID_H264) {
+ av_dict_set(&options, "coder", "cabac", 0); // TODO: cavlc is faster than cabac but worse compression. Which to use?
+ }
+
+ av_dict_set(&options, "strict", "experimental", 0);
+
+ if(settings->ffmpeg_video_opts)
+ av_dict_parse_string(&options, settings->ffmpeg_video_opts, "=", ";", 0);
+
+ int ret = avcodec_open2(codec_context, codec_context->codec, &options);
+ if (ret < 0) {
+ gsr_log(GSR_LOG_LEVEL_ERROR, "Could not open video codec: %s", gsr_av_error_to_string(ret));
+ return false;
+ }
+
+ return true;
+}
+
+static void video_set_rc(gsr_video_codec video_codec, gsr_gpu_vendor vendor, gsr_bitrate_mode bitrate_mode, AVDictionary **options) {
+ switch(bitrate_mode) {
+ case GSR_BITRATE_MODE_QP: {
+ if(video_codec_is_vulkan(video_codec))
+ av_dict_set(options, "rc_mode", "cqp", 0);
+ else if(vendor == GSR_GPU_VENDOR_NVIDIA)
+ av_dict_set(options, "rc", "constqp", 0);
+ else
+ av_dict_set(options, "rc_mode", "CQP", 0);
+ break;
+ }
+ case GSR_BITRATE_MODE_VBR: {
+ if(video_codec_is_vulkan(video_codec))
+ av_dict_set(options, "rc_mode", "vbr", 0);
+ else if(vendor == GSR_GPU_VENDOR_NVIDIA)
+ av_dict_set(options, "rc", "vbr", 0);
+ else
+ av_dict_set(options, "rc_mode", "VBR", 0);
+ break;
+ }
+ case GSR_BITRATE_MODE_CBR: {
+ if(video_codec_is_vulkan(video_codec))
+ av_dict_set(options, "rc_mode", "cbr", 0);
+ else if(vendor == GSR_GPU_VENDOR_NVIDIA)
+ av_dict_set(options, "rc", "cbr", 0);
+ else
+ av_dict_set(options, "rc_mode", "CBR", 0);
+ break;
+ }
+ }
+}
+
+static void video_hardware_set_qp(AVCodecContext *codec_context, gsr_video_quality video_quality, bool hdr, AVDictionary **options) {
+ // 8 bit / 10 bit = 80%
+ const float qp_multiply = hdr ? 8.0f/10.0f : 1.0f;
+ av_dict_set_int(options, "qp", video_quality_to_codec_quality_value(codec_context->codec_id, video_quality) * qp_multiply, 0);
+}
+
+bool open_video_hardware(AVCodecContext *codec_context, bool low_power, const gsr_egl *egl, const gsr_recorder_settings *settings) {
+ const gsr_color_depth color_depth = video_codec_to_bit_depth(settings->video_codec);
+ const bool hdr = video_codec_is_hdr(settings->video_codec);
+ AVDictionary *options = NULL;
+
+ if(settings->bitrate_mode == GSR_BITRATE_MODE_QP)
+ video_hardware_set_qp(codec_context, settings->video_quality, hdr, &options);
+
+ video_set_rc(settings->video_codec, egl->gpu_info.vendor, settings->bitrate_mode, &options);
+
+ // TODO: Enable multipass
+
+ dict_set_profile(codec_context, egl->gpu_info.vendor, color_depth, settings->video_codec, &options);
+
+ if(video_codec_is_vulkan(settings->video_codec)) {
+ av_dict_set_int(&options, "async_depth", 3, 0);
+ av_dict_set(&options, "tune", "ll", 0); // Low latency
+ av_dict_set(&options, "usage", settings->is_livestream ? "stream" : "record", 0);
+ av_dict_set(&options, "content", "rendered", 0); // Game or 3D content
+
+ if(codec_context->codec_id == AV_CODEC_ID_H264) {
+ // Removed because it causes stutter in games for some people
+ //av_dict_set_int(&options, "quality", 5, 0); // quality preset
+ } else if(codec_context->codec_id == AV_CODEC_ID_AV1) {
+ av_dict_set(&options, "tier", "main", 0);
+ } else if(codec_context->codec_id == AV_CODEC_ID_HEVC) {
+ if(hdr)
+ av_dict_set(&options, "sei", "hdr", 0);
+ }
+ } else if(egl->gpu_info.vendor == GSR_GPU_VENDOR_NVIDIA) {
+ // TODO: These dont seem to be necessary
+ // av_dict_set_int(&options, "zerolatency", 1, 0);
+ // if(codec_context->codec_id == AV_CODEC_ID_AV1) {
+ // av_dict_set(&options, "tune", "ll", 0);
+ // } else if(codec_context->codec_id == AV_CODEC_ID_H264 || codec_context->codec_id == AV_CODEC_ID_HEVC) {
+ // av_dict_set(&options, "preset", "llhq", 0);
+ // av_dict_set(&options, "tune", "ll", 0);
+ // }
+ av_dict_set(&options, "tune", "ll", 0);
+ av_dict_set_int(&options, "forced-idr", 1, 0);
+
+ switch(settings->tune) {
+ case GSR_TUNE_PERFORMANCE:
+ //av_dict_set(&options, "multipass", "qres", 0);
+ break;
+ case GSR_TUNE_QUALITY:
+ av_dict_set(&options, "multipass", "fullres", 0);
+ av_dict_set(&options, "preset", "p6", 0);
+ av_dict_set_int(&options, "rc-lookahead", 0, 0);
+ break;
+ }
+
+ if(codec_context->codec_id == AV_CODEC_ID_H264) {
+ // TODO: h264 10bit?
+ // TODO:
+ // switch(pixel_format) {
+ // case GSR_PIXEL_FORMAT_YUV420:
+ // av_dict_set_int(&options, "profile", AV_PROFILE_H264_HIGH, 0);
+ // break;
+ // case GSR_PIXEL_FORMAT_YUV444:
+ // av_dict_set_int(&options, "profile", AV_PROFILE_H264_HIGH_444, 0);
+ // break;
+ // }
+ } else if(codec_context->codec_id == AV_CODEC_ID_AV1) {
+ switch(settings->pixel_format) {
+ case GSR_PIXEL_FORMAT_YUV420:
+ av_dict_set(&options, "rgb_mode", "yuv420", 0);
+ break;
+ case GSR_PIXEL_FORMAT_YUV444:
+ av_dict_set(&options, "rgb_mode", "yuv444", 0);
+ break;
+ }
+ } else if(codec_context->codec_id == AV_CODEC_ID_HEVC) {
+ //av_dict_set(&options, "pix_fmt", "yuv420p16le", 0);
+ }
+ } else {
+ // TODO: More quality options
+ if(low_power)
+ av_dict_set_int(&options, "low_power", 1, 0);
+ // Improves performance but increases vram.
+ // TODO: Might need a different async_depth for optimal performance on different amd/intel gpus
+ av_dict_set_int(&options, "async_depth", 3, 0);
+
+ if(codec_context->codec_id == AV_CODEC_ID_H264) {
+ // Removed because it causes stutter in games for some people
+ //av_dict_set_int(&options, "quality", 5, 0); // quality preset
+ } else if(codec_context->codec_id == AV_CODEC_ID_AV1) {
+ av_dict_set(&options, "tier", "main", 0);
+ } else if(codec_context->codec_id == AV_CODEC_ID_HEVC) {
+ if(hdr)
+ av_dict_set(&options, "sei", "hdr", 0);
+ }
+
+ // TODO: vp8/vp9 10bit
+ }
+
+ if(codec_context->codec_id == AV_CODEC_ID_H264) {
+ av_dict_set(&options, "coder", "cabac", 0); // TODO: cavlc is faster than cabac but worse compression. Which to use?
+ }
+
+ av_dict_set(&options, "strict", "experimental", 0);
+
+ if(settings->ffmpeg_video_opts)
+ av_dict_parse_string(&options, settings->ffmpeg_video_opts, "=", ";", 0);
+
+ int ret = avcodec_open2(codec_context, codec_context->codec, &options);
+ if (ret < 0) {
+ gsr_log(GSR_LOG_LEVEL_ERROR, "Could not open video codec: %s", gsr_av_error_to_string(ret));
+ return false;
+ }
+
+ return true;
+}
+
+enum AVPixelFormat get_pixel_format(gsr_video_codec video_codec, gsr_gpu_vendor vendor, bool use_software_video_encoder) {
+ if(use_software_video_encoder) {
+ return AV_PIX_FMT_NV12;
+ } else {
+ if(video_codec_is_vulkan(video_codec))
+ return AV_PIX_FMT_VULKAN;
+ else
+ return vendor == GSR_GPU_VENDOR_NVIDIA ? AV_PIX_FMT_CUDA : AV_PIX_FMT_VAAPI;
+ }
+}