diff options
Diffstat (limited to 'src/recorder/capture_setup.c')
| -rw-r--r-- | src/recorder/capture_setup.c | 571 |
1 files changed, 571 insertions, 0 deletions
diff --git a/src/recorder/capture_setup.c b/src/recorder/capture_setup.c new file mode 100644 index 0000000..9fced21 --- /dev/null +++ b/src/recorder/capture_setup.c @@ -0,0 +1,571 @@ +#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; +} + +/* The size and position of a capture source can be relative to the video size, in which case it can't be used to calculate the video size */ +static bool video_source_size_is_relative_to_video_size(const gsr_video_source *self) { + return self->capture_source->pos.x_type == VVEC2I_TYPE_SCALAR || self->capture_source->pos.y_type == VVEC2I_TYPE_SCALAR + || (self->capture_source->size.x_type == VVEC2I_TYPE_SCALAR && self->capture_source->size.x != 100) + || (self->capture_source->size.y_type == VVEC2I_TYPE_SCALAR && self->capture_source->size.y != 100); +} + +/* The video size is the area that all capture sources cover */ +static vec2i video_sources_get_total_size(const gsr_video_sources *self) { + 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]; + if(video_source_size_is_relative_to_video_size(video_source)) + 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; + } + + /* Every capture source is relative to the video size, so use the size of the capture sources themselves as the video size */ + if(end_pos.x <= start_pos.x || end_pos.y <= start_pos.y) { + start_pos = (vec2i){0, 0}; + end_pos = (vec2i){0, 0}; + for(size_t i = 0; i < self->num_items; ++i) { + const vec2i capture_size = self->items[i].metadata.video_size; + if(capture_size.x > end_pos.x) + end_pos.x = capture_size.x; + if(capture_size.y > end_pos.y) + end_pos.y = capture_size.y; + } + } + + vec2i video_size = { end_pos.x - start_pos.x, end_pos.y - start_pos.y }; + if(video_size.x < 0) + video_size.x = 0; + if(video_size.y < 0) + video_size.y = 0; + + return video_size; +} + +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; + } + } + + *video_size = video_sources_get_total_size(self); + + 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; +} |
