diff options
Diffstat (limited to 'src/recorder/capture_source.c')
| -rw-r--r-- | src/recorder/capture_source.c | 390 |
1 files changed, 390 insertions, 0 deletions
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; +} |
