aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--include/recorder/capture_setup.h49
-rw-r--r--include/recorder/capture_source.h60
-rw-r--r--include/recorder/error.h2
-rw-r--r--meson.build2
-rw-r--r--src/main.cpp929
-rw-r--r--src/recorder/capture_setup.c543
-rw-r--r--src/recorder/capture_source.c390
7 files changed, 1128 insertions, 847 deletions
diff --git a/include/recorder/capture_setup.h b/include/recorder/capture_setup.h
new file mode 100644
index 0000000..fd4b7e7
--- /dev/null
+++ b/include/recorder/capture_setup.h
@@ -0,0 +1,49 @@
+#ifndef GSR_RECORDER_CAPTURE_SETUP_H
+#define GSR_RECORDER_CAPTURE_SETUP_H
+
+#include <stdbool.h>
+#include <stddef.h>
+#include "../egl.h"
+#include "../cursor.h"
+#include "../kde_night_light.h"
+#include "../capture/capture.h"
+#include "../../kms/client/kms_client.h"
+#include "capture_source.h"
+#include "settings.h"
+
+#include <X11/Xlib.h>
+
+typedef struct {
+ gsr_kms_client kms_client;
+ bool kms_client_initialized;
+ gsr_kms_response kms_response;
+ gsr_kde_night_light *kde_night_light;
+ bool kde_night_light_initialized;
+ gsr_cursor x11_cursor;
+ Display *x11_cursor_display;
+} gsr_capture_deps;
+
+typedef struct {
+ gsr_capture *capture;
+ gsr_capture_metadata metadata;
+ gsr_capture_source *capture_source;
+} gsr_video_source;
+
+typedef struct {
+ gsr_video_source *items;
+ size_t num_items;
+} gsr_video_sources;
+
+void gsr_capture_deps_init(gsr_capture_deps *self);
+void gsr_capture_deps_init_cursor(gsr_capture_deps *self, gsr_egl *egl, bool record_cursor);
+void gsr_capture_deps_deinit(gsr_capture_deps *self);
+void gsr_capture_deps_cleanup_kms_fds(gsr_capture_deps *self);
+void gsr_capture_deps_update_kms(gsr_capture_deps *self);
+
+/* Returns a |gsr_error| value, or the error returned by gsr_capture_start. |video_size| is set to the size of all capture sources combined */
+int gsr_video_sources_create(gsr_video_sources *self, const gsr_recorder_settings *settings, gsr_egl *egl, gsr_capture_deps *deps, bool prefer_ximage, gsr_capture_sources *capture_sources, vec2i *video_size);
+void gsr_video_sources_update_with_real_video_size(gsr_video_sources *self, vec2i video_size);
+bool gsr_video_sources_uses_external_image(const gsr_video_sources *self);
+void gsr_video_sources_deinit(gsr_video_sources *self);
+
+#endif /* GSR_RECORDER_CAPTURE_SETUP_H */
diff --git a/include/recorder/capture_source.h b/include/recorder/capture_source.h
new file mode 100644
index 0000000..503c620
--- /dev/null
+++ b/include/recorder/capture_source.h
@@ -0,0 +1,60 @@
+#ifndef GSR_RECORDER_CAPTURE_SOURCE_H
+#define GSR_RECORDER_CAPTURE_SOURCE_H
+
+#include <stdbool.h>
+#include <stdint.h>
+#include <stddef.h>
+#include "../vec2.h"
+#include "../args_parser.h"
+#include "../capture/capture.h"
+#include "../capture/v4l2.h"
+
+#define GSR_CAPTURE_SOURCE_NAME_MAX_SIZE 256
+
+typedef enum {
+ VVEC2I_TYPE_PIXELS,
+ VVEC2I_TYPE_SCALAR
+} vvec2i_type;
+
+typedef struct {
+ int x;
+ int y;
+ vvec2i_type x_type;
+ vvec2i_type y_type;
+} vvec2i;
+
+typedef struct {
+ char name[GSR_CAPTURE_SOURCE_NAME_MAX_SIZE];
+ CaptureSourceType type;
+ gsr_capture_alignment halign;
+ gsr_capture_alignment valign;
+ gsr_capture_v4l2_pixfmt v4l2_pixfmt;
+ uint32_t flip;
+ vvec2i pos;
+ vvec2i size;
+ vec2i region_pos;
+ vec2i region_size;
+ bool region_set;
+ int64_t window_id;
+ int camera_fps;
+ vec2i camera_resolution;
+} gsr_capture_source;
+
+typedef struct {
+ gsr_capture_source *items;
+ size_t num_items;
+ size_t capacity_items;
+} gsr_capture_sources;
+
+void gsr_capture_source_init(gsr_capture_source *self, vec2i region_position, vec2i region_size);
+
+/* Returns a |gsr_error| value. Parses the -w option value, which may contain multiple capture sources separated by | */
+int gsr_capture_sources_parse(gsr_capture_sources *self, const char *capture_source_arg, vec2i region_position, vec2i region_size);
+void gsr_capture_sources_deinit(gsr_capture_sources *self);
+
+bool gsr_capture_sources_has_type(const gsr_capture_sources *self, CaptureSourceType type);
+bool gsr_capture_sources_has_damage_tracked_target(const gsr_capture_sources *self);
+bool gsr_capture_sources_has_region_set(const gsr_capture_sources *self);
+bool gsr_capture_sources_has_monitor_or_region(const gsr_capture_sources *self);
+
+#endif /* GSR_RECORDER_CAPTURE_SOURCE_H */
diff --git a/include/recorder/error.h b/include/recorder/error.h
index 75f1416..cfa83cd 100644
--- a/include/recorder/error.h
+++ b/include/recorder/error.h
@@ -9,7 +9,7 @@ extern "C" {
typedef enum {
GSR_ERROR_OK = 0,
GSR_ERROR_GENERIC = -1,
- GSR_ERROR_AUDIO_SETUP_FAILED = -2,
+ GSR_ERROR_UNSUPPORTED = -2,
GSR_ERROR_CAPTURE_FAILED = -3,
GSR_ERROR_VIDEO_CODEC_QUERY_FAILED = -11,
GSR_ERROR_OPENGL_LOAD_FAILED = -22,
diff --git a/meson.build b/meson.build
index 2568340..af5473d 100644
--- a/meson.build
+++ b/meson.build
@@ -36,6 +36,8 @@ src = [
'src/recorder/video_codec.c',
'src/recorder/codec_select.c',
'src/recorder/windowing.c',
+ 'src/recorder/capture_source.c',
+ 'src/recorder/capture_setup.c',
'src/egl.c',
'src/cuda.c',
'src/window_texture.c',
diff --git a/src/main.cpp b/src/main.cpp
index ec5a260..d64be78 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -4,6 +4,8 @@ extern "C" {
#include "../include/recorder/video_codec.h"
#include "../include/recorder/codec_select.h"
#include "../include/recorder/windowing.h"
+#include "../include/recorder/capture_source.h"
+#include "../include/recorder/capture_setup.h"
#include "../include/recorder/error.h"
#include "../include/capture/nvfbc.h"
#include "../include/capture/xcomposite.h"
@@ -99,59 +101,6 @@ struct MergedAudioInputs {
static const int VIDEO_STREAM_INDEX = 0;
-typedef struct {
- const gsr_window *window;
-} MonitorOutputCallbackUserdata;
-
-static void monitor_output_callback_print(const gsr_monitor *monitor, void *userdata) {
- const MonitorOutputCallbackUserdata *options = (MonitorOutputCallbackUserdata*)userdata;
- vec2i monitor_position = monitor->pos;
- vec2i monitor_size = monitor->size;
- if(gsr_window_get_display_server(options->window) == GSR_DISPLAY_SERVER_WAYLAND) {
- gsr_monitor_rotation monitor_rotation = GSR_MONITOR_ROT_0;
- drm_monitor_get_display_server_data(options->window, monitor, &monitor_rotation, &monitor_position);
- if(monitor_rotation == GSR_MONITOR_ROT_90 || monitor_rotation == GSR_MONITOR_ROT_270)
- std::swap(monitor_size.x, monitor_size.y);
- }
- fprintf(stderr, " \"%.*s\" (%dx%d+%d+%d)\n", monitor->name_len, monitor->name, monitor_size.x, monitor_size.y, monitor_position.x, monitor_position.y);
-}
-
-typedef struct {
- char *output_name;
-} FirstOutputCallback;
-
-static void get_first_output_callback(const gsr_monitor *monitor, void *userdata) {
- FirstOutputCallback *data = (FirstOutputCallback*)userdata;
- if(!data->output_name)
- data->output_name = strdup(monitor->name);
-}
-
-typedef struct {
- gsr_window *window;
- vec2i position;
- char *output_name;
- vec2i monitor_pos;
- vec2i monitor_size;
- double monitor_scale_inverted;
-} MonitorByPositionCallback;
-
-static void get_monitor_by_position_callback(const gsr_monitor *monitor, void *userdata) {
- MonitorByPositionCallback *data = (MonitorByPositionCallback*)userdata;
-
- const vec2i monitor_position = monitor->logical_pos;
- const vec2i monitor_size = monitor->size;
- const vec2i monitor_logical_size = monitor->logical_size;
-
- if(!data->output_name && data->position.x >= monitor_position.x && data->position.x <= monitor_position.x + monitor_logical_size.x
- && data->position.y >= monitor_position.y && data->position.y <= monitor_position.y + monitor_logical_size.y)
- {
- data->output_name = strdup(monitor->name);
- data->monitor_pos = monitor_position;
- data->monitor_size = monitor_size;
- data->monitor_scale_inverted = (double)monitor_size.x / (double)monitor_logical_size.x;
- }
-}
-
/* 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. */
@@ -363,41 +312,7 @@ struct RecordingStartResult {
std::vector<RecordingStartAudio> audio_inputs;
};
-typedef enum {
- VVEC2I_TYPE_PIXELS,
- VVEC2I_TYPE_SCALAR
-} vvec2i_type;
-
-typedef struct {
- int x, y;
- vvec2i_type x_type;
- vvec2i_type y_type;
-} vvec2i;
-
-struct CaptureSource {
- std::string name;
- CaptureSourceType type = GSR_CAPTURE_SOURCE_TYPE_WINDOW;
- gsr_capture_alignment halign = GSR_CAPTURE_ALIGN_CENTER;
- gsr_capture_alignment valign = GSR_CAPTURE_ALIGN_CENTER;
- gsr_capture_v4l2_pixfmt v4l2_pixfmt = GSR_CAPTURE_V4L2_PIXFMT_AUTO;
- uint32_t flip = GSR_FLIP_NONE;
- vvec2i pos = {0, 0, VVEC2I_TYPE_PIXELS, VVEC2I_TYPE_PIXELS};
- vvec2i size = {100, 100, VVEC2I_TYPE_SCALAR, VVEC2I_TYPE_SCALAR};
- vec2i region_pos = {0, 0};
- vec2i region_size = {0, 0};
- bool region_set = false;
- int64_t window_id = 0;
- int camera_fps = 0;
- vec2i camera_resolution = {0, 0};
-};
-
-struct VideoSource {
- gsr_capture *capture;
- gsr_capture_metadata metadata;
- CaptureSource *capture_source;
-};
-
-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) {
+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, gsr_video_sources *video_sources) {
AVFormatContext *av_format_context;
avformat_alloc_output_context2(&av_format_context, nullptr, arg_parser.settings.container_format, filename);
set_format_context_options(av_format_context);
@@ -437,7 +352,8 @@ static RecordingStartResult start_recording_create_streams(const char *filename,
return result;
}
- for(VideoSource &video_source : video_sources) {
+ for(size_t video_source_index = 0; video_source_index < video_sources->num_items; ++video_source_index) {
+ gsr_video_source &video_source = video_sources->items[video_source_index];
if(hdr && add_hdr_metadata_to_video_stream(video_source.capture, video_stream))
break;
}
@@ -494,7 +410,7 @@ struct AudioPtsOffset {
int stream_index = 0;
};
-static bool save_replay_async(AVCodecContext *video_codec_context, int video_stream_index, const std::vector<AudioTrack> &audio_tracks, gsr_encoder *encoder, const args_parser &arg_parser, const std::string &file_extension, bool date_folders, bool hdr, std::vector<VideoSource> &video_sources, int current_save_replay_seconds) {
+static bool save_replay_async(AVCodecContext *video_codec_context, int video_stream_index, const std::vector<AudioTrack> &audio_tracks, gsr_encoder *encoder, const args_parser &arg_parser, const std::string &file_extension, bool date_folders, bool hdr, gsr_video_sources *video_sources, int current_save_replay_seconds) {
if(save_replay_thread.valid())
return true;
@@ -1103,262 +1019,7 @@ static void list_monitors_command(void *userdata) {
_exit(0);
}
-static std::string validate_monitor_get_valid(const gsr_egl *egl, const char* window) {
- const bool is_x11 = gsr_window_get_display_server(egl->window) == GSR_DISPLAY_SERVER_X11;
- const gsr_connection_type connection_type = is_x11 ? GSR_CONNECTION_X11 : GSR_CONNECTION_DRM;
- const bool capture_use_drm = monitor_capture_use_drm(egl->window, egl->gpu_info.vendor);
-
- std::string capture_source_result = window;
- if(strcmp(capture_source_result.c_str(), "screen") == 0) {
- FirstOutputCallback data;
- data.output_name = NULL;
- for_each_active_monitor_output(egl->window, egl->card_path, connection_type, get_first_output_callback, &data);
-
- if(data.output_name) {
- capture_source_result = data.output_name;
- free(data.output_name);
- } else {
- gsr_log(GSR_LOG_LEVEL_ERROR, "no usable output found");
- _exit(51);
- }
- } else if(capture_use_drm || (strcmp(capture_source_result.c_str(), "screen-direct") != 0 && strcmp(capture_source_result.c_str(), "screen-direct-force") != 0)) {
- gsr_monitor gmon;
- if(!get_monitor_by_name(egl, connection_type, capture_source_result.c_str(), &gmon)) {
- gsr_log(GSR_LOG_LEVEL_ERROR, "display \"%s\" not found, expected one of:", capture_source_result.c_str());
- fprintf(stderr, " \"screen\"\n");
- if(!capture_use_drm)
- fprintf(stderr, " \"screen-direct\"\n");
-
- MonitorOutputCallbackUserdata userdata;
- userdata.window = egl->window;
- for_each_active_monitor_output(egl->window, egl->card_path, connection_type, monitor_output_callback_print, &userdata);
- _exit(51);
- }
- }
- return capture_source_result;
-}
-
-static std::string get_monitor_by_region_center(const gsr_egl *egl, vec2i region_position, vec2i region_size, vec2i *monitor_pos, vec2i *monitor_size, double *monitor_scale_inverted) {
- const bool is_x11 = gsr_window_get_display_server(egl->window) == GSR_DISPLAY_SERVER_X11;
- const gsr_connection_type connection_type = is_x11 ? GSR_CONNECTION_X11 : GSR_CONNECTION_WAYLAND;
-
- MonitorByPositionCallback data;
- data.window = egl->window;
- data.position = { region_position.x + region_size.x / 2, region_position.y + region_size.y / 2 };
- data.output_name = NULL;
- data.monitor_pos = {0, 0};
- data.monitor_size = {0, 0};
- data.monitor_scale_inverted = 1.0;
- for_each_active_monitor_output(egl->window, egl->card_path, connection_type, get_monitor_by_position_callback, &data);
-
- std::string result;
- if(data.output_name) {
- result = data.output_name;
- free(data.output_name);
- }
- *monitor_pos = data.monitor_pos;
- *monitor_size = data.monitor_size;
- *monitor_scale_inverted = data.monitor_scale_inverted;
- return result;
-}
-
-static gsr_kms_client kms_client;
-static bool kms_client_initialized = false;
-static gsr_kms_response kms_response;
-static gsr_kde_night_light *kde_night_light = nullptr;
-static bool kde_night_light_initialized = false;
-
-static gsr_cursor x11_cursor;
-static Display *x11_cursor_display = NULL;
-
-static gsr_capture* create_monitor_capture(const args_parser &arg_parser, gsr_egl *egl, const CaptureSource &capture_source, bool prefer_ximage) {
- if(gsr_window_get_display_server(egl->window) == GSR_DISPLAY_SERVER_X11 && prefer_ximage) {
- gsr_capture_ximage_params ximage_params;
- memset(&ximage_params, 0, sizeof(ximage_params));
- 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.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);
- }
-
- if(monitor_capture_use_drm(egl->window, egl->gpu_info.vendor)) {
- if(!kms_client_initialized) {
- kms_client_initialized = true;
- const int kms_init_res = gsr_kms_client_init(&kms_client, egl->card_path);
- if(kms_init_res != 0)
- _exit(kms_init_res < 0 ? 1 : kms_init_res);
- }
-
- if(!kde_night_light_initialized && gsr_window_get_display_server(egl->window) == GSR_DISPLAY_SERVER_WAYLAND) {
- kde_night_light_initialized = true;
- kde_night_light = gsr_kde_night_light_create();
- }
-
- gsr_capture_kms_params kms_params;
- memset(&kms_params, 0, sizeof(kms_params));
- kms_params.egl = egl;
- kms_params.x11_cursor = &x11_cursor;
- 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.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);
- } else {
- const char *capture_source_real = capture_source.name.c_str();
- const bool direct_capture = strcmp(capture_source.name.c_str(), "screen-direct") == 0 || strcmp(capture_source.name.c_str(), "screen-direct-force") == 0;
- if(direct_capture) {
- capture_source_real = "screen";
- gsr_log(GSR_LOG_LEVEL_WARNING, "%s capture option is not recommended unless you use G-SYNC as Nvidia has driver issues that can cause your system or games to freeze/crash.", capture_source.name.c_str());
- }
-
- gsr_capture_nvfbc_params nvfbc_params;
- memset(&nvfbc_params, 0, sizeof(nvfbc_params));
- nvfbc_params.egl = egl;
- nvfbc_params.display_to_capture = capture_source_real;
- nvfbc_params.fps = arg_parser.settings.fps;
- nvfbc_params.direct_capture = direct_capture;
- 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);
- }
-}
-
-static void monitor_output_callback_print_region(const gsr_monitor *monitor, void *userdata) {
- const vec2i monitor_position = monitor->logical_pos;
- const vec2i monitor_size = monitor->logical_size;
- fprintf(stderr, " \"%.*s\" (%dx%d+%d+%d)\n", monitor->name_len, monitor->name, monitor_size.x, monitor_size.y, monitor_position.x, monitor_position.y);
-}
-
-static std::string region_get_data(gsr_egl *egl, vec2i *region_size, vec2i *region_position) {
- vec2i monitor_pos = {0, 0};
- vec2i monitor_size = {0, 0};
- double monitor_scale_inverted = 1.0;
- std::string window = get_monitor_by_region_center(egl, *region_position, *region_size, &monitor_pos, &monitor_size, &monitor_scale_inverted);
- if(window.empty()) {
- const bool is_x11 = gsr_window_get_display_server(egl->window) == GSR_DISPLAY_SERVER_X11;
- const gsr_connection_type connection_type = is_x11 ? GSR_CONNECTION_X11 : GSR_CONNECTION_WAYLAND;
- gsr_log(GSR_LOG_LEVEL_ERROR, "the region %dx%d+%d+%d doesn't match any monitor. Available monitors and their regions:", region_size->x, region_size->y, region_position->x, region_position->y);
-
- MonitorOutputCallbackUserdata userdata;
- userdata.window = egl->window;
- for_each_active_monitor_output(egl->window, egl->card_path, connection_type, monitor_output_callback_print_region, &userdata);
- _exit(51);
- }
-
- // Capture whole monitor when region size is set to 0x0
- if(region_size->x == 0 && region_size->y == 0) {
- region_position->x = 0;
- region_position->y = 0;
- } else {
- region_position->x -= monitor_pos.x;
- region_position->y -= monitor_pos.y;
- // Match drm plane coordinate space (1x scaling) to wayland coordinate space (which may have scaling set by user)
- region_position->x *= monitor_scale_inverted;
- region_position->y *= monitor_scale_inverted;
-
- region_size->x *= monitor_scale_inverted;
- region_size->y *= monitor_scale_inverted;
- }
- return window;
-}
-
-static gsr_capture* create_capture_impl(const args_parser &arg_parser, gsr_egl *egl, CaptureSource &capture_source, bool prefer_ximage) {
- bool follow_focused = false;
- const bool wayland = gsr_window_get_display_server(egl->window) == GSR_DISPLAY_SERVER_WAYLAND;
-
- gsr_capture *capture = nullptr;
- if(capture_source.type == GSR_CAPTURE_SOURCE_TYPE_FOCUSED_WINDOW) {
- if(wayland) {
- gsr_log(GSR_LOG_LEVEL_ERROR, "GPU Screen Recorder window capture only works in a pure X11 session. Xwayland is not supported. You can record a monitor instead on wayland");
- _exit(2);
- }
-
- 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);
- }
-
- follow_focused = true;
- } else if(capture_source.type == GSR_CAPTURE_SOURCE_TYPE_PORTAL) {
-#ifdef GSR_PORTAL
- // Desktop portal capture on x11 doesn't seem to be hardware accelerated
- if(!wayland) {
- gsr_log(GSR_LOG_LEVEL_ERROR, "desktop portal capture is not supported on X11");
- _exit(1);
- }
-
- gsr_capture_portal_params portal_params;
- memset(&portal_params, 0, sizeof(portal_params));
- portal_params.egl = egl;
- 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);
-#else
- gsr_log(GSR_LOG_LEVEL_ERROR, "option '-w portal' used but GPU Screen Recorder was compiled without desktop portal support. Please recompile GPU Screen recorder with the -Dportal=true option");
- _exit(2);
-#endif
- } else if(capture_source.type == GSR_CAPTURE_SOURCE_TYPE_REGION) {
- capture_source.name = region_get_data(egl, &capture_source.region_size, &capture_source.region_pos);
- capture = create_monitor_capture(arg_parser, egl, capture_source, prefer_ximage);
- if(!capture)
- _exit(1);
- } else if(capture_source.type == GSR_CAPTURE_SOURCE_TYPE_MONITOR) {
- capture_source.name = validate_monitor_get_valid(egl, capture_source.name.c_str());
- capture = create_monitor_capture(arg_parser, egl, capture_source, prefer_ximage);
- if(!capture)
- _exit(1);
- } else if(capture_source.type == GSR_CAPTURE_SOURCE_TYPE_V4L2) {
- gsr_capture_v4l2_params v4l2_params;
- memset(&v4l2_params, 0, sizeof(v4l2_params));
- v4l2_params.egl = egl;
- 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;
- v4l2_params.camera_resolution.width = capture_source.camera_resolution.x;
- v4l2_params.camera_resolution.height = capture_source.camera_resolution.y;
- capture = gsr_capture_v4l2_create(&v4l2_params);
- if(!capture)
- _exit(1);
- } else {
- if(wayland) {
- gsr_log(GSR_LOG_LEVEL_ERROR, "GPU Screen Recorder window capture only works in a pure X11 session. Xwayland is not supported. You can record a monitor instead on wayland or use -w portal option which supports window capture if your wayland compositor supports window capture");
- _exit(2);
- }
- }
-
- if(!capture) {
- gsr_capture_xcomposite_params xcomposite_params;
- memset(&xcomposite_params, 0, sizeof(xcomposite_params));
- xcomposite_params.egl = 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.settings.record_cursor;
- xcomposite_params.output_resolution = arg_parser.settings.output_resolution;
- capture = gsr_capture_xcomposite_create(&xcomposite_params);
- if(!capture)
- _exit(1);
- }
-
- return capture;
-}
+static gsr_capture_deps capture_deps;
static gsr_color_range image_format_to_color_range(gsr_image_format image_format, int image_quality) {
switch(image_format) {
@@ -1384,115 +1045,6 @@ static int video_quality_to_image_quality_value(gsr_video_quality video_quality)
return 90;
}
-static bool any_video_sources_uses_external_image(std::vector<VideoSource> &video_sources) {
- for(VideoSource &video_source : video_sources) {
- if(gsr_capture_uses_external_image(video_source.capture))
- return true;
- }
- return false;
-}
-
-static std::vector<VideoSource> create_video_sources(const args_parser &arg_parser, gsr_egl *egl, bool prefer_ximage, std::vector<CaptureSource> &capture_sources, vec2i &video_size) {
- std::vector<VideoSource> video_sources;
- video_sources.reserve(capture_sources.size());
-
- for(CaptureSource &capture_source : capture_sources) {
- gsr_capture_metadata capture_metadata;
- memset(&capture_metadata, 0, sizeof(capture_metadata));
- 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;
- video_sources.push_back(VideoSource{create_capture_impl(arg_parser, egl, capture_source, prefer_ximage), capture_metadata, &capture_source});
- }
-
- for(VideoSource &video_source : video_sources) {
- int capture_result = gsr_capture_start(video_source.capture, &video_source.metadata);
- if(capture_result != 0) {
- gsr_log(GSR_LOG_LEVEL_ERROR, "gsr_capture_start failed");
- _exit(capture_result);
- }
- }
-
- vec2i start_pos = {99999, 99999};
- vec2i end_pos = {-99999, -99999};
- for(const VideoSource &video_source : video_sources) {
- // TODO: Skip scalar positions for now, but this should be handled in a better way.
- // Maybe handle scalars at the next loop by multiplying video size by the scalar.
- if(video_source.capture_source->pos.x_type == VVEC2I_TYPE_SCALAR || video_source.capture_source->pos.y_type == VVEC2I_TYPE_SCALAR
- || (video_source.capture_source->size.x_type == VVEC2I_TYPE_SCALAR && video_source.capture_source->size.x != 100)
- || (video_source.capture_source->size.y_type == VVEC2I_TYPE_SCALAR && video_source.capture_source->size.y != 100))
- {
- continue;
- }
- const vec2i video_source_start_pos = {video_source.capture_source->pos.x, video_source.capture_source->pos.y};
- const vec2i video_source_end_pos = {video_source_start_pos.x + video_source.metadata.video_size.x, video_source_start_pos.y + video_source.metadata.video_size.y};
-
- start_pos.x = std::min(start_pos.x, video_source_start_pos.x);
- start_pos.y = std::min(start_pos.y, video_source_start_pos.y);
-
- end_pos.x = std::max(end_pos.x, video_source_end_pos.x);
- end_pos.y = std::max(end_pos.y, video_source_end_pos.y);
- }
-
- video_size.x = std::max(0, end_pos.x - start_pos.x);
- video_size.y = std::max(0, end_pos.y - start_pos.y);
-
- for(VideoSource &video_source : video_sources) {
- video_source.metadata.video_size = video_size;
- }
-
- return video_sources;
-}
-
-static void video_sources_update_with_real_video_size(std::vector<CaptureSource> &capture_sources, std::vector<VideoSource> &video_sources, vec2i video_size) {
- assert(capture_sources.size() == video_sources.size());
- for(size_t i = 0; i < capture_sources.size(); ++i) {
- CaptureSource &capture_source = capture_sources[i];
- VideoSource &video_source = video_sources[i];
-
- video_source.metadata.recording_size = video_source.metadata.video_size;
- // TODO: What if this updated resolution is above max resolution?
- video_source.metadata.video_size = video_size;
-
- if(capture_source.pos.x != 0 || capture_source.pos.y != 0) {
- video_source.metadata.position.x = capture_source.pos.x;
- video_source.metadata.position.y = capture_source.pos.y;
-
- if(capture_source.pos.x_type == VVEC2I_TYPE_SCALAR)
- video_source.metadata.position.x = video_source.metadata.video_size.x * ((double)video_source.metadata.position.x / 100.0);
-
- if(capture_source.pos.y_type == VVEC2I_TYPE_SCALAR)
- video_source.metadata.position.y = video_source.metadata.video_size.y * ((double)video_source.metadata.position.y / 100.0);
- }
-
- if(capture_source.size.x != 0 || capture_source.size.y != 0) {
- video_source.metadata.recording_size.x = capture_source.size.x;
- video_source.metadata.recording_size.y = capture_source.size.y;
-
- if(capture_source.size.x_type == VVEC2I_TYPE_SCALAR)
- video_source.metadata.recording_size.x = video_source.metadata.video_size.x * ((double)video_source.metadata.recording_size.x / 100.0);
-
- if(capture_source.size.y_type == VVEC2I_TYPE_SCALAR)
- video_source.metadata.recording_size.y = video_source.metadata.video_size.y * ((double)video_source.metadata.recording_size.y / 100.0);
- }
- }
-}
-
-static void gsr_capture_kms_cleanup_kms_fds() {
- for(int i = 0; i < kms_response.num_items; ++i) {
- for(int j = 0; j < kms_response.items[i].num_dma_bufs; ++j) {
- gsr_kms_response_dma_buf *dma_buf = &kms_response.items[i].dma_buf[j];
- if(dma_buf->fd > 0) {
- close(dma_buf->fd);
- dma_buf->fd = -1;
- }
- }
- kms_response.items[i].num_dma_bufs = 0;
- }
- kms_response.num_items = 0;
-}
-
static void load_plugins(gsr_plugins *plugins, args_parser &arg_parser, gsr_egl *egl, vec2i video_size) {
const Arg *plugin_arg = args_parser_get_arg(&arg_parser, "-p");
assert(plugin_arg);
@@ -1520,14 +1072,18 @@ 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) {
+static void capture_image_to_file(args_parser &arg_parser, gsr_egl *egl, gsr_window *window, gsr_image_format image_format, gsr_capture_sources *capture_sources) {
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.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);
- video_sources_update_with_real_video_size(capture_sources, video_sources, video_size);
+ gsr_video_sources video_sources_data;
+ const int video_sources_result = gsr_video_sources_create(&video_sources_data, &arg_parser.settings, egl, &capture_deps, true, capture_sources, &video_size);
+ if(video_sources_result != GSR_ERROR_OK)
+ _exit(gsr_error_to_exit_code(video_sources_result));
+ gsr_video_sources *video_sources = &video_sources_data;
+ gsr_video_sources_update_with_real_video_size(video_sources, video_size);
const Arg *plugin_arg = args_parser_get_arg(&arg_parser, "-p");
assert(plugin_arg);
@@ -1548,7 +1104,7 @@ static void capture_image_to_file(args_parser &arg_parser, gsr_egl *egl, gsr_win
memset(&color_conversion_params, 0, sizeof(color_conversion_params));
color_conversion_params.color_range = color_range;
color_conversion_params.egl = egl;
- color_conversion_params.load_external_image_shader = any_video_sources_uses_external_image(video_sources);
+ color_conversion_params.load_external_image_shader = gsr_video_sources_uses_external_image(video_sources);
color_conversion_params.destination_textures[0] = image_writer.texture;
color_conversion_params.destination_textures_size[0] = video_size;
@@ -1570,26 +1126,25 @@ 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.settings.record_cursor)
- gsr_cursor_on_event(&x11_cursor, gsr_window_get_event_data(window));
+ if(capture_deps.x11_cursor_display && arg_parser.settings.record_cursor)
+ gsr_cursor_on_event(&capture_deps.x11_cursor, gsr_window_get_event_data(window));
- for(VideoSource &video_source : video_sources) {
+ for(size_t video_source_index = 0; video_source_index < video_sources->num_items; ++video_source_index) {
+ gsr_video_source &video_source = video_sources->items[video_source_index];
gsr_capture_on_event(video_source.capture, egl);
}
}
- if(x11_cursor_display && arg_parser.settings.record_cursor)
- gsr_cursor_tick(&x11_cursor, DefaultRootWindow(x11_cursor_display));
+ if(capture_deps.x11_cursor_display && arg_parser.settings.record_cursor)
+ gsr_cursor_tick(&capture_deps.x11_cursor, DefaultRootWindow(capture_deps.x11_cursor_display));
- gsr_capture_kms_cleanup_kms_fds();
+ gsr_capture_deps_cleanup_kms_fds(&capture_deps);
- if(kms_client_initialized) {
- if(gsr_kms_client_get_kms(&kms_client, &kms_response) != 0)
- gsr_log(GSR_LOG_LEVEL_ERROR, "failed to get kms, error: %d (%s)", kms_response.result, kms_response.err_msg);
- }
+ gsr_capture_deps_update_kms(&capture_deps);
should_stop_error = false;
- for(VideoSource &video_source : video_sources) {
+ for(size_t video_source_index = 0; video_source_index < video_sources->num_items; ++video_source_index) {
+ gsr_video_source &video_source = video_sources->items[video_source_index];
gsr_capture_tick(video_source.capture);
if(gsr_capture_should_stop(video_source.capture, &should_stop_error)) {
running = 0;
@@ -1597,7 +1152,8 @@ static void capture_image_to_file(args_parser &arg_parser, gsr_egl *egl, gsr_win
}
}
- for(VideoSource &video_source : video_sources) {
+ for(size_t video_source_index = 0; video_source_index < video_sources->num_items; ++video_source_index) {
+ gsr_video_source &video_source = video_sources->items[video_source_index];
if(video_source.capture->pre_capture)
video_source.capture->pre_capture(video_source.capture, &video_source.metadata, output_color_conversion);
}
@@ -1608,14 +1164,15 @@ static void capture_image_to_file(args_parser &arg_parser, gsr_egl *egl, gsr_win
}
bool all_sources_captured = true;
- for(VideoSource &video_source : video_sources) {
+ for(size_t video_source_index = 0; video_source_index < video_sources->num_items; ++video_source_index) {
+ gsr_video_source &video_source = video_sources->items[video_source_index];
// It can fail, for example when capturing portal and the target is a monitor that hasn't been updated.
// This can also happen for example if the system suspends and the monitor to capture's framebuffer is gone, or if the target window disappeared.
if(gsr_capture_capture(video_source.capture, &video_source.metadata, output_color_conversion) != 0)
all_sources_captured = false;
}
- gsr_capture_kms_cleanup_kms_fds();
+ gsr_capture_deps_cleanup_kms_fds(&capture_deps);
if(all_sources_captured)
break;
@@ -1646,9 +1203,7 @@ static void capture_image_to_file(args_parser &arg_parser, gsr_egl *egl, gsr_win
gsr_plugins_deinit(&plugins);
gsr_image_writer_deinit(&image_writer);
- for(VideoSource &video_source : video_sources) {
- gsr_capture_destroy(video_source.capture);
- }
+ gsr_video_sources_deinit(video_sources);
_exit(should_stop_error ? 3 : 0);
}
@@ -1782,293 +1337,6 @@ static std::vector<MergedAudioInputs> parse_audio_inputs(const gsr_audio_devices
return requested_audio_inputs;
}
-static bool is_hex_num(char c) {
- return (c >= 'A' && c <= 'F') || (c >= 'a' && c <= 'f') || (c >= '0' && c <= '9');
-}
-
-static bool contains_non_hex_number(const char *str) {
- bool hex_start = false;
- size_t len = strlen(str);
- if(len >= 2 && memcmp(str, "0x", 2) == 0) {
- str += 2;
- len -= 2;
- hex_start = true;
- }
-
- bool is_hex = false;
- for(size_t i = 0; i < len; ++i) {
- char c = str[i];
- if(c == '\0')
- return false;
- if(!is_hex_num(c))
- return true;
- if((c >= 'A' && c <= 'F') || (c >= 'a' && c <= 'f'))
- is_hex = true;
- }
-
- return is_hex && !hex_start;
-}
-
-static void capture_source_type_from_string(const char *capture_source_str, size_t size, CaptureSource &capture_source) {
- char capture_source_str_n[64];
- snprintf(capture_source_str_n, sizeof(capture_source_str_n), "%.*s", (int)size, capture_source_str);
-
- if(size == 7 && memcmp(capture_source_str_n, "focused", 7) == 0) {
- capture_source.type = GSR_CAPTURE_SOURCE_TYPE_FOCUSED_WINDOW;
- } else if(size == 6 && memcmp(capture_source_str_n, "portal", 6) == 0) {
- capture_source.type = GSR_CAPTURE_SOURCE_TYPE_PORTAL;
- } else if(size == 6 && memcmp(capture_source_str_n, "region", 6) == 0) {
- capture_source.type = GSR_CAPTURE_SOURCE_TYPE_REGION;
- } else if(size >= 10 && memcmp(capture_source_str_n, "/dev/video", 10) == 0) {
- capture_source.type = GSR_CAPTURE_SOURCE_TYPE_V4L2;
- } else if(sscanf(capture_source_str_n, "%dx%d+%d+%d", &capture_source.region_size.x, &capture_source.region_size.y, &capture_source.region_pos.x, &capture_source.region_pos.y) == 4) {
- capture_source.type = GSR_CAPTURE_SOURCE_TYPE_REGION;
- capture_source.region_set = true;
- } else if(contains_non_hex_number(capture_source_str_n)) {
- capture_source.type = GSR_CAPTURE_SOURCE_TYPE_MONITOR;
- } else {
- capture_source.type = GSR_CAPTURE_SOURCE_TYPE_WINDOW;
- }
-}
-
-static bool string_to_capture_alignment(const char *str, size_t len, gsr_capture_alignment *alignment) {
- if(len == 5 && memcmp(str, "start", 5) == 0) {
- *alignment = GSR_CAPTURE_ALIGN_START;
- return true;
- } else if(len == 6 && memcmp(str, "center", 6) == 0) {
- *alignment = GSR_CAPTURE_ALIGN_CENTER;
- return true;
- } else if(len == 3 && memcmp(str, "end", 3) == 0) {
- *alignment = GSR_CAPTURE_ALIGN_END;
- return true;
- } else {
- return false;
- }
-}
-
-static bool string_to_v4l2_pixfmt(const char *str, size_t len, gsr_capture_v4l2_pixfmt *pixfmt) {
- if(len == 4 && memcmp(str, "auto", 4) == 0) {
- *pixfmt = GSR_CAPTURE_V4L2_PIXFMT_AUTO;
- return true;
- } else if(len == 4 && memcmp(str, "yuyv", 4) == 0) {
- *pixfmt = GSR_CAPTURE_V4L2_PIXFMT_YUYV;
- return true;
- } else if(len == 5 && memcmp(str, "mjpeg", 5) == 0) {
- *pixfmt = GSR_CAPTURE_V4L2_PIXFMT_MJPEG;
- return true;
- } else {
- return false;
- }
-}
-
-static bool string_to_bool(const char *str, size_t len, bool *value) {
- if(len == 4 && memcmp(str, "true", 4) == 0) {
- *value = true;
- return true;
- } else if(len == 5 && memcmp(str, "false", 5) == 0) {
- *value = false;
- return true;
- } else {
- return false;
- }
-}
-
-struct ParseCaptureSourceOptionsUserdata {
- CaptureSource *capture_source;
- bool is_first_column;
-};
-
-static bool parse_capture_source_options_callback(const char *sub, size_t size, void *userdata) {
- ParseCaptureSourceOptionsUserdata *parse_userdata = (ParseCaptureSourceOptionsUserdata*)userdata;
- CaptureSource &capture_source = *parse_userdata->capture_source;
- if(size == 0)
- return true;
-
- if(parse_userdata->is_first_column) {
- parse_userdata->is_first_column = false;
- return true;
- }
-
- if(gsr_string_starts_with(sub, size, "x=")) {
- capture_source.pos.x_type = sub[size - 1] == '%' ? VVEC2I_TYPE_SCALAR : VVEC2I_TYPE_PIXELS;
- sub += 2;
- size -= 2;
- if(!gsr_string_to_int(sub, size, &capture_source.pos.x)) {
- gsr_log(GSR_LOG_LEVEL_ERROR, "invalid capture target value for option x: \"%.*s\", expected a number", (int)size, sub);
- _exit(1);
- }
- } else if(gsr_string_starts_with(sub, size, "y=")) {
- capture_source.pos.y_type = sub[size - 1] == '%' ? VVEC2I_TYPE_SCALAR : VVEC2I_TYPE_PIXELS;
- sub += 2;
- size -= 2;
- if(!gsr_string_to_int(sub, size, &capture_source.pos.y)) {
- gsr_log(GSR_LOG_LEVEL_ERROR, "invalid capture target value for option y: \"%.*s\", expected a number", (int)size, sub);
- _exit(1);
- }
-
- } else if(gsr_string_starts_with(sub, size, "width=")) {
- capture_source.size.x_type = sub[size - 1] == '%' ? VVEC2I_TYPE_SCALAR : VVEC2I_TYPE_PIXELS;
- sub += 6;
- size -= 6;
- if(!gsr_string_to_int(sub, size, &capture_source.size.x)) {
- gsr_log(GSR_LOG_LEVEL_ERROR, "invalid capture target value for option width: \"%.*s\", expected a number", (int)size, sub);
- _exit(1);
- }
- } else if(gsr_string_starts_with(sub, size, "height=")) {
- capture_source.size.y_type = sub[size - 1] == '%' ? VVEC2I_TYPE_SCALAR : VVEC2I_TYPE_PIXELS;
- sub += 7;
- size -= 7;
- if(!gsr_string_to_int(sub, size, &capture_source.size.y)) {
- gsr_log(GSR_LOG_LEVEL_ERROR, "invalid capture target value for option height: \"%.*s\", expected a number", (int)size, sub);
- _exit(1);
- }
- } else if(gsr_string_starts_with(sub, size, "halign=")) {
- sub += 7;
- size -= 7;
- if(!string_to_capture_alignment(sub, size, &capture_source.halign)) {
- gsr_log(GSR_LOG_LEVEL_ERROR, "invalid capture target value for option halign: \"%.*s\", expected a \"start\", \"center\" or \"end\"", (int)size, sub);
- _exit(1);
- }
- } else if(gsr_string_starts_with(sub, size, "valign=")) {
- sub += 7;
- size -= 7;
- if(!string_to_capture_alignment(sub, size, &capture_source.valign)) {
- gsr_log(GSR_LOG_LEVEL_ERROR, "invalid capture target value for option valign: \"%.*s\", expected a \"start\", \"center\" or \"end\"", (int)size, sub);
- _exit(1);
- }
- } else if(gsr_string_starts_with(sub, size, "pixfmt=")) {
- sub += 7;
- size -= 7;
- if(!string_to_v4l2_pixfmt(sub, size, &capture_source.v4l2_pixfmt)) {
- gsr_log(GSR_LOG_LEVEL_ERROR, "invalid v4l2 pixfmt value for option pixfmt: \"%.*s\", expected a \"auto\", \"yuyv\" or \"mjpeg\"", (int)size, sub);
- _exit(1);
- }
- } else if(gsr_string_starts_with(sub, size, "hflip=")) {
- sub += 6;
- size -= 6;
- bool hflip = false;
- if(!string_to_bool(sub, size, &hflip)) {
- gsr_log(GSR_LOG_LEVEL_ERROR, "invalid bool value for option hflip: \"%.*s\", expected a \"true\" or \"false\"", (int)size, sub);
- _exit(1);
- }
-
- if(hflip)
- capture_source.flip |= GSR_FLIP_HORIZONTAL;
- } else if(gsr_string_starts_with(sub, size, "vflip=")) {
- sub += 6;
- size -= 6;
- bool vflip = false;
- if(!string_to_bool(sub, size, &vflip)) {
- gsr_log(GSR_LOG_LEVEL_ERROR, "invalid bool value for option vflip: \"%.*s\", expected a \"true\" or \"false\"", (int)size, sub);
- _exit(1);
- }
-
- if(vflip)
- capture_source.flip |= GSR_FLIP_VERTICAL;
- } else if(gsr_string_starts_with(sub, size, "camera_fps=")) {
- sub += 11;
- size -= 11;
- if(!gsr_string_to_int(sub, size, &capture_source.camera_fps)) {
- gsr_log(GSR_LOG_LEVEL_ERROR, "invalid capture target value for option camera_fps: \"%.*s\", expected a number", (int)size, sub);
- _exit(1);
- }
- } else if(gsr_string_starts_with(sub, size, "camera_width=")) {
- sub += 13;
- size -= 13;
- if(!gsr_string_to_int(sub, size, &capture_source.camera_resolution.x)) {
- gsr_log(GSR_LOG_LEVEL_ERROR, "invalid capture target value for option camera_width: \"%.*s\", expected a number", (int)size, sub);
- _exit(1);
- }
- } else if(gsr_string_starts_with(sub, size, "camera_height=")) {
- sub += 14;
- size -= 14;
- if(!gsr_string_to_int(sub, size, &capture_source.camera_resolution.y)) {
- gsr_log(GSR_LOG_LEVEL_ERROR, "invalid capture target value for option camera_height: \"%.*s\", expected a number", (int)size, sub);
- _exit(1);
- }
- } else {
- gsr_log(GSR_LOG_LEVEL_ERROR, "invalid capture target option \"%.*s\", expected x, y, width, height, halign, valign, pixfmt, hflip, vflip, camera_fps, camera_width or camera_height", (int)size, sub);
- _exit(1);
- }
-
- return true;
-}
-
-static void parse_capture_source_options(const std::string &capture_source_str, CaptureSource &capture_source) {
- ParseCaptureSourceOptionsUserdata userdata;
- userdata.capture_source = &capture_source;
- userdata.is_first_column = true;
- gsr_string_split(capture_source_str.c_str(), ';', parse_capture_source_options_callback, &userdata);
-}
-
-struct ParseCaptureSourceArgUserdata {
- std::vector<CaptureSource> *requested_capture_sources;
- const args_parser *arg_parser;
- bool has_multiple_capture_sources;
-};
-
-static bool parse_capture_source_arg_callback(const char *sub, size_t size, void *userdata) {
- ParseCaptureSourceArgUserdata *parse_userdata = (ParseCaptureSourceArgUserdata*)userdata;
- if(size == 0)
- return true;
-
- const char *substr_start = sub;
- size_t capture_source_size = size;
- const char *capture_source_end = (const char*)memchr(sub, ';', size);
- if(capture_source_end)
- capture_source_size = capture_source_end - sub;
-
- CaptureSource capture_source;
- 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;
- sub += 8;
- capture_source_size -= 8;
- } else if(gsr_string_starts_with(sub, capture_source_size, "window:")) {
- capture_source.type = GSR_CAPTURE_SOURCE_TYPE_WINDOW;
- sub += 7;
- capture_source_size -= 7;
- } else if(gsr_string_starts_with(sub, capture_source_size, "v4l2:")) {
- capture_source.type = GSR_CAPTURE_SOURCE_TYPE_V4L2;
- sub += 5;
- capture_source_size -= 5;
- } else {
- capture_source_type_from_string(sub, capture_source_size, capture_source);
- }
-
- capture_source.name.assign(sub, capture_source_size);
-
- if(capture_source.type == GSR_CAPTURE_SOURCE_TYPE_WINDOW) {
- if(!gsr_string_to_int64(capture_source.name.c_str(), capture_source.name.size(), &capture_source.window_id)) {
- gsr_log(GSR_LOG_LEVEL_ERROR, "invalid window number %s", capture_source.name.c_str());
- args_parser_print_usage();
- _exit(1);
- }
- }
-
- if(parse_userdata->has_multiple_capture_sources) {
- capture_source.halign = GSR_CAPTURE_ALIGN_START;
- capture_source.valign = GSR_CAPTURE_ALIGN_START;
- capture_source.pos = {0, 0, VVEC2I_TYPE_PIXELS, VVEC2I_TYPE_PIXELS};
- }
-
- parse_capture_source_options(std::string(substr_start, size), capture_source);
- parse_userdata->requested_capture_sources->push_back(capture_source);
- return true;
-}
-
-static std::vector<CaptureSource> parse_capture_source_arg(const char *capture_source_arg, const args_parser &arg_parser) {
- std::vector<CaptureSource> requested_capture_sources;
- ParseCaptureSourceArgUserdata userdata;
- userdata.requested_capture_sources = &requested_capture_sources;
- userdata.arg_parser = &arg_parser;
- userdata.has_multiple_capture_sources = strchr(capture_source_arg, '|') != nullptr;
- gsr_string_split(capture_source_arg, '|', parse_capture_source_arg_callback, &userdata);
- return requested_capture_sources;
-}
-
static bool audio_inputs_has_app_audio(const std::vector<AudioInput> &audio_inputs) {
for(const auto &audio_input : audio_inputs) {
if(audio_input.type == AudioInputType::APPLICATION)
@@ -2278,54 +1546,22 @@ static void set_display_server_environment_variables() {
}
}
-static bool is_capturing_damage_tracked_target(const std::vector<CaptureSource> &capture_sources) {
- for(const CaptureSource &capture_source : capture_sources) {
- if(capture_source.type != GSR_CAPTURE_SOURCE_TYPE_V4L2)
- return true;
- }
- return false;
-}
-
-static bool is_capturing_type(const std::vector<CaptureSource> &capture_sources, CaptureSourceType target_type) {
- for(const CaptureSource &capture_source : capture_sources) {
- if(capture_source.type == target_type)
- return true;
- }
- return false;
-}
-
-static bool has_capture_source_with_region_set(const std::vector<CaptureSource> &capture_sources) {
- for(const CaptureSource &capture_source : capture_sources) {
- if(capture_source.type == GSR_CAPTURE_SOURCE_TYPE_REGION && capture_source.region_set)
- return true;
- }
- return false;
-}
-
-static bool is_capturing_monitor_or_region(const std::vector<CaptureSource> &capture_sources) {
- for(const CaptureSource &capture_source : capture_sources) {
- if(capture_source.type == GSR_CAPTURE_SOURCE_TYPE_MONITOR || capture_source.type == GSR_CAPTURE_SOURCE_TYPE_REGION)
- return true;
- }
- return false;
-}
-
-static void validate_args_with_capture_sources(args_parser &arg_parser, const std::vector<CaptureSource> &capture_sources) {
+static void validate_args_with_capture_sources(args_parser &arg_parser, const gsr_capture_sources *capture_sources) {
const Arg *output_resolution_arg = args_parser_get_arg(&arg_parser, "-s");
assert(output_resolution_arg);
const Arg *region_arg = args_parser_get_arg(&arg_parser, "-region");
assert(region_arg);
- if(is_capturing_type(capture_sources, GSR_CAPTURE_SOURCE_TYPE_FOCUSED_WINDOW) && output_resolution_arg->num_values == 0) {
+ if(gsr_capture_sources_has_type(capture_sources, GSR_CAPTURE_SOURCE_TYPE_FOCUSED_WINDOW) && output_resolution_arg->num_values == 0) {
gsr_log(GSR_LOG_LEVEL_ERROR, "option -s is required when using '-w focused' option");
args_parser_print_usage();
_exit(1);
}
- const bool is_capturing_region = is_capturing_type(capture_sources, GSR_CAPTURE_SOURCE_TYPE_REGION);
+ const bool is_capturing_region = gsr_capture_sources_has_type(capture_sources, GSR_CAPTURE_SOURCE_TYPE_REGION);
if(region_arg->num_values == 0) {
- if(is_capturing_region && !has_capture_source_with_region_set(capture_sources)) {
+ if(is_capturing_region && !gsr_capture_sources_has_region_set(capture_sources)) {
gsr_log(GSR_LOG_LEVEL_ERROR, "option -region is required when '-w region' is used");
args_parser_print_usage();
_exit(1);
@@ -2340,7 +1576,7 @@ static void validate_args_with_capture_sources(args_parser &arg_parser, const st
}
}
- if(!arg_parser.settings.restore_portal_session && is_capturing_type(capture_sources, GSR_CAPTURE_SOURCE_TYPE_PORTAL))
+ if(!arg_parser.settings.restore_portal_session && gsr_capture_sources_has_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");
}
@@ -2453,8 +1689,13 @@ int main(int argc, char **argv) {
setenv("AMD_DEBUG", "lowlatencyenc", true);
}
- std::vector<CaptureSource> capture_sources = parse_capture_source_arg(arg_parser.settings.capture_source, arg_parser);
- if(capture_sources.empty()) {
+ gsr_capture_sources capture_sources_data;
+ const int parse_capture_sources_result = gsr_capture_sources_parse(&capture_sources_data, arg_parser.settings.capture_source, arg_parser.settings.region_position, arg_parser.settings.region_size);
+ if(parse_capture_sources_result != GSR_ERROR_OK)
+ _exit(gsr_error_to_exit_code(parse_capture_sources_result));
+
+ gsr_capture_sources *capture_sources = &capture_sources_data;
+ if(capture_sources->num_items == 0) {
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();
_exit(1);
@@ -2507,7 +1748,7 @@ int main(int argc, char **argv) {
gsr_windowing windowing;
gsr_windowing_params windowing_params;
- windowing_params.monitor_capture = is_capturing_monitor_or_region(capture_sources);
+ windowing_params.monitor_capture = gsr_capture_sources_has_monitor_or_region(capture_sources);
windowing_params.gl_debug = arg_parser.settings.gl_debug;
windowing_params.listen_to_x11_events = true;
if(gsr_windowing_init(&windowing, &windowing_params) != GSR_ERROR_OK)
@@ -2516,7 +1757,7 @@ int main(int argc, char **argv) {
Display *dpy = windowing.display;
gsr_window *window = windowing.window;
- if(is_capturing_type(capture_sources, GSR_CAPTURE_SOURCE_TYPE_PORTAL)) {
+ if(gsr_capture_sources_has_type(capture_sources, GSR_CAPTURE_SOURCE_TYPE_PORTAL)) {
if(gsr_windowing_is_using_prime_run()) {
gsr_log(GSR_LOG_LEVEL_WARNING, "use of prime-run with -w portal option is currently not supported. Disabling prime-run");
gsr_windowing_disable_prime_run();
@@ -2546,12 +1787,7 @@ int main(int argc, char **argv) {
_exit(2);
}
- memset(&x11_cursor, 0, sizeof(x11_cursor));
- x11_cursor_display = NULL;
- 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);
- }
+ gsr_capture_deps_init_cursor(&capture_deps, &egl, arg_parser.settings.record_cursor);
// if(wayland && arg_parser.capture_source_type == GSR_CAPTURE_SOURCE_TYPE_MONITOR) {
// fprintf(stderr, "gsr warning: it's not possible to sync video to recorded monitor exactly on wayland when recording a monitor."
@@ -2604,7 +1840,12 @@ int main(int argc, char **argv) {
arg_parser.settings.audio_codec = select_audio_codec_with_fallback(arg_parser.settings.audio_codec, file_extension.c_str(), uses_amix);
vec2i video_size = {0, 0};
- std::vector<VideoSource> video_sources = create_video_sources(arg_parser, &egl, false, capture_sources, video_size);
+ gsr_video_sources video_sources_data;
+ const int video_sources_result = gsr_video_sources_create(&video_sources_data, &arg_parser.settings, &egl, &capture_deps, false, capture_sources, &video_size);
+ if(video_sources_result != GSR_ERROR_OK)
+ _exit(gsr_error_to_exit_code(video_sources_result));
+
+ gsr_video_sources *video_sources = &video_sources_data;
// (Some?) livestreaming services require at least one audio track to work.
// If not audio is provided then create one silent audio track.
@@ -2668,7 +1909,7 @@ int main(int argc, char **argv) {
video_size.x = video_codec_context->width;
video_size.y = video_codec_context->height;
- video_sources_update_with_real_video_size(capture_sources, video_sources, video_size);
+ gsr_video_sources_update_with_real_video_size(video_sources, video_size);
const Arg *plugin_arg = args_parser_get_arg(&arg_parser, "-p");
assert(plugin_arg);
@@ -2683,7 +1924,7 @@ int main(int argc, char **argv) {
memset(&color_conversion_params, 0, sizeof(color_conversion_params));
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);
+ color_conversion_params.load_external_image_shader = gsr_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);
gsr_color_conversion color_conversion;
@@ -3044,43 +2285,45 @@ int main(int argc, char **argv) {
bool use_damage_tracking = false;
gsr_damage damage;
memset(&damage, 0, sizeof(damage));
- if(arg_parser.settings.framerate_mode == GSR_FRAMERATE_MODE_CONTENT && is_capturing_damage_tracked_target(capture_sources)) {
+ if(arg_parser.settings.framerate_mode == GSR_FRAMERATE_MODE_CONTENT && gsr_capture_sources_has_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.settings.record_cursor);
+ gsr_damage_init(&damage, &egl, &capture_deps.x11_cursor, arg_parser.settings.record_cursor);
use_damage_tracking = true;
- for(const CaptureSource &capture_source : capture_sources) {
- switch(capture_source.type) {
+ for(size_t i = 0; i < capture_sources->num_items; ++i) {
+ const gsr_capture_source *capture_source = &capture_sources->items[i];
+ switch(capture_source->type) {
case GSR_CAPTURE_SOURCE_TYPE_WINDOW:
- gsr_damage_start_tracking_window(&damage, capture_source.window_id);
+ gsr_damage_start_tracking_window(&damage, capture_source->window_id);
break;
case GSR_CAPTURE_SOURCE_TYPE_MONITOR:
case GSR_CAPTURE_SOURCE_TYPE_REGION:
// TODO: When capturing a region only track damage in that region
- gsr_damage_start_tracking_monitor(&damage, capture_source.name.c_str());
+ gsr_damage_start_tracking_monitor(&damage, capture_source->name);
break;
default:
break;
}
}
- } else if(is_capturing_monitor_or_region(capture_sources)) {
+ } else if(gsr_capture_sources_has_monitor_or_region(capture_sources)) {
gsr_log(GSR_LOG_LEVEL_WARNING, "\"-fm content\" has no effect on Wayland when recording a monitor. Either record a monitor on X11 or capture with desktop portal instead (-w portal)");
}
}
while(running) {
while(gsr_window_process_event(window)) {
- if(x11_cursor_display && arg_parser.settings.record_cursor)
- gsr_cursor_on_event(&x11_cursor, gsr_window_get_event_data(window));
+ if(capture_deps.x11_cursor_display && arg_parser.settings.record_cursor)
+ gsr_cursor_on_event(&capture_deps.x11_cursor, gsr_window_get_event_data(window));
gsr_damage_on_event(&damage, gsr_window_get_event_data(window));
- for(VideoSource &video_source : video_sources) {
+ for(size_t video_source_index = 0; video_source_index < video_sources->num_items; ++video_source_index) {
+ gsr_video_source &video_source = video_sources->items[video_source_index];
gsr_capture_on_event(video_source.capture, &egl);
}
}
- if(x11_cursor_display && arg_parser.settings.record_cursor)
- gsr_cursor_tick(&x11_cursor, DefaultRootWindow(x11_cursor_display));
+ if(capture_deps.x11_cursor_display && arg_parser.settings.record_cursor)
+ gsr_cursor_tick(&capture_deps.x11_cursor, DefaultRootWindow(capture_deps.x11_cursor_display));
gsr_damage_tick(&damage);
@@ -3090,7 +2333,8 @@ int main(int argc, char **argv) {
if(use_damage_tracking)
damaged = gsr_damage_is_damaged(&damage);
- for(VideoSource &video_source : video_sources) {
+ for(size_t video_source_index = 0; video_source_index < video_sources->num_items; ++video_source_index) {
+ gsr_video_source &video_source = video_sources->items[video_source_index];
gsr_capture_tick(video_source.capture);
if(gsr_capture_should_stop(video_source.capture, &should_stop_error)) {
@@ -3149,15 +2393,13 @@ int main(int argc, char **argv) {
gsr_damage_clear(&damage);
gsr_plugins_clear_damage(&plugins);
- gsr_capture_kms_cleanup_kms_fds();
+ gsr_capture_deps_cleanup_kms_fds(&capture_deps);
- if(kms_client_initialized) {
- if(gsr_kms_client_get_kms(&kms_client, &kms_response) != 0)
- gsr_log(GSR_LOG_LEVEL_ERROR, "failed to get kms, error: %d (%s)", kms_response.result, kms_response.err_msg);
- }
+ gsr_capture_deps_update_kms(&capture_deps);
bool capture_has_synchronous_task = false;
- for(VideoSource &video_source : video_sources) {
+ for(size_t video_source_index = 0; video_source_index < video_sources->num_items; ++video_source_index) {
+ gsr_video_source &video_source = video_sources->items[video_source_index];
if(video_source.capture->clear_damage)
video_source.capture->clear_damage(video_source.capture);
@@ -3170,7 +2412,8 @@ int main(int argc, char **argv) {
}
}
- for(VideoSource &video_source : video_sources) {
+ for(size_t video_source_index = 0; video_source_index < video_sources->num_items; ++video_source_index) {
+ gsr_video_source &video_source = video_sources->items[video_source_index];
if(video_source.capture->pre_capture)
video_source.capture->pre_capture(video_source.capture, &video_source.metadata, output_color_conversion);
}
@@ -3180,11 +2423,12 @@ int main(int argc, char **argv) {
gsr_color_conversion_clear(output_color_conversion);
}
- for(VideoSource &video_source : video_sources) {
+ for(size_t video_source_index = 0; video_source_index < video_sources->num_items; ++video_source_index) {
+ gsr_video_source &video_source = video_sources->items[video_source_index];
gsr_capture_capture(video_source.capture, &video_source.metadata, output_color_conversion);
}
- gsr_capture_kms_cleanup_kms_fds();
+ gsr_capture_deps_cleanup_kms_fds(&capture_deps);
if(plugins.num_plugins > 0) {
gsr_plugins_draw(&plugins);
@@ -3202,7 +2446,8 @@ int main(int argc, char **argv) {
gsr_egl_swap_buffers(&egl);
gsr_video_encoder_copy_textures_to_frame(video_encoder, video_frame, output_color_conversion);
- for(VideoSource &video_source : video_sources) {
+ for(size_t video_source_index = 0; video_source_index < video_sources->num_items; ++video_source_index) {
+ gsr_video_source &video_source = video_sources->items[video_source_index];
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;
}
@@ -3421,23 +2666,15 @@ int main(int argc, char **argv) {
avformat_free_context(av_format_context);
}
- gsr_cursor_deinit(&x11_cursor);
gsr_damage_deinit(&damage);
gsr_color_conversion_deinit(&color_conversion);
gsr_video_encoder_destroy(video_encoder, video_codec_context);
gsr_encoder_deinit(&encoder);
- for(VideoSource &video_source : video_sources) {
- gsr_capture_destroy(video_source.capture);
- }
+ gsr_video_sources_deinit(video_sources);
#ifdef GSR_APP_AUDIO
gsr_pipewire_audio_deinit(&pipewire_audio);
#endif
- if(kms_client_initialized) {
- gsr_capture_kms_cleanup_kms_fds();
- gsr_kms_client_deinit(&kms_client);
- }
-
- gsr_kde_night_light_destroy(kde_night_light);
+ gsr_capture_deps_deinit(&capture_deps);
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");
diff --git a/src/recorder/capture_setup.c b/src/recorder/capture_setup.c
new file mode 100644
index 0000000..93dbe37
--- /dev/null
+++ b/src/recorder/capture_setup.c
@@ -0,0 +1,543 @@
+#include "../../include/recorder/capture_setup.h"
+#include "../../include/recorder/error.h"
+#include "../../include/recorder/windowing.h"
+#include "../../include/capture/nvfbc.h"
+#include "../../include/capture/xcomposite.h"
+#include "../../include/capture/ximage.h"
+#include "../../include/capture/kms.h"
+#include "../../include/capture/v4l2.h"
+#ifdef GSR_PORTAL
+#include "../../include/capture/portal.h"
+#endif
+#include "../../include/args_parser.h"
+#include "../../include/utils.h"
+#include "../../include/window/window.h"
+#include "../../include/log.h"
+
+#include <string.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <unistd.h>
+
+typedef struct {
+ gsr_window *window;
+} monitor_output_callback_userdata;
+
+typedef struct {
+ char *output_name;
+} first_output_callback_userdata;
+
+typedef struct {
+ gsr_window *window;
+ vec2i position;
+ char *output_name;
+ vec2i monitor_pos;
+ vec2i monitor_size;
+ double monitor_scale_inverted;
+} monitor_by_position_callback_userdata;
+
+static void monitor_output_callback_print(const gsr_monitor *monitor, void *userdata) {
+ const monitor_output_callback_userdata *options = userdata;
+ vec2i monitor_position = monitor->pos;
+ vec2i monitor_size = monitor->size;
+ if(gsr_window_get_display_server(options->window) == GSR_DISPLAY_SERVER_WAYLAND) {
+ gsr_monitor_rotation monitor_rotation = GSR_MONITOR_ROT_0;
+ drm_monitor_get_display_server_data(options->window, monitor, &monitor_rotation, &monitor_position);
+ if(monitor_rotation == GSR_MONITOR_ROT_90 || monitor_rotation == GSR_MONITOR_ROT_270) {
+ const int tmp = monitor_size.x;
+ monitor_size.x = monitor_size.y;
+ monitor_size.y = tmp;
+ }
+ }
+ fprintf(stderr, " \"%.*s\" (%dx%d+%d+%d)\n", monitor->name_len, monitor->name, monitor_size.x, monitor_size.y, monitor_position.x, monitor_position.y);
+}
+
+static void monitor_output_callback_print_region(const gsr_monitor *monitor, void *userdata) {
+ (void)userdata;
+ const vec2i monitor_position = monitor->logical_pos;
+ const vec2i monitor_size = monitor->logical_size;
+ fprintf(stderr, " \"%.*s\" (%dx%d+%d+%d)\n", monitor->name_len, monitor->name, monitor_size.x, monitor_size.y, monitor_position.x, monitor_position.y);
+}
+
+static void get_first_output_callback(const gsr_monitor *monitor, void *userdata) {
+ first_output_callback_userdata *data = userdata;
+ if(!data->output_name)
+ data->output_name = strdup(monitor->name);
+}
+
+static void get_monitor_by_position_callback(const gsr_monitor *monitor, void *userdata) {
+ monitor_by_position_callback_userdata *data = userdata;
+
+ const vec2i monitor_position = monitor->logical_pos;
+ const vec2i monitor_size = monitor->size;
+ const vec2i monitor_logical_size = monitor->logical_size;
+
+ if(!data->output_name && data->position.x >= monitor_position.x && data->position.x <= monitor_position.x + monitor_logical_size.x
+ && data->position.y >= monitor_position.y && data->position.y <= monitor_position.y + monitor_logical_size.y)
+ {
+ data->output_name = strdup(monitor->name);
+ data->monitor_pos = monitor_position;
+ data->monitor_size = monitor_size;
+ data->monitor_scale_inverted = (double)monitor_size.x / (double)monitor_logical_size.x;
+ }
+}
+
+void gsr_capture_deps_init(gsr_capture_deps *self) {
+ memset(self, 0, sizeof(*self));
+}
+
+void gsr_capture_deps_init_cursor(gsr_capture_deps *self, gsr_egl *egl, bool record_cursor) {
+ if(gsr_window_get_display_server(egl->window) != GSR_DISPLAY_SERVER_X11 || !record_cursor)
+ return;
+
+ self->x11_cursor_display = (Display*)gsr_window_get_display(egl->window);
+ gsr_cursor_init(&self->x11_cursor, egl, self->x11_cursor_display);
+}
+
+void gsr_capture_deps_deinit(gsr_capture_deps *self) {
+ gsr_cursor_deinit(&self->x11_cursor);
+ self->x11_cursor_display = NULL;
+
+ if(self->kms_client_initialized) {
+ gsr_capture_deps_cleanup_kms_fds(self);
+ gsr_kms_client_deinit(&self->kms_client);
+ self->kms_client_initialized = false;
+ }
+
+ gsr_kde_night_light_destroy(self->kde_night_light);
+ self->kde_night_light = NULL;
+ self->kde_night_light_initialized = false;
+}
+
+void gsr_capture_deps_cleanup_kms_fds(gsr_capture_deps *self) {
+ for(int i = 0; i < self->kms_response.num_items; ++i) {
+ for(int j = 0; j < self->kms_response.items[i].num_dma_bufs; ++j) {
+ gsr_kms_response_dma_buf *dma_buf = &self->kms_response.items[i].dma_buf[j];
+ if(dma_buf->fd > 0) {
+ close(dma_buf->fd);
+ dma_buf->fd = 0;
+ }
+ }
+ self->kms_response.items[i].num_dma_bufs = 0;
+ }
+ self->kms_response.num_items = 0;
+}
+
+void gsr_capture_deps_update_kms(gsr_capture_deps *self) {
+ if(!self->kms_client_initialized)
+ return;
+
+ if(gsr_kms_client_get_kms(&self->kms_client, &self->kms_response) != 0)
+ gsr_log(GSR_LOG_LEVEL_ERROR, "failed to get kms, error: %d (%s)", self->kms_response.result, self->kms_response.err_msg);
+}
+
+static int validate_monitor_get_valid(const gsr_egl *egl, const char *window, char *output_name, size_t output_name_size) {
+ const bool is_x11 = gsr_window_get_display_server(egl->window) == GSR_DISPLAY_SERVER_X11;
+ const gsr_connection_type connection_type = is_x11 ? GSR_CONNECTION_X11 : GSR_CONNECTION_DRM;
+ const bool capture_use_drm = monitor_capture_use_drm(egl->window, egl->gpu_info.vendor);
+
+ snprintf(output_name, output_name_size, "%s", window);
+ if(strcmp(output_name, "screen") == 0) {
+ first_output_callback_userdata data;
+ data.output_name = NULL;
+ for_each_active_monitor_output(egl->window, egl->card_path, connection_type, get_first_output_callback, &data);
+
+ if(data.output_name) {
+ snprintf(output_name, output_name_size, "%s", data.output_name);
+ free(data.output_name);
+ } else {
+ gsr_log(GSR_LOG_LEVEL_ERROR, "no usable output found");
+ return GSR_ERROR_MONITOR_NOT_FOUND;
+ }
+ } else if(capture_use_drm || (strcmp(output_name, "screen-direct") != 0 && strcmp(output_name, "screen-direct-force") != 0)) {
+ gsr_monitor gmon;
+ if(!get_monitor_by_name(egl, connection_type, output_name, &gmon)) {
+ gsr_log(GSR_LOG_LEVEL_ERROR, "display \"%s\" not found, expected one of:", output_name);
+ fprintf(stderr, " \"screen\"\n");
+ if(!capture_use_drm)
+ fprintf(stderr, " \"screen-direct\"\n");
+
+ monitor_output_callback_userdata userdata;
+ userdata.window = egl->window;
+ for_each_active_monitor_output(egl->window, egl->card_path, connection_type, monitor_output_callback_print, &userdata);
+ return GSR_ERROR_MONITOR_NOT_FOUND;
+ }
+ }
+
+ return GSR_ERROR_OK;
+}
+
+static bool get_monitor_by_region_center(const gsr_egl *egl, vec2i region_position, vec2i region_size, char *output_name, size_t output_name_size, vec2i *monitor_pos, vec2i *monitor_size, double *monitor_scale_inverted) {
+ const bool is_x11 = gsr_window_get_display_server(egl->window) == GSR_DISPLAY_SERVER_X11;
+ const gsr_connection_type connection_type = is_x11 ? GSR_CONNECTION_X11 : GSR_CONNECTION_WAYLAND;
+
+ monitor_by_position_callback_userdata data;
+ data.window = egl->window;
+ data.position = (vec2i){ region_position.x + region_size.x / 2, region_position.y + region_size.y / 2 };
+ data.output_name = NULL;
+ data.monitor_pos = (vec2i){0, 0};
+ data.monitor_size = (vec2i){0, 0};
+ data.monitor_scale_inverted = 1.0;
+ for_each_active_monitor_output(egl->window, egl->card_path, connection_type, get_monitor_by_position_callback, &data);
+
+ output_name[0] = '\0';
+ if(data.output_name) {
+ snprintf(output_name, output_name_size, "%s", data.output_name);
+ free(data.output_name);
+ }
+ *monitor_pos = data.monitor_pos;
+ *monitor_size = data.monitor_size;
+ *monitor_scale_inverted = data.monitor_scale_inverted;
+ return output_name[0] != '\0';
+}
+
+static int region_get_data(gsr_egl *egl, vec2i *region_size, vec2i *region_position, char *output_name, size_t output_name_size) {
+ vec2i monitor_pos = {0, 0};
+ vec2i monitor_size = {0, 0};
+ double monitor_scale_inverted = 1.0;
+ if(!get_monitor_by_region_center(egl, *region_position, *region_size, output_name, output_name_size, &monitor_pos, &monitor_size, &monitor_scale_inverted)) {
+ const bool is_x11 = gsr_window_get_display_server(egl->window) == GSR_DISPLAY_SERVER_X11;
+ const gsr_connection_type connection_type = is_x11 ? GSR_CONNECTION_X11 : GSR_CONNECTION_WAYLAND;
+ gsr_log(GSR_LOG_LEVEL_ERROR, "the region %dx%d+%d+%d doesn't match any monitor. Available monitors and their regions:", region_size->x, region_size->y, region_position->x, region_position->y);
+ for_each_active_monitor_output(egl->window, egl->card_path, connection_type, monitor_output_callback_print_region, NULL);
+ return GSR_ERROR_MONITOR_NOT_FOUND;
+ }
+
+ /* Capture whole monitor when region size is set to 0x0 */
+ if(region_size->x == 0 && region_size->y == 0) {
+ region_position->x = 0;
+ region_position->y = 0;
+ } else {
+ region_position->x -= monitor_pos.x;
+ region_position->y -= monitor_pos.y;
+ /* Match drm plane coordinate space (1x scaling) to wayland coordinate space (which may have scaling set by user) */
+ region_position->x *= monitor_scale_inverted;
+ region_position->y *= monitor_scale_inverted;
+
+ region_size->x *= monitor_scale_inverted;
+ region_size->y *= monitor_scale_inverted;
+ }
+
+ return GSR_ERROR_OK;
+}
+
+static gsr_capture* create_monitor_capture(const gsr_recorder_settings *settings, gsr_egl *egl, gsr_capture_deps *deps, const gsr_capture_source *capture_source, bool prefer_ximage, int *error) {
+ *error = GSR_ERROR_OK;
+
+ if(gsr_window_get_display_server(egl->window) == GSR_DISPLAY_SERVER_X11 && prefer_ximage) {
+ gsr_capture_ximage_params ximage_params;
+ memset(&ximage_params, 0, sizeof(ximage_params));
+ ximage_params.egl = egl;
+ ximage_params.cursor = &deps->x11_cursor;
+ ximage_params.display_to_capture = capture_source->name;
+ ximage_params.record_cursor = settings->record_cursor;
+ ximage_params.output_resolution = 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);
+ }
+
+ if(monitor_capture_use_drm(egl->window, egl->gpu_info.vendor)) {
+ if(!deps->kms_client_initialized) {
+ deps->kms_client_initialized = true;
+ const int kms_init_res = gsr_kms_client_init(&deps->kms_client, egl->card_path);
+ if(kms_init_res != 0) {
+ *error = kms_init_res < 0 ? GSR_ERROR_GENERIC : -kms_init_res;
+ return NULL;
+ }
+ }
+
+ if(!deps->kde_night_light_initialized && gsr_window_get_display_server(egl->window) == GSR_DISPLAY_SERVER_WAYLAND) {
+ deps->kde_night_light_initialized = true;
+ deps->kde_night_light = gsr_kde_night_light_create();
+ }
+
+ gsr_capture_kms_params kms_params;
+ memset(&kms_params, 0, sizeof(kms_params));
+ kms_params.egl = egl;
+ kms_params.x11_cursor = &deps->x11_cursor;
+ kms_params.kms_response = &deps->kms_response;
+ kms_params.kde_night_light = deps->kde_night_light;
+ kms_params.display_to_capture = capture_source->name;
+ kms_params.record_cursor = settings->record_cursor;
+ kms_params.hdr = video_codec_is_hdr(settings->video_codec);
+ kms_params.fps = settings->fps;
+ kms_params.output_resolution = 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);
+ } else {
+ const char *capture_source_real = capture_source->name;
+ const bool direct_capture = strcmp(capture_source->name, "screen-direct") == 0 || strcmp(capture_source->name, "screen-direct-force") == 0;
+ if(direct_capture) {
+ capture_source_real = "screen";
+ gsr_log(GSR_LOG_LEVEL_WARNING, "%s capture option is not recommended unless you use G-SYNC as Nvidia has driver issues that can cause your system or games to freeze/crash.", capture_source->name);
+ }
+
+ gsr_capture_nvfbc_params nvfbc_params;
+ memset(&nvfbc_params, 0, sizeof(nvfbc_params));
+ nvfbc_params.egl = egl;
+ nvfbc_params.display_to_capture = capture_source_real;
+ nvfbc_params.fps = settings->fps;
+ nvfbc_params.direct_capture = direct_capture;
+ nvfbc_params.record_cursor = settings->record_cursor;
+ nvfbc_params.output_resolution = 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);
+ }
+}
+
+static gsr_capture* create_capture_impl(const gsr_recorder_settings *settings, gsr_egl *egl, gsr_capture_deps *deps, gsr_capture_source *capture_source, bool prefer_ximage, int *error) {
+ bool follow_focused = false;
+ const bool wayland = gsr_window_get_display_server(egl->window) == GSR_DISPLAY_SERVER_WAYLAND;
+
+ *error = GSR_ERROR_OK;
+ gsr_capture *capture = NULL;
+ if(capture_source->type == GSR_CAPTURE_SOURCE_TYPE_FOCUSED_WINDOW) {
+ if(wayland) {
+ gsr_log(GSR_LOG_LEVEL_ERROR, "GPU Screen Recorder window capture only works in a pure X11 session. Xwayland is not supported. You can record a monitor instead on wayland");
+ *error = GSR_ERROR_UNSUPPORTED;
+ return NULL;
+ }
+
+ if(settings->output_resolution.x <= 0 || 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", settings->output_resolution.x, settings->output_resolution.y);
+ args_parser_print_usage();
+ *error = GSR_ERROR_GENERIC;
+ return NULL;
+ }
+
+ follow_focused = true;
+ } else if(capture_source->type == GSR_CAPTURE_SOURCE_TYPE_PORTAL) {
+#ifdef GSR_PORTAL
+ /* Desktop portal capture on x11 doesn't seem to be hardware accelerated */
+ if(!wayland) {
+ gsr_log(GSR_LOG_LEVEL_ERROR, "desktop portal capture is not supported on X11");
+ *error = GSR_ERROR_GENERIC;
+ return NULL;
+ }
+
+ gsr_capture_portal_params portal_params;
+ memset(&portal_params, 0, sizeof(portal_params));
+ portal_params.egl = egl;
+ portal_params.record_cursor = settings->record_cursor;
+ portal_params.restore_portal_session = settings->restore_portal_session;
+ portal_params.portal_session_token_filepath = settings->portal_session_token_filepath;
+ portal_params.output_resolution = settings->output_resolution;
+ capture = gsr_capture_portal_create(&portal_params);
+ if(!capture) {
+ *error = GSR_ERROR_GENERIC;
+ return NULL;
+ }
+#else
+ gsr_log(GSR_LOG_LEVEL_ERROR, "option '-w portal' used but GPU Screen Recorder was compiled without desktop portal support. Please recompile GPU Screen recorder with the -Dportal=true option");
+ *error = GSR_ERROR_UNSUPPORTED;
+ return NULL;
+#endif
+ } else if(capture_source->type == GSR_CAPTURE_SOURCE_TYPE_REGION) {
+ const int region_result = region_get_data(egl, &capture_source->region_size, &capture_source->region_pos, capture_source->name, sizeof(capture_source->name));
+ if(region_result != GSR_ERROR_OK) {
+ *error = region_result;
+ return NULL;
+ }
+
+ capture = create_monitor_capture(settings, egl, deps, capture_source, prefer_ximage, error);
+ if(!capture) {
+ if(*error == GSR_ERROR_OK)
+ *error = GSR_ERROR_GENERIC;
+ return NULL;
+ }
+ } else if(capture_source->type == GSR_CAPTURE_SOURCE_TYPE_MONITOR) {
+ char monitor_name[GSR_CAPTURE_SOURCE_NAME_MAX_SIZE];
+ const int monitor_result = validate_monitor_get_valid(egl, capture_source->name, monitor_name, sizeof(monitor_name));
+ if(monitor_result != GSR_ERROR_OK) {
+ *error = monitor_result;
+ return NULL;
+ }
+ snprintf(capture_source->name, sizeof(capture_source->name), "%s", monitor_name);
+
+ capture = create_monitor_capture(settings, egl, deps, capture_source, prefer_ximage, error);
+ if(!capture) {
+ if(*error == GSR_ERROR_OK)
+ *error = GSR_ERROR_GENERIC;
+ return NULL;
+ }
+ } else if(capture_source->type == GSR_CAPTURE_SOURCE_TYPE_V4L2) {
+ gsr_capture_v4l2_params v4l2_params;
+ memset(&v4l2_params, 0, sizeof(v4l2_params));
+ v4l2_params.egl = egl;
+ v4l2_params.output_resolution = settings->output_resolution;
+ v4l2_params.device_path = capture_source->name;
+ v4l2_params.pixfmt = capture_source->v4l2_pixfmt;
+ v4l2_params.camera_fps = capture_source->camera_fps;
+ v4l2_params.camera_resolution.width = capture_source->camera_resolution.x;
+ v4l2_params.camera_resolution.height = capture_source->camera_resolution.y;
+ capture = gsr_capture_v4l2_create(&v4l2_params);
+ if(!capture) {
+ *error = GSR_ERROR_GENERIC;
+ return NULL;
+ }
+ } else {
+ if(wayland) {
+ gsr_log(GSR_LOG_LEVEL_ERROR, "GPU Screen Recorder window capture only works in a pure X11 session. Xwayland is not supported. You can record a monitor instead on wayland or use -w portal option which supports window capture if your wayland compositor supports window capture");
+ *error = GSR_ERROR_UNSUPPORTED;
+ return NULL;
+ }
+ }
+
+ if(!capture) {
+ gsr_capture_xcomposite_params xcomposite_params;
+ memset(&xcomposite_params, 0, sizeof(xcomposite_params));
+ xcomposite_params.egl = egl;
+ xcomposite_params.cursor = &deps->x11_cursor;
+ xcomposite_params.window = capture_source->window_id;
+ xcomposite_params.follow_focused = follow_focused;
+ xcomposite_params.record_cursor = settings->record_cursor;
+ xcomposite_params.output_resolution = settings->output_resolution;
+ capture = gsr_capture_xcomposite_create(&xcomposite_params);
+ if(!capture) {
+ *error = GSR_ERROR_GENERIC;
+ return NULL;
+ }
+ }
+
+ return capture;
+}
+
+int gsr_video_sources_create(gsr_video_sources *self, const gsr_recorder_settings *settings, gsr_egl *egl, gsr_capture_deps *deps, bool prefer_ximage, gsr_capture_sources *capture_sources, vec2i *video_size) {
+ memset(self, 0, sizeof(*self));
+ if(capture_sources->num_items == 0)
+ return GSR_ERROR_GENERIC;
+
+ self->items = calloc(capture_sources->num_items, sizeof(gsr_video_source));
+ if(!self->items) {
+ gsr_log(GSR_LOG_LEVEL_ERROR, "failed to allocate video sources");
+ return GSR_ERROR_GENERIC;
+ }
+
+ for(size_t i = 0; i < capture_sources->num_items; ++i) {
+ gsr_capture_source *capture_source = &capture_sources->items[i];
+ gsr_video_source *video_source = &self->items[i];
+
+ memset(&video_source->metadata, 0, sizeof(video_source->metadata));
+ video_source->metadata.fps = settings->fps;
+ video_source->metadata.halign = capture_source->halign;
+ video_source->metadata.valign = capture_source->valign;
+ video_source->metadata.flip = (gsr_flip)capture_source->flip;
+ video_source->capture_source = capture_source;
+
+ int error = GSR_ERROR_OK;
+ video_source->capture = create_capture_impl(settings, egl, deps, capture_source, prefer_ximage, &error);
+ if(!video_source->capture) {
+ gsr_video_sources_deinit(self);
+ return error;
+ }
+
+ ++self->num_items;
+ }
+
+ for(size_t i = 0; i < self->num_items; ++i) {
+ const int capture_result = gsr_capture_start(self->items[i].capture, &self->items[i].metadata);
+ if(capture_result != 0) {
+ gsr_log(GSR_LOG_LEVEL_ERROR, "gsr_capture_start failed");
+ gsr_video_sources_deinit(self);
+ return -capture_result;
+ }
+ }
+
+ vec2i start_pos = {99999, 99999};
+ vec2i end_pos = {-99999, -99999};
+ for(size_t i = 0; i < self->num_items; ++i) {
+ const gsr_video_source *video_source = &self->items[i];
+ /* TODO: Skip scalar positions for now, but this should be handled in a better way.
+ Maybe handle scalars at the next loop by multiplying video size by the scalar. */
+ if(video_source->capture_source->pos.x_type == VVEC2I_TYPE_SCALAR || video_source->capture_source->pos.y_type == VVEC2I_TYPE_SCALAR
+ || (video_source->capture_source->size.x_type == VVEC2I_TYPE_SCALAR && video_source->capture_source->size.x != 100)
+ || (video_source->capture_source->size.y_type == VVEC2I_TYPE_SCALAR && video_source->capture_source->size.y != 100))
+ {
+ continue;
+ }
+ const vec2i video_source_start_pos = {video_source->capture_source->pos.x, video_source->capture_source->pos.y};
+ const vec2i video_source_end_pos = {video_source_start_pos.x + video_source->metadata.video_size.x, video_source_start_pos.y + video_source->metadata.video_size.y};
+
+ if(video_source_start_pos.x < start_pos.x)
+ start_pos.x = video_source_start_pos.x;
+ if(video_source_start_pos.y < start_pos.y)
+ start_pos.y = video_source_start_pos.y;
+
+ if(video_source_end_pos.x > end_pos.x)
+ end_pos.x = video_source_end_pos.x;
+ if(video_source_end_pos.y > end_pos.y)
+ end_pos.y = video_source_end_pos.y;
+ }
+
+ video_size->x = end_pos.x - start_pos.x;
+ video_size->y = end_pos.y - start_pos.y;
+ if(video_size->x < 0)
+ video_size->x = 0;
+ if(video_size->y < 0)
+ video_size->y = 0;
+
+ for(size_t i = 0; i < self->num_items; ++i) {
+ self->items[i].metadata.video_size = *video_size;
+ }
+
+ return GSR_ERROR_OK;
+}
+
+void gsr_video_sources_update_with_real_video_size(gsr_video_sources *self, vec2i video_size) {
+ for(size_t i = 0; i < self->num_items; ++i) {
+ gsr_video_source *video_source = &self->items[i];
+ const gsr_capture_source *capture_source = video_source->capture_source;
+
+ video_source->metadata.recording_size = video_source->metadata.video_size;
+ /* TODO: What if this updated resolution is above max resolution? */
+ video_source->metadata.video_size = video_size;
+
+ if(capture_source->pos.x != 0 || capture_source->pos.y != 0) {
+ video_source->metadata.position.x = capture_source->pos.x;
+ video_source->metadata.position.y = capture_source->pos.y;
+
+ if(capture_source->pos.x_type == VVEC2I_TYPE_SCALAR)
+ video_source->metadata.position.x = video_source->metadata.video_size.x * ((double)video_source->metadata.position.x / 100.0);
+
+ if(capture_source->pos.y_type == VVEC2I_TYPE_SCALAR)
+ video_source->metadata.position.y = video_source->metadata.video_size.y * ((double)video_source->metadata.position.y / 100.0);
+ }
+
+ if(capture_source->size.x != 0 || capture_source->size.y != 0) {
+ video_source->metadata.recording_size.x = capture_source->size.x;
+ video_source->metadata.recording_size.y = capture_source->size.y;
+
+ if(capture_source->size.x_type == VVEC2I_TYPE_SCALAR)
+ video_source->metadata.recording_size.x = video_source->metadata.video_size.x * ((double)video_source->metadata.recording_size.x / 100.0);
+
+ if(capture_source->size.y_type == VVEC2I_TYPE_SCALAR)
+ video_source->metadata.recording_size.y = video_source->metadata.video_size.y * ((double)video_source->metadata.recording_size.y / 100.0);
+ }
+ }
+}
+
+bool gsr_video_sources_uses_external_image(const gsr_video_sources *self) {
+ for(size_t i = 0; i < self->num_items; ++i) {
+ if(gsr_capture_uses_external_image(self->items[i].capture))
+ return true;
+ }
+ return false;
+}
+
+void gsr_video_sources_deinit(gsr_video_sources *self) {
+ for(size_t i = 0; i < self->num_items; ++i) {
+ if(self->items[i].capture) {
+ gsr_capture_destroy(self->items[i].capture);
+ self->items[i].capture = NULL;
+ }
+ }
+
+ if(self->items) {
+ free(self->items);
+ self->items = NULL;
+ }
+ self->num_items = 0;
+}
diff --git a/src/recorder/capture_source.c b/src/recorder/capture_source.c
new file mode 100644
index 0000000..5407abc
--- /dev/null
+++ b/src/recorder/capture_source.c
@@ -0,0 +1,390 @@
+#include "../../include/recorder/capture_source.h"
+#include "../../include/recorder/error.h"
+#include "../../include/utils.h"
+#include "../../include/log.h"
+
+#include <string.h>
+#include <stdlib.h>
+#include <stdio.h>
+
+typedef struct {
+ gsr_capture_source *capture_source;
+ bool is_first_column;
+ int error;
+} parse_capture_source_options_userdata;
+
+typedef struct {
+ gsr_capture_sources *capture_sources;
+ vec2i region_position;
+ vec2i region_size;
+ bool has_multiple_capture_sources;
+ int error;
+} parse_capture_source_arg_userdata;
+
+void gsr_capture_source_init(gsr_capture_source *self, vec2i region_position, vec2i region_size) {
+ memset(self, 0, sizeof(*self));
+ self->type = GSR_CAPTURE_SOURCE_TYPE_WINDOW;
+ self->halign = GSR_CAPTURE_ALIGN_CENTER;
+ self->valign = GSR_CAPTURE_ALIGN_CENTER;
+ self->v4l2_pixfmt = GSR_CAPTURE_V4L2_PIXFMT_AUTO;
+ self->flip = GSR_FLIP_NONE;
+ self->pos = (vvec2i){0, 0, VVEC2I_TYPE_PIXELS, VVEC2I_TYPE_PIXELS};
+ self->size = (vvec2i){100, 100, VVEC2I_TYPE_SCALAR, VVEC2I_TYPE_SCALAR};
+ self->region_pos = region_position;
+ self->region_size = region_size;
+}
+
+static bool is_hex_num(char c) {
+ return (c >= 'A' && c <= 'F') || (c >= 'a' && c <= 'f') || (c >= '0' && c <= '9');
+}
+
+static bool contains_non_hex_number(const char *str) {
+ bool hex_start = false;
+ size_t len = strlen(str);
+ if(len >= 2 && memcmp(str, "0x", 2) == 0) {
+ str += 2;
+ len -= 2;
+ hex_start = true;
+ }
+
+ bool is_hex = false;
+ for(size_t i = 0; i < len; ++i) {
+ char c = str[i];
+ if(c == '\0')
+ return false;
+ if(!is_hex_num(c))
+ return true;
+ if((c >= 'A' && c <= 'F') || (c >= 'a' && c <= 'f'))
+ is_hex = true;
+ }
+
+ return is_hex && !hex_start;
+}
+
+static void capture_source_type_from_string(const char *capture_source_str, size_t size, gsr_capture_source *capture_source) {
+ char capture_source_str_n[64];
+ snprintf(capture_source_str_n, sizeof(capture_source_str_n), "%.*s", (int)size, capture_source_str);
+
+ if(size == 7 && memcmp(capture_source_str_n, "focused", 7) == 0) {
+ capture_source->type = GSR_CAPTURE_SOURCE_TYPE_FOCUSED_WINDOW;
+ } else if(size == 6 && memcmp(capture_source_str_n, "portal", 6) == 0) {
+ capture_source->type = GSR_CAPTURE_SOURCE_TYPE_PORTAL;
+ } else if(size == 6 && memcmp(capture_source_str_n, "region", 6) == 0) {
+ capture_source->type = GSR_CAPTURE_SOURCE_TYPE_REGION;
+ } else if(size >= 10 && memcmp(capture_source_str_n, "/dev/video", 10) == 0) {
+ capture_source->type = GSR_CAPTURE_SOURCE_TYPE_V4L2;
+ } else if(sscanf(capture_source_str_n, "%dx%d+%d+%d", &capture_source->region_size.x, &capture_source->region_size.y, &capture_source->region_pos.x, &capture_source->region_pos.y) == 4) {
+ capture_source->type = GSR_CAPTURE_SOURCE_TYPE_REGION;
+ capture_source->region_set = true;
+ } else if(contains_non_hex_number(capture_source_str_n)) {
+ capture_source->type = GSR_CAPTURE_SOURCE_TYPE_MONITOR;
+ } else {
+ capture_source->type = GSR_CAPTURE_SOURCE_TYPE_WINDOW;
+ }
+}
+
+static bool string_to_capture_alignment(const char *str, size_t len, gsr_capture_alignment *alignment) {
+ if(len == 5 && memcmp(str, "start", 5) == 0) {
+ *alignment = GSR_CAPTURE_ALIGN_START;
+ return true;
+ } else if(len == 6 && memcmp(str, "center", 6) == 0) {
+ *alignment = GSR_CAPTURE_ALIGN_CENTER;
+ return true;
+ } else if(len == 3 && memcmp(str, "end", 3) == 0) {
+ *alignment = GSR_CAPTURE_ALIGN_END;
+ return true;
+ } else {
+ return false;
+ }
+}
+
+static bool string_to_v4l2_pixfmt(const char *str, size_t len, gsr_capture_v4l2_pixfmt *pixfmt) {
+ if(len == 4 && memcmp(str, "auto", 4) == 0) {
+ *pixfmt = GSR_CAPTURE_V4L2_PIXFMT_AUTO;
+ return true;
+ } else if(len == 4 && memcmp(str, "yuyv", 4) == 0) {
+ *pixfmt = GSR_CAPTURE_V4L2_PIXFMT_YUYV;
+ return true;
+ } else if(len == 5 && memcmp(str, "mjpeg", 5) == 0) {
+ *pixfmt = GSR_CAPTURE_V4L2_PIXFMT_MJPEG;
+ return true;
+ } else {
+ return false;
+ }
+}
+
+static bool string_to_bool(const char *str, size_t len, bool *value) {
+ if(len == 4 && memcmp(str, "true", 4) == 0) {
+ *value = true;
+ return true;
+ } else if(len == 5 && memcmp(str, "false", 5) == 0) {
+ *value = false;
+ return true;
+ } else {
+ return false;
+ }
+}
+
+static bool parse_capture_source_options_callback(const char *sub, size_t size, void *userdata) {
+ parse_capture_source_options_userdata *parse_userdata = userdata;
+ gsr_capture_source *capture_source = parse_userdata->capture_source;
+ if(size == 0)
+ return true;
+
+ /* First column contains the capture target */
+ if(parse_userdata->is_first_column) {
+ parse_userdata->is_first_column = false;
+ return true;
+ }
+
+ if(gsr_string_starts_with(sub, size, "x=")) {
+ capture_source->pos.x_type = sub[size - 1] == '%' ? VVEC2I_TYPE_SCALAR : VVEC2I_TYPE_PIXELS;
+ sub += 2;
+ size -= 2;
+ if(!gsr_string_to_int(sub, size, &capture_source->pos.x)) {
+ gsr_log(GSR_LOG_LEVEL_ERROR, "invalid capture target value for option x: \"%.*s\", expected a number", (int)size, sub);
+ parse_userdata->error = GSR_ERROR_GENERIC;
+ return false;
+ }
+ } else if(gsr_string_starts_with(sub, size, "y=")) {
+ capture_source->pos.y_type = sub[size - 1] == '%' ? VVEC2I_TYPE_SCALAR : VVEC2I_TYPE_PIXELS;
+ sub += 2;
+ size -= 2;
+ if(!gsr_string_to_int(sub, size, &capture_source->pos.y)) {
+ gsr_log(GSR_LOG_LEVEL_ERROR, "invalid capture target value for option y: \"%.*s\", expected a number", (int)size, sub);
+ parse_userdata->error = GSR_ERROR_GENERIC;
+ return false;
+ }
+ } else if(gsr_string_starts_with(sub, size, "width=")) {
+ capture_source->size.x_type = sub[size - 1] == '%' ? VVEC2I_TYPE_SCALAR : VVEC2I_TYPE_PIXELS;
+ sub += 6;
+ size -= 6;
+ if(!gsr_string_to_int(sub, size, &capture_source->size.x)) {
+ gsr_log(GSR_LOG_LEVEL_ERROR, "invalid capture target value for option width: \"%.*s\", expected a number", (int)size, sub);
+ parse_userdata->error = GSR_ERROR_GENERIC;
+ return false;
+ }
+ } else if(gsr_string_starts_with(sub, size, "height=")) {
+ capture_source->size.y_type = sub[size - 1] == '%' ? VVEC2I_TYPE_SCALAR : VVEC2I_TYPE_PIXELS;
+ sub += 7;
+ size -= 7;
+ if(!gsr_string_to_int(sub, size, &capture_source->size.y)) {
+ gsr_log(GSR_LOG_LEVEL_ERROR, "invalid capture target value for option height: \"%.*s\", expected a number", (int)size, sub);
+ parse_userdata->error = GSR_ERROR_GENERIC;
+ return false;
+ }
+ } else if(gsr_string_starts_with(sub, size, "halign=")) {
+ sub += 7;
+ size -= 7;
+ if(!string_to_capture_alignment(sub, size, &capture_source->halign)) {
+ gsr_log(GSR_LOG_LEVEL_ERROR, "invalid capture target value for option halign: \"%.*s\", expected a \"start\", \"center\" or \"end\"", (int)size, sub);
+ parse_userdata->error = GSR_ERROR_GENERIC;
+ return false;
+ }
+ } else if(gsr_string_starts_with(sub, size, "valign=")) {
+ sub += 7;
+ size -= 7;
+ if(!string_to_capture_alignment(sub, size, &capture_source->valign)) {
+ gsr_log(GSR_LOG_LEVEL_ERROR, "invalid capture target value for option valign: \"%.*s\", expected a \"start\", \"center\" or \"end\"", (int)size, sub);
+ parse_userdata->error = GSR_ERROR_GENERIC;
+ return false;
+ }
+ } else if(gsr_string_starts_with(sub, size, "pixfmt=")) {
+ sub += 7;
+ size -= 7;
+ if(!string_to_v4l2_pixfmt(sub, size, &capture_source->v4l2_pixfmt)) {
+ gsr_log(GSR_LOG_LEVEL_ERROR, "invalid v4l2 pixfmt value for option pixfmt: \"%.*s\", expected a \"auto\", \"yuyv\" or \"mjpeg\"", (int)size, sub);
+ parse_userdata->error = GSR_ERROR_GENERIC;
+ return false;
+ }
+ } else if(gsr_string_starts_with(sub, size, "hflip=")) {
+ sub += 6;
+ size -= 6;
+ bool hflip = false;
+ if(!string_to_bool(sub, size, &hflip)) {
+ gsr_log(GSR_LOG_LEVEL_ERROR, "invalid bool value for option hflip: \"%.*s\", expected a \"true\" or \"false\"", (int)size, sub);
+ parse_userdata->error = GSR_ERROR_GENERIC;
+ return false;
+ }
+
+ if(hflip)
+ capture_source->flip |= GSR_FLIP_HORIZONTAL;
+ } else if(gsr_string_starts_with(sub, size, "vflip=")) {
+ sub += 6;
+ size -= 6;
+ bool vflip = false;
+ if(!string_to_bool(sub, size, &vflip)) {
+ gsr_log(GSR_LOG_LEVEL_ERROR, "invalid bool value for option vflip: \"%.*s\", expected a \"true\" or \"false\"", (int)size, sub);
+ parse_userdata->error = GSR_ERROR_GENERIC;
+ return false;
+ }
+
+ if(vflip)
+ capture_source->flip |= GSR_FLIP_VERTICAL;
+ } else if(gsr_string_starts_with(sub, size, "camera_fps=")) {
+ sub += 11;
+ size -= 11;
+ if(!gsr_string_to_int(sub, size, &capture_source->camera_fps)) {
+ gsr_log(GSR_LOG_LEVEL_ERROR, "invalid capture target value for option camera_fps: \"%.*s\", expected a number", (int)size, sub);
+ parse_userdata->error = GSR_ERROR_GENERIC;
+ return false;
+ }
+ } else if(gsr_string_starts_with(sub, size, "camera_width=")) {
+ sub += 13;
+ size -= 13;
+ if(!gsr_string_to_int(sub, size, &capture_source->camera_resolution.x)) {
+ gsr_log(GSR_LOG_LEVEL_ERROR, "invalid capture target value for option camera_width: \"%.*s\", expected a number", (int)size, sub);
+ parse_userdata->error = GSR_ERROR_GENERIC;
+ return false;
+ }
+ } else if(gsr_string_starts_with(sub, size, "camera_height=")) {
+ sub += 14;
+ size -= 14;
+ if(!gsr_string_to_int(sub, size, &capture_source->camera_resolution.y)) {
+ gsr_log(GSR_LOG_LEVEL_ERROR, "invalid capture target value for option camera_height: \"%.*s\", expected a number", (int)size, sub);
+ parse_userdata->error = GSR_ERROR_GENERIC;
+ return false;
+ }
+ } else {
+ gsr_log(GSR_LOG_LEVEL_ERROR, "invalid capture target option \"%.*s\", expected x, y, width, height, halign, valign, pixfmt, hflip, vflip, camera_fps, camera_width or camera_height", (int)size, sub);
+ parse_userdata->error = GSR_ERROR_GENERIC;
+ return false;
+ }
+
+ return true;
+}
+
+static int parse_capture_source_options(const char *capture_source_str, size_t capture_source_str_size, gsr_capture_source *capture_source) {
+ char options[1024];
+ snprintf(options, sizeof(options), "%.*s", (int)capture_source_str_size, capture_source_str);
+
+ parse_capture_source_options_userdata userdata;
+ userdata.capture_source = capture_source;
+ userdata.is_first_column = true;
+ userdata.error = GSR_ERROR_OK;
+ gsr_string_split(options, ';', parse_capture_source_options_callback, &userdata);
+ return userdata.error;
+}
+
+static bool parse_capture_source_arg_callback(const char *sub, size_t size, void *userdata) {
+ parse_capture_source_arg_userdata *parse_userdata = userdata;
+ if(size == 0)
+ return true;
+
+ const char *substr_start = sub;
+ const size_t substr_size = size;
+ size_t capture_source_size = size;
+ const char *capture_source_end = memchr(sub, ';', size);
+ if(capture_source_end)
+ capture_source_size = capture_source_end - sub;
+
+ gsr_capture_source capture_source;
+ gsr_capture_source_init(&capture_source, parse_userdata->region_position, parse_userdata->region_size);
+
+ if(gsr_string_starts_with(sub, capture_source_size, "monitor:")) {
+ capture_source.type = GSR_CAPTURE_SOURCE_TYPE_MONITOR;
+ sub += 8;
+ capture_source_size -= 8;
+ } else if(gsr_string_starts_with(sub, capture_source_size, "window:")) {
+ capture_source.type = GSR_CAPTURE_SOURCE_TYPE_WINDOW;
+ sub += 7;
+ capture_source_size -= 7;
+ } else if(gsr_string_starts_with(sub, capture_source_size, "v4l2:")) {
+ capture_source.type = GSR_CAPTURE_SOURCE_TYPE_V4L2;
+ sub += 5;
+ capture_source_size -= 5;
+ } else {
+ capture_source_type_from_string(sub, capture_source_size, &capture_source);
+ }
+
+ snprintf(capture_source.name, sizeof(capture_source.name), "%.*s", (int)capture_source_size, sub);
+
+ if(capture_source.type == GSR_CAPTURE_SOURCE_TYPE_WINDOW) {
+ if(!gsr_string_to_int64(capture_source.name, strlen(capture_source.name), &capture_source.window_id)) {
+ gsr_log(GSR_LOG_LEVEL_ERROR, "invalid window number %s", capture_source.name);
+ args_parser_print_usage();
+ parse_userdata->error = GSR_ERROR_GENERIC;
+ return false;
+ }
+ }
+
+ if(parse_userdata->has_multiple_capture_sources) {
+ capture_source.halign = GSR_CAPTURE_ALIGN_START;
+ capture_source.valign = GSR_CAPTURE_ALIGN_START;
+ capture_source.pos = (vvec2i){0, 0, VVEC2I_TYPE_PIXELS, VVEC2I_TYPE_PIXELS};
+ }
+
+ const int parse_options_result = parse_capture_source_options(substr_start, substr_size, &capture_source);
+ if(parse_options_result != GSR_ERROR_OK) {
+ parse_userdata->error = parse_options_result;
+ return false;
+ }
+
+ gsr_capture_sources *capture_sources = parse_userdata->capture_sources;
+ if(!gsr_array_ensure_capacity((void**)&capture_sources->items, capture_sources->num_items, &capture_sources->capacity_items, sizeof(gsr_capture_source))) {
+ parse_userdata->error = GSR_ERROR_GENERIC;
+ return false;
+ }
+
+ capture_sources->items[capture_sources->num_items] = capture_source;
+ ++capture_sources->num_items;
+ return true;
+}
+
+int gsr_capture_sources_parse(gsr_capture_sources *self, const char *capture_source_arg, vec2i region_position, vec2i region_size) {
+ memset(self, 0, sizeof(*self));
+
+ parse_capture_source_arg_userdata userdata;
+ userdata.capture_sources = self;
+ userdata.region_position = region_position;
+ userdata.region_size = region_size;
+ userdata.has_multiple_capture_sources = strchr(capture_source_arg, '|') != NULL;
+ userdata.error = GSR_ERROR_OK;
+
+ gsr_string_split(capture_source_arg, '|', parse_capture_source_arg_callback, &userdata);
+ if(userdata.error != GSR_ERROR_OK)
+ gsr_capture_sources_deinit(self);
+
+ return userdata.error;
+}
+
+void gsr_capture_sources_deinit(gsr_capture_sources *self) {
+ if(self->items) {
+ free(self->items);
+ self->items = NULL;
+ }
+ self->num_items = 0;
+ self->capacity_items = 0;
+}
+
+bool gsr_capture_sources_has_type(const gsr_capture_sources *self, CaptureSourceType type) {
+ for(size_t i = 0; i < self->num_items; ++i) {
+ if(self->items[i].type == type)
+ return true;
+ }
+ return false;
+}
+
+bool gsr_capture_sources_has_damage_tracked_target(const gsr_capture_sources *self) {
+ for(size_t i = 0; i < self->num_items; ++i) {
+ if(self->items[i].type != GSR_CAPTURE_SOURCE_TYPE_V4L2)
+ return true;
+ }
+ return false;
+}
+
+bool gsr_capture_sources_has_region_set(const gsr_capture_sources *self) {
+ for(size_t i = 0; i < self->num_items; ++i) {
+ if(self->items[i].type == GSR_CAPTURE_SOURCE_TYPE_REGION && self->items[i].region_set)
+ return true;
+ }
+ return false;
+}
+
+bool gsr_capture_sources_has_monitor_or_region(const gsr_capture_sources *self) {
+ for(size_t i = 0; i < self->num_items; ++i) {
+ if(self->items[i].type == GSR_CAPTURE_SOURCE_TYPE_MONITOR || self->items[i].type == GSR_CAPTURE_SOURCE_TYPE_REGION)
+ return true;
+ }
+ return false;
+}