From 8acb34638212ab8dba0d48a57dd40721203a7a44 Mon Sep 17 00:00:00 2001 From: dec05eba Date: Sat, 14 Sep 2024 01:15:01 +0200 Subject: Set update fps to video fps, on x11 sync video to damage tracking --- src/capture/capture.c | 5 ++ src/capture/kms.c | 15 ++-- src/capture/xcomposite.c | 122 ++++++++++--------------- src/damage.c | 229 +++++++++++++++++++++++++++++++++++++++++++---- src/egl.c | 35 ++++++-- src/main.cpp | 187 +++++++++++++++++++++----------------- src/utils.c | 4 +- 7 files changed, 403 insertions(+), 194 deletions(-) (limited to 'src') diff --git a/src/capture/capture.c b/src/capture/capture.c index 7c5737d..5fc96d0 100644 --- a/src/capture/capture.c +++ b/src/capture/capture.c @@ -16,6 +16,11 @@ void gsr_capture_tick(gsr_capture *cap, AVCodecContext *video_codec_context) { cap->tick(cap, video_codec_context); } +void gsr_capture_on_event(gsr_capture *cap, gsr_egl *egl) { + if(cap->on_event) + cap->on_event(cap, egl); +} + bool gsr_capture_should_stop(gsr_capture *cap, bool *err) { assert(cap->started); if(cap->should_stop) diff --git a/src/capture/kms.c b/src/capture/kms.c index bdf3202..9ba4bd2 100644 --- a/src/capture/kms.c +++ b/src/capture/kms.c @@ -4,7 +4,6 @@ #include "../../include/cursor.h" #include "../../kms/client/kms_client.h" -#include #include #include #include @@ -50,7 +49,6 @@ typedef struct { bool is_x11; gsr_cursor x11_cursor; - XEvent xev; } gsr_capture_kms; static void gsr_capture_kms_cleanup_kms_fds(gsr_capture_kms *self) { @@ -199,17 +197,13 @@ static int gsr_capture_kms_start(gsr_capture *cap, AVCodecContext *video_codec_c return 0; } -static void gsr_capture_kms_tick(gsr_capture *cap, AVCodecContext *video_codec_context) { - (void)video_codec_context; +static void gsr_capture_kms_on_event(gsr_capture *cap, gsr_egl *egl) { gsr_capture_kms *self = cap->priv; - if(!self->is_x11) return; - while(XPending(self->params.egl->x11.dpy)) { - XNextEvent(self->params.egl->x11.dpy, &self->xev); - gsr_cursor_update(&self->x11_cursor, &self->xev); - } + XEvent *xev = gsr_egl_get_event_data(egl); + gsr_cursor_update(&self->x11_cursor, xev); } static float monitor_rotation_to_radians(gsr_monitor_rotation rot) { @@ -628,7 +622,8 @@ gsr_capture* gsr_capture_kms_create(const gsr_capture_kms_params *params) { *cap = (gsr_capture) { .start = gsr_capture_kms_start, - .tick = gsr_capture_kms_tick, + .on_event = gsr_capture_kms_on_event, + .tick = NULL, .should_stop = gsr_capture_kms_should_stop, .capture = gsr_capture_kms_capture, .capture_end = gsr_capture_kms_capture_end, diff --git a/src/capture/xcomposite.c b/src/capture/xcomposite.c index 5ec8c12..e99cabf 100644 --- a/src/capture/xcomposite.c +++ b/src/capture/xcomposite.c @@ -2,7 +2,6 @@ #include "../../include/window_texture.h" #include "../../include/utils.h" #include "../../include/cursor.h" -#include "../../include/damage.h" #include "../../include/color_conversion.h" #include @@ -17,12 +16,12 @@ typedef struct { gsr_capture_xcomposite_params params; - XEvent xev; bool should_stop; bool stop_is_error; bool window_resized; bool follow_focused_initialized; + bool init_new_window; Window window; vec2i window_size; @@ -34,8 +33,6 @@ typedef struct { Atom net_active_window_atom; gsr_cursor cursor; - gsr_damage damage; - bool cursor_damaged; bool clear_background; } gsr_capture_xcomposite; @@ -43,7 +40,6 @@ typedef struct { static void gsr_capture_xcomposite_stop(gsr_capture_xcomposite *self) { window_texture_deinit(&self->window_texture); gsr_cursor_deinit(&self->cursor); - gsr_damage_deinit(&self->damage); } static int max_int(int a, int b) { @@ -78,13 +74,6 @@ static int gsr_capture_xcomposite_start(gsr_capture *cap, AVCodecContext *video_ self->window = self->params.window; } - if(self->params.track_damage) - gsr_damage_init(&self->damage, self->params.egl->x11.dpy); - else - memset(&self->damage, 0, sizeof(self->damage)); - - gsr_damage_set_target_window(&self->damage, self->window); - /* TODO: Do these in tick, and allow error if follow_focused */ XWindowAttributes attr; @@ -141,59 +130,12 @@ static void gsr_capture_xcomposite_tick(gsr_capture *cap, AVCodecContext *video_ (void)video_codec_context; gsr_capture_xcomposite *self = cap->priv; - bool init_new_window = false; - while(XPending(self->params.egl->x11.dpy)) { - XNextEvent(self->params.egl->x11.dpy, &self->xev); - - switch(self->xev.type) { - case DestroyNotify: { - /* Window died (when not following focused window), so we stop recording */ - if(!self->params.follow_focused && self->xev.xdestroywindow.window == self->window) { - self->should_stop = true; - self->stop_is_error = false; - } - break; - } - case Expose: { - /* Requires window texture recreate */ - if(self->xev.xexpose.count == 0 && self->xev.xexpose.window == self->window) { - self->window_resize_timer = clock_get_monotonic_seconds(); - self->window_resized = true; - } - break; - } - case ConfigureNotify: { - /* Window resized */ - if(self->xev.xconfigure.window == self->window && (self->xev.xconfigure.width != self->window_size.x || self->xev.xconfigure.height != self->window_size.y)) { - self->window_size.x = max_int(self->xev.xconfigure.width, 0); - self->window_size.y = max_int(self->xev.xconfigure.height, 0); - self->window_resize_timer = clock_get_monotonic_seconds(); - self->window_resized = true; - } - break; - } - case PropertyNotify: { - /* Focused window changed */ - if(self->params.follow_focused && self->xev.xproperty.atom == self->net_active_window_atom) { - init_new_window = true; - } - break; - } - } - - gsr_damage_update(&self->damage, &self->xev); - if(gsr_cursor_update(&self->cursor, &self->xev)) { - if(self->params.record_cursor && self->cursor.visible) { - self->cursor_damaged = true; - } - } - } - if(self->params.follow_focused && !self->follow_focused_initialized) { - init_new_window = true; + self->init_new_window = true; } - if(init_new_window) { + if(self->init_new_window) { + self->init_new_window = false; Window focused_window = get_focused_window(self->params.egl->x11.dpy, self->net_active_window_atom); if(focused_window != self->window || !self->follow_focused_initialized) { self->follow_focused_initialized = true; @@ -223,7 +165,6 @@ static void gsr_capture_xcomposite_tick(gsr_capture *cap, AVCodecContext *video_ self->window_resized = false; self->clear_background = true; - gsr_damage_set_target_window(&self->damage, self->window); } } @@ -247,19 +188,49 @@ static void gsr_capture_xcomposite_tick(gsr_capture *cap, AVCodecContext *video_ self->params.egl->glBindTexture(GL_TEXTURE_2D, 0); self->clear_background = true; - gsr_damage_set_target_window(&self->damage, self->window); } } -static bool gsr_capture_xcomposite_is_damaged(gsr_capture *cap) { +static void gsr_capture_xcomposite_on_event(gsr_capture *cap, gsr_egl *egl) { gsr_capture_xcomposite *self = cap->priv; - return gsr_damage_is_damaged(&self->damage) || self->cursor_damaged; -} + XEvent *xev = gsr_egl_get_event_data(egl); + switch(xev->type) { + case DestroyNotify: { + /* Window died (when not following focused window), so we stop recording */ + if(!self->params.follow_focused && xev->xdestroywindow.window == self->window) { + self->should_stop = true; + self->stop_is_error = false; + } + break; + } + case Expose: { + /* Requires window texture recreate */ + if(xev->xexpose.count == 0 && xev->xexpose.window == self->window) { + self->window_resize_timer = clock_get_monotonic_seconds(); + self->window_resized = true; + } + break; + } + case ConfigureNotify: { + /* Window resized */ + if(xev->xconfigure.window == self->window && (xev->xconfigure.width != self->window_size.x || xev->xconfigure.height != self->window_size.y)) { + self->window_size.x = max_int(xev->xconfigure.width, 0); + self->window_size.y = max_int(xev->xconfigure.height, 0); + self->window_resize_timer = clock_get_monotonic_seconds(); + self->window_resized = true; + } + break; + } + case PropertyNotify: { + /* Focused window changed */ + if(self->params.follow_focused && xev->xproperty.atom == self->net_active_window_atom) { + self->init_new_window = true; + } + break; + } + } -static void gsr_capture_xcomposite_clear_damage(gsr_capture *cap) { - gsr_capture_xcomposite *self = cap->priv; - gsr_damage_clear(&self->damage); - self->cursor_damaged = false; + gsr_cursor_update(&self->cursor, xev); } static bool gsr_capture_xcomposite_should_stop(gsr_capture *cap, bool *err) { @@ -325,6 +296,11 @@ static gsr_source_color gsr_capture_xcomposite_get_source_color(gsr_capture *cap return GSR_SOURCE_COLOR_RGB; } +static uint64_t gsr_capture_xcomposite_get_window_id(gsr_capture *cap) { + gsr_capture_xcomposite *self = cap->priv; + return self->window; +} + static void gsr_capture_xcomposite_destroy(gsr_capture *cap, AVCodecContext *video_codec_context) { (void)video_codec_context; if(cap->priv) { @@ -355,14 +331,14 @@ gsr_capture* gsr_capture_xcomposite_create(const gsr_capture_xcomposite_params * *cap = (gsr_capture) { .start = gsr_capture_xcomposite_start, + .on_event = gsr_capture_xcomposite_on_event, .tick = gsr_capture_xcomposite_tick, - .is_damaged = gsr_capture_xcomposite_is_damaged, - .clear_damage = gsr_capture_xcomposite_clear_damage, .should_stop = gsr_capture_xcomposite_should_stop, .capture = gsr_capture_xcomposite_capture, .capture_end = NULL, .get_source_color = gsr_capture_xcomposite_get_source_color, .uses_external_image = NULL, + .get_window_id = gsr_capture_xcomposite_get_window_id, .destroy = gsr_capture_xcomposite_destroy, .priv = cap_xcomp }; diff --git a/src/damage.c b/src/damage.c index 1e68e6e..2eda5d7 100644 --- a/src/damage.c +++ b/src/damage.c @@ -1,68 +1,259 @@ #include "../include/damage.h" +#include "../include/utils.h" #include #include #include +#include -bool gsr_damage_init(gsr_damage *self, Display *display) { +typedef struct { + vec2i pos; + vec2i size; +} gsr_rectangle; + +static bool rectangles_intersect(gsr_rectangle rect1, gsr_rectangle rect2) { + return rect1.pos.x < rect2.pos.x + rect2.size.x && rect1.pos.x + rect1.size.x > rect2.pos.x && + rect1.pos.y < rect2.pos.y + rect2.size.y && rect1.pos.y + rect1.size.y > rect2.pos.y; +} + +static bool xrandr_is_supported(Display *display) { + int major_version = 0; + int minor_version = 0; + if(!XRRQueryVersion(display, &major_version, &minor_version)) + return false; + + return major_version > 1 || (major_version == 1 && minor_version >= 2); +} + +bool gsr_damage_init(gsr_damage *self, gsr_egl *egl, bool track_cursor) { memset(self, 0, sizeof(*self)); - self->display = display; + self->egl = egl; + self->track_cursor = track_cursor; + + if(gsr_egl_get_display_server(egl) != GSR_DISPLAY_SERVER_X11) { + fprintf(stderr, "gsr warning: gsr_damage_init: damage tracking is not supported on wayland\n"); + return false; + } - if(!XDamageQueryExtension(self->display, &self->damage_event, &self->damage_error)) { + if(!XDamageQueryExtension(self->egl->x11.dpy, &self->damage_event, &self->damage_error)) { fprintf(stderr, "gsr warning: gsr_damage_init: XDamage is not supported by your X11 server\n"); - self->damage_event = 0; - self->damage_error = 0; + gsr_damage_deinit(self); return false; } + if(!XRRQueryExtension(self->egl->x11.dpy, &self->randr_event, &self->randr_error)) { + fprintf(stderr, "gsr warning: gsr_damage_init: XRandr is not supported by your X11 server\n"); + gsr_damage_deinit(self); + return false; + } + + if(!xrandr_is_supported(self->egl->x11.dpy)) { + fprintf(stderr, "gsr warning: gsr_damage_init: your X11 randr version is too old\n"); + gsr_damage_deinit(self); + return false; + } + + XRRSelectInput(self->egl->x11.dpy, DefaultRootWindow(self->egl->x11.dpy), RRScreenChangeNotifyMask | RRCrtcChangeNotifyMask | RROutputChangeNotifyMask); + self->damaged = true; return true; } void gsr_damage_deinit(gsr_damage *self) { if(self->damage) { - XDamageDestroy(self->display, self->damage); + XDamageDestroy(self->egl->x11.dpy, self->damage); self->damage = None; } + + self->damage_event = 0; + self->damage_error = 0; + + self->randr_event = 0; + self->randr_error = 0; } bool gsr_damage_set_target_window(gsr_damage *self, uint64_t window) { if(self->damage_event == 0) return false; + if(window == self->window) + return true; + if(self->damage) { - XDamageDestroy(self->display, self->damage); + XDamageDestroy(self->egl->x11.dpy, self->damage); self->damage = None; } - self->damage = XDamageCreate(self->display, window, XDamageReportNonEmpty); + self->window = window; + self->damage = XDamageCreate(self->egl->x11.dpy, window, XDamageReportNonEmpty); if(self->damage) { - XDamageSubtract(self->display, self->damage, None, None); + XDamageSubtract(self->egl->x11.dpy, self->damage, None, None); self->damaged = true; + self->track_type = GSR_DAMAGE_TRACK_WINDOW; return true; } else { fprintf(stderr, "gsr warning: gsr_damage_set_target_window: XDamageCreate failed\n"); + self->track_type = GSR_DAMAGE_TRACK_NONE; return false; } } -void gsr_damage_update(gsr_damage *self, XEvent *xev) { - if(self->damage_event == 0 || !self->damage) +bool gsr_damage_set_target_monitor(gsr_damage *self, const char *monitor_name) { + if(self->damage_event == 0) + return false; + + if(strcmp(self->monitor_name, monitor_name) == 0) + return true; + + if(self->damage) { + XDamageDestroy(self->egl->x11.dpy, self->damage); + self->damage = None; + } + + memset(&self->monitor, 0, sizeof(self->monitor)); + if(!get_monitor_by_name(self->egl, GSR_CONNECTION_X11, monitor_name, &self->monitor)) + fprintf(stderr, "gsr warning: gsr_damage_set_target_monitor: failed to find monitor: %s\n", monitor_name); + + self->window = DefaultRootWindow(self->egl->x11.dpy); + self->damage = XDamageCreate(self->egl->x11.dpy, self->window, XDamageReportNonEmpty); + if(self->damage) { + XDamageSubtract(self->egl->x11.dpy, self->damage, None, None); + self->damaged = true; + snprintf(self->monitor_name, sizeof(self->monitor_name), "%s", monitor_name); + self->track_type = GSR_DAMAGE_TRACK_MONITOR; + return true; + } else { + fprintf(stderr, "gsr warning: gsr_damage_set_target_monitor: XDamageCreate failed\n"); + self->track_type = GSR_DAMAGE_TRACK_NONE; + return false; + } +} + +static void gsr_damage_on_crtc_change(gsr_damage *self, XEvent *xev) { + const XRRCrtcChangeNotifyEvent *rr_crtc_change_event = (XRRCrtcChangeNotifyEvent*)xev; + if(rr_crtc_change_event->crtc == 0 || self->monitor.monitor_identifier == 0) + return; + + if(rr_crtc_change_event->crtc != self->monitor.monitor_identifier) + return; + + if(rr_crtc_change_event->width == 0 || rr_crtc_change_event->height == 0) return; - if(self->damage_event && xev->type == self->damage_event + XDamageNotify) { - XDamageNotifyEvent *de = (XDamageNotifyEvent*)xev; - XserverRegion region = XFixesCreateRegion(self->display, NULL, 0); - /* Subtract all the damage, repairing the window */ - XDamageSubtract(self->display, de->damage, None, region); - XFixesDestroyRegion(self->display, region); - XFlush(self->display); + if(rr_crtc_change_event->x != self->monitor.pos.x || rr_crtc_change_event->y != self->monitor.pos.y || + (int)rr_crtc_change_event->width != self->monitor.size.x || (int)rr_crtc_change_event->height != self->monitor.size.y) { + self->monitor.pos.x = rr_crtc_change_event->x; + self->monitor.pos.y = rr_crtc_change_event->y; + + self->monitor.size.x = rr_crtc_change_event->width; + self->monitor.size.y = rr_crtc_change_event->height; + } +} + +static void gsr_damage_on_output_change(gsr_damage *self, XEvent *xev) { + const XRROutputChangeNotifyEvent *rr_output_change_event = (XRROutputChangeNotifyEvent*)xev; + if(!rr_output_change_event->output || self->monitor.monitor_identifier == 0) + return; + + XRRScreenResources *screen_res = XRRGetScreenResources(self->egl->x11.dpy, DefaultRootWindow(self->egl->x11.dpy)); + if(!screen_res) + return; + + XRROutputInfo *out_info = XRRGetOutputInfo(self->egl->x11.dpy, screen_res, rr_output_change_event->output); + if(out_info && out_info->crtc && out_info->crtc == self->monitor.monitor_identifier) { + XRRCrtcInfo *crtc_info = XRRGetCrtcInfo(self->egl->x11.dpy, screen_res, out_info->crtc); + if(crtc_info && (crtc_info->x != self->monitor.pos.x || crtc_info->y != self->monitor.pos.y || + (int)crtc_info->width != self->monitor.size.x || (int)crtc_info->height != self->monitor.size.y)) + { + self->monitor.pos.x = crtc_info->x; + self->monitor.pos.y = crtc_info->y; + + self->monitor.size.x = crtc_info->width; + self->monitor.size.y = crtc_info->height; + } + + if(crtc_info) + XRRFreeCrtcInfo(crtc_info); + } + + if(out_info) + XRRFreeOutputInfo(out_info); + + XRRFreeScreenResources(screen_res); +} + +static void gsr_damage_on_randr_event(gsr_damage *self, XEvent *xev) { + const XRRNotifyEvent *rr_event = (XRRNotifyEvent*)xev; + switch(rr_event->subtype) { + case RRNotify_CrtcChange: + gsr_damage_on_crtc_change(self, xev); + break; + case RRNotify_OutputChange: + gsr_damage_on_output_change(self, xev); + break; + } +} + +static void gsr_damage_on_damage_event(gsr_damage *self, XEvent *xev) { + const XDamageNotifyEvent *de = (XDamageNotifyEvent*)xev; + XserverRegion region = XFixesCreateRegion(self->egl->x11.dpy, NULL, 0); + /* Subtract all the damage, repairing the window */ + XDamageSubtract(self->egl->x11.dpy, de->damage, None, region); + + if(self->track_type == GSR_DAMAGE_TRACK_WINDOW || (self->track_type == GSR_DAMAGE_TRACK_MONITOR && self->monitor.connector_id == 0)) { + self->damaged = true; + } else { + int num_rectangles = 0; + XRectangle *rectangles = XFixesFetchRegion(self->egl->x11.dpy, region, &num_rectangles); + if(rectangles) { + const gsr_rectangle monitor_region = { self->monitor.pos, self->monitor.size }; + for(int i = 0; i < num_rectangles; ++i) { + const gsr_rectangle damage_region = { (vec2i){rectangles[i].x, rectangles[i].y}, (vec2i){rectangles[i].width, rectangles[i].height} }; + self->damaged = rectangles_intersect(monitor_region, damage_region); + if(self->damaged) + break; + } + XFree(rectangles); + } + } + + XFixesDestroyRegion(self->egl->x11.dpy, region); + XFlush(self->egl->x11.dpy); +} + +static void gsr_damage_update_cursor(gsr_damage *self) { + Window dummy_window; + int dummy_i; + unsigned int dummy_u; + vec2i cursor_position = {0, 0}; + XQueryPointer(self->egl->x11.dpy, self->window, &dummy_window, &dummy_window, &dummy_i, &dummy_i, &cursor_position.x, &cursor_position.y, &dummy_u); + if(cursor_position.x != self->cursor_position.x || cursor_position.y != self->cursor_position.y) { + self->cursor_position = cursor_position; self->damaged = true; } } +void gsr_damage_update(gsr_damage *self, XEvent *xev) { + if(self->damage_event == 0 || self->track_type == GSR_DAMAGE_TRACK_NONE) + return; + + if(self->randr_event) { + if(xev->type == self->randr_event + RRScreenChangeNotify) + XRRUpdateConfiguration(xev); + + if(xev->type == self->randr_event + RRNotify) + gsr_damage_on_randr_event(self, xev); + } + + if(self->damage_event && xev->type == self->damage_event + XDamageNotify) + gsr_damage_on_damage_event(self, xev); + + if(self->track_cursor && !self->damaged) + gsr_damage_update_cursor(self); +} + bool gsr_damage_is_damaged(gsr_damage *self) { - return self->damage_event == 0 || !self->damage || self->damaged; + return self->damage_event == 0 || !self->damage || self->damaged || self->track_type == GSR_DAMAGE_TRACK_NONE; } void gsr_damage_clear(gsr_damage *self) { diff --git a/src/egl.c b/src/egl.c index d97ff0f..ea69796 100644 --- a/src/egl.c +++ b/src/egl.c @@ -152,6 +152,7 @@ static void store_x11_monitor(const gsr_monitor *monitor, void *userdata) { egl->x11.outputs[index].size = monitor->size; egl->x11.outputs[index].connector_id = monitor->connector_id; egl->x11.outputs[index].rotation = monitor->rotation; + egl->x11.outputs[index].monitor_identifier = monitor->monitor_identifier; ++egl->x11.num_outputs; } @@ -675,12 +676,23 @@ void gsr_egl_unload(gsr_egl *self) { memset(self, 0, sizeof(gsr_egl)); } -void gsr_egl_update(gsr_egl *self) { - if(!self->wayland.dpy) - return; - - // TODO: pselect on wl_display_get_fd before doing dispatch - wl_display_dispatch(self->wayland.dpy); +bool gsr_egl_update(gsr_egl *self) { + switch(gsr_egl_get_display_server(self)) { + case GSR_DISPLAY_SERVER_X11: { + if(XPending(self->x11.dpy)) { + XNextEvent(self->x11.dpy, &self->x11.xev); + return true; + } + return false; + } + case GSR_DISPLAY_SERVER_WAYLAND: { + // TODO: pselect on wl_display_get_fd before doing dispatch + const bool events_available = wl_display_dispatch_pending(self->wayland.dpy) > 0; + wl_display_flush(self->wayland.dpy); + return events_available; + } + } + return false; } void gsr_egl_swap_buffers(gsr_egl *self) { @@ -691,9 +703,16 @@ void gsr_egl_swap_buffers(gsr_egl *self) { } } -gsr_display_server gsr_egl_get_display_server(const gsr_egl *egl) { - if(egl->wayland.dpy) +gsr_display_server gsr_egl_get_display_server(const gsr_egl *self) { + if(self->wayland.dpy) return GSR_DISPLAY_SERVER_WAYLAND; else return GSR_DISPLAY_SERVER_X11; } + +XEvent* gsr_egl_get_event_data(gsr_egl *self) { + if(gsr_egl_get_display_server(self) == GSR_DISPLAY_SERVER_X11) + return &self->x11.xev; + else + return NULL; +} diff --git a/src/main.cpp b/src/main.cpp index c6e9909..2cefd0a 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -11,6 +11,7 @@ extern "C" { #include "../include/encoder/video/software.h" #include "../include/egl.h" #include "../include/utils.h" +#include "../include/damage.h" #include "../include/color_conversion.h" } @@ -1036,7 +1037,7 @@ static void usage_full() { //fprintf(stderr, " -pixfmt The pixel format to use for the output video. yuv420 is the most common format and is best supported, but the color is compressed, so colors can look washed out and certain colors of text can look bad. Use yuv444 for no color compression, but the video may not work everywhere and it may not work with hardware video decoding. Optional, set to 'yuv420' by default\n"); fprintf(stderr, " -o The output file path. If omitted then the encoded data is sent to stdout. Required in replay mode (when using -r).\n"); fprintf(stderr, " In replay mode this has to be a directory instead of a file.\n"); - fprintf(stderr, " The directory to the file is created (recursively) if it doesn't already exist.\n"); + fprintf(stderr, " Note: the directory to the file is created automatically if it doesn't already exist.\n"); fprintf(stderr, "\n"); fprintf(stderr, " -v Prints per second, fps updates. Optional, set to 'yes' by default.\n"); fprintf(stderr, "\n"); @@ -1853,8 +1854,8 @@ static void list_audio_devices_command() { _exit(0); } -static gsr_capture* create_capture_impl(const char *window_str, const char *screen_region, bool wayland, gsr_egl *egl, int fps, VideoCodec video_codec, gsr_color_range color_range, - bool record_cursor, bool track_damage, bool use_software_video_encoder, bool restore_portal_session, const char *portal_session_token_filepath, +static gsr_capture* create_capture_impl(std::string &window_str, const char *screen_region, bool wayland, gsr_egl *egl, int fps, VideoCodec video_codec, gsr_color_range color_range, + bool record_cursor, bool use_software_video_encoder, bool restore_portal_session, const char *portal_session_token_filepath, gsr_color_depth color_depth) { vec2i region_size = { 0, 0 }; @@ -1862,7 +1863,7 @@ static gsr_capture* create_capture_impl(const char *window_str, const char *scre bool follow_focused = false; gsr_capture *capture = nullptr; - if(strcmp(window_str, "focused") == 0) { + if(strcmp(window_str.c_str(), "focused") == 0) { if(wayland) { fprintf(stderr, "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\n"); _exit(2); @@ -1884,7 +1885,7 @@ static gsr_capture* create_capture_impl(const char *window_str, const char *scre } follow_focused = true; - } else if(strcmp(window_str, "portal") == 0) { + } else if(strcmp(window_str.c_str(), "portal") == 0) { #ifdef GSR_PORTAL // Desktop portal capture on x11 doesn't seem to be hardware accelerated if(!wayland) { @@ -1910,12 +1911,12 @@ static gsr_capture* create_capture_impl(const char *window_str, const char *scre fprintf(stderr, "Error: option '-w portal' used but GPU Screen Recorder was compiled without desktop portal support\n"); _exit(2); #endif - } else if(contains_non_hex_number(window_str)) { + } else if(contains_non_hex_number(window_str.c_str())) { if(monitor_capture_use_drm(egl, wayland)) { const bool is_x11 = gsr_egl_get_display_server(egl) == GSR_DISPLAY_SERVER_X11; const gsr_connection_type connection_type = is_x11 ? GSR_CONNECTION_X11 : GSR_CONNECTION_DRM; - if(strcmp(window_str, "screen") == 0) { + if(strcmp(window_str.c_str(), "screen") == 0) { FirstOutputCallback first_output; first_output.output_name = NULL; for_each_active_monitor_output(egl, connection_type, get_first_output, &first_output); @@ -1928,20 +1929,20 @@ static gsr_capture* create_capture_impl(const char *window_str, const char *scre } } else { gsr_monitor gmon; - if(!get_monitor_by_name(egl, connection_type, window_str, &gmon)) { - fprintf(stderr, "gsr error: display \"%s\" not found, expected one of:\n", window_str); + if(!get_monitor_by_name(egl, connection_type, window_str.c_str(), &gmon)) { + fprintf(stderr, "gsr error: display \"%s\" not found, expected one of:\n", window_str.c_str()); fprintf(stderr, " \"screen\"\n"); for_each_active_monitor_output(egl, connection_type, monitor_output_callback_print, NULL); _exit(1); } } } else { - if(strcmp(window_str, "screen") != 0 && strcmp(window_str, "screen-direct") != 0 && strcmp(window_str, "screen-direct-force") != 0) { + if(strcmp(window_str.c_str(), "screen") != 0 && strcmp(window_str.c_str(), "screen-direct") != 0 && strcmp(window_str.c_str(), "screen-direct-force") != 0) { gsr_monitor gmon; - if(!get_monitor_by_name(egl, GSR_CONNECTION_X11, window_str, &gmon)) { + if(!get_monitor_by_name(egl, GSR_CONNECTION_X11, window_str.c_str(), &gmon)) { const int screens_width = XWidthOfScreen(DefaultScreenOfDisplay(egl->x11.dpy)); const int screens_height = XWidthOfScreen(DefaultScreenOfDisplay(egl->x11.dpy)); - fprintf(stderr, "gsr error: display \"%s\" not found, expected one of:\n", window_str); + fprintf(stderr, "gsr error: display \"%s\" not found, expected one of:\n", window_str.c_str()); fprintf(stderr, " \"screen\" (%dx%d+%d+%d)\n", screens_width, screens_height, 0, 0); fprintf(stderr, " \"screen-direct\" (%dx%d+%d+%d)\n", screens_width, screens_height, 0, 0); fprintf(stderr, " \"screen-direct-force\" (%dx%d+%d+%d)\n", screens_width, screens_height, 0, 0); @@ -1952,8 +1953,8 @@ static gsr_capture* create_capture_impl(const char *window_str, const char *scre } if(egl->gpu_info.vendor == GSR_GPU_VENDOR_NVIDIA && !wayland) { - const char *capture_target = window_str; - bool direct_capture = strcmp(window_str, "screen-direct") == 0; + const char *capture_target = window_str.c_str(); + bool direct_capture = strcmp(window_str.c_str(), "screen-direct") == 0; if(direct_capture) { capture_target = "screen"; // TODO: Temporary disable direct capture because push model causes stuttering when it's direct capturing. This might be a nvfbc bug. This does not happen when using a compositor. @@ -1961,7 +1962,7 @@ static gsr_capture* create_capture_impl(const char *window_str, const char *scre fprintf(stderr, "Warning: screen-direct has temporary been disabled as it causes stuttering. This is likely a NvFBC bug. Falling back to \"screen\".\n"); } - if(strcmp(window_str, "screen-direct-force") == 0) { + if(strcmp(window_str.c_str(), "screen-direct-force") == 0) { direct_capture = true; capture_target = "screen"; } @@ -1983,7 +1984,7 @@ static gsr_capture* create_capture_impl(const char *window_str, const char *scre } else { gsr_capture_kms_params kms_params; kms_params.egl = egl; - kms_params.display_to_capture = window_str; + kms_params.display_to_capture = window_str.c_str(); kms_params.color_depth = color_depth; kms_params.color_range = color_range; kms_params.record_cursor = record_cursor; @@ -1999,9 +2000,9 @@ static gsr_capture* create_capture_impl(const char *window_str, const char *scre } errno = 0; - src_window_id = strtol(window_str, nullptr, 0); + src_window_id = strtol(window_str.c_str(), nullptr, 0); if(src_window_id == None || errno == EINVAL) { - fprintf(stderr, "Invalid window number %s\n", window_str); + fprintf(stderr, "Invalid window number %s\n", window_str.c_str()); usage(); } } @@ -2014,7 +2015,6 @@ static gsr_capture* create_capture_impl(const char *window_str, const char *scre xcomposite_params.region_size = region_size; xcomposite_params.color_range = color_range; xcomposite_params.record_cursor = record_cursor; - xcomposite_params.track_damage = track_damage; xcomposite_params.color_depth = color_depth; capture = gsr_capture_xcomposite_create(&xcomposite_params); if(!capture) @@ -2677,10 +2677,9 @@ int main(int argc, char **argv) { replay_buffer_size_secs += std::ceil(keyint); // Add a few seconds to account of lost packets because of non-keyframe packets skipped } - // TODO: Remove strdup - const char *window_str = strdup(args["-w"].value()); + std::string window_str = args["-w"].value(); - if(!restore_portal_session && strcmp(window_str, "portal") == 0) { + if(!restore_portal_session && strcmp(window_str.c_str(), "portal") == 0) { fprintf(stderr, "gsr info: option '-w portal' was used without '-restore-portal-session yes'. The previous screencast session will be ignored\n"); } @@ -2705,7 +2704,7 @@ int main(int argc, char **argv) { disable_prime_run(); } - if(strcmp(window_str, "portal") == 0 && is_using_prime_run()) { + if(strcmp(window_str.c_str(), "portal") == 0 && is_using_prime_run()) { fprintf(stderr, "Warning: use of prime-run with -w portal option is currently not supported. Disabling prime-run\n"); disable_prime_run(); } @@ -2715,7 +2714,7 @@ int main(int argc, char **argv) { _exit(1); } - const bool is_monitor_capture = strcmp(window_str, "focused") != 0 && strcmp(window_str, "portal") != 0 && contains_non_hex_number(window_str); + const bool is_monitor_capture = strcmp(window_str.c_str(), "focused") != 0 && strcmp(window_str.c_str(), "portal") != 0 && contains_non_hex_number(window_str.c_str()); gsr_egl egl; if(!gsr_egl_load(&egl, dpy, wayland, is_monitor_capture)) { fprintf(stderr, "gsr error: failed to load opengl\n"); @@ -2817,7 +2816,7 @@ int main(int argc, char **argv) { const char *screen_region = args["-s"].value(); - if(screen_region && strcmp(window_str, "focused") != 0) { + if(screen_region && strcmp(window_str.c_str(), "focused") != 0) { fprintf(stderr, "Error: option -s is only available when using -w focused\n"); usage(); } @@ -2900,7 +2899,7 @@ int main(int argc, char **argv) { const AVCodec *video_codec_f = select_video_codec_with_fallback(&video_codec, video_codec_to_use, file_extension.c_str(), use_software_video_encoder, &egl); const gsr_color_depth color_depth = video_codec_to_bit_depth(video_codec); - gsr_capture *capture = create_capture_impl(window_str, screen_region, wayland, &egl, fps, video_codec, color_range, record_cursor, framerate_mode == FramerateMode::CONTENT, use_software_video_encoder, restore_portal_session, portal_session_token_filepath, color_depth); + gsr_capture *capture = create_capture_impl(window_str, screen_region, wayland, &egl, fps, video_codec, color_range, record_cursor, use_software_video_encoder, restore_portal_session, portal_session_token_filepath, color_depth); // (Some?) livestreaming services require at least one audio track to work. // If not audio is provided then create one silent audio track. @@ -3087,7 +3086,6 @@ int main(int argc, char **argv) { } double fps_start_time = clock_get_monotonic_seconds(); - double frame_timer_start = fps_start_time - target_fps; // We want to capture the first frame immediately int fps_counter = 0; int damage_fps_counter = 0; @@ -3166,7 +3164,7 @@ int main(int argc, char **argv) { if(paused) { if(!audio_device.sound_device.handle) - usleep(timeout_ms * 1000); + av_usleep(timeout_ms * 1000); continue; } @@ -3229,7 +3227,7 @@ int main(int argc, char **argv) { } if(!audio_device.sound_device.handle) - usleep(timeout_ms * 1000); + av_usleep(timeout_ms * 1000); if(got_audio_data) { // TODO: Instead of converting audio, get float audio from alsa. Or does alsa do conversion internally to get this format? @@ -3290,13 +3288,14 @@ int main(int argc, char **argv) { audio_track.pts += audio_track.codec_context->frame_size; } } + av_usleep(5 * 1000); // 5 milliseconds } av_frame_free(&aframe); }); } // Set update_fps to 24 to test if duplicate/delayed frames cause video/audio desync or too fast/slow video. - const double update_fps = fps + 190; + const double update_fps = fps; bool should_stop_error = false; int64_t video_pts_counter = 0; @@ -3304,25 +3303,50 @@ int main(int argc, char **argv) { bool hdr_metadata_set = false; + const double damage_timeout_seconds = framerate_mode == FramerateMode::CONTENT ? 0.5 : 0.1; + bool use_damage_tracking = false; + gsr_damage damage; + memset(&damage, 0, sizeof(damage)); + if(gsr_egl_get_display_server(&egl) == GSR_DISPLAY_SERVER_X11) { + gsr_damage_init(&damage, &egl, record_cursor); + use_damage_tracking = true; + } + + if(is_monitor_capture) + gsr_damage_set_target_monitor(&damage, window_str.c_str()); + while(running) { - double frame_start = clock_get_monotonic_seconds(); + const double frame_start = clock_get_monotonic_seconds(); + while(gsr_egl_update(&egl)) { + gsr_capture_on_event(capture, &egl); + gsr_damage_update(&damage, gsr_egl_get_event_data(&egl)); + } gsr_capture_tick(capture, video_codec_context); + + if(!is_monitor_capture) { + Window damage_target_window = 0; + if(capture->get_window_id) + damage_target_window = capture->get_window_id(capture); + + if(damage_target_window != 0) + gsr_damage_set_target_window(&damage, damage_target_window); + } + should_stop_error = false; if(gsr_capture_should_stop(capture, &should_stop_error)) { running = 0; break; } - const bool damaged = !capture->is_damaged || capture->is_damaged(capture); + const bool damaged = !use_damage_tracking || gsr_damage_is_damaged(&damage); if(damaged) { ++damage_fps_counter; } ++fps_counter; double time_now = clock_get_monotonic_seconds(); - double frame_timer_elapsed = time_now - frame_timer_start; - double elapsed = time_now - fps_start_time; + const double elapsed = time_now - fps_start_time; if (elapsed >= 1.0) { if(verbose) { fprintf(stderr, "update fps: %d, damage fps: %d\n", fps_counter, damage_fps_counter); @@ -3332,52 +3356,47 @@ int main(int argc, char **argv) { damage_fps_counter = 0; } - double frame_time_overflow = frame_timer_elapsed - target_fps; - if (frame_time_overflow >= 0.0 && damaged) { - if(capture->clear_damage) - capture->clear_damage(capture); - frame_time_overflow = std::min(frame_time_overflow, target_fps); - frame_timer_start = time_now - frame_time_overflow; - - const double this_video_frame_time = clock_get_monotonic_seconds() - paused_time_offset; - const int64_t expected_frames = std::round((this_video_frame_time - record_start_time) / target_fps); - const int num_frames = framerate_mode == FramerateMode::CONSTANT ? std::max((int64_t)0LL, expected_frames - video_pts_counter) : 1; - - if(num_frames > 0 && !paused) { - egl.glClear(0); - gsr_capture_capture(capture, video_frame, &color_conversion); - gsr_egl_swap_buffers(&egl); - - gsr_video_encoder_copy_textures_to_frame(video_encoder, video_frame); - - if(hdr && !hdr_metadata_set && replay_buffer_size_secs == -1 && add_hdr_metadata_to_video_stream(capture, video_stream)) - hdr_metadata_set = true; - - // TODO: Check if duplicate frame can be saved just by writing it with a different pts instead of sending it again - for(int i = 0; i < num_frames; ++i) { - if(framerate_mode == FramerateMode::CONSTANT) { - video_frame->pts = video_pts_counter + i; - } else { - video_frame->pts = (this_video_frame_time - record_start_time) * (double)AV_TIME_BASE; - const bool same_pts = video_frame->pts == video_prev_pts; - video_prev_pts = video_frame->pts; - if(same_pts) - continue; - } + const double this_video_frame_time = clock_get_monotonic_seconds() - paused_time_offset; + const int64_t expected_frames = std::round((this_video_frame_time - record_start_time) / target_fps); + int num_frames = std::max((int64_t)0LL, expected_frames - video_pts_counter); + const double num_frames_seconds = num_frames * target_fps; + if((damaged || num_frames_seconds >= damage_timeout_seconds) && !paused/* && fps_counter < fps + 100*/) { + gsr_damage_clear(&damage); - int ret = avcodec_send_frame(video_codec_context, video_frame); - if(ret == 0) { - // TODO: Move to separate thread because this could write to network (for example when livestreaming) - receive_frames(video_codec_context, VIDEO_STREAM_INDEX, video_stream, video_frame->pts, av_format_context, - record_start_time, frame_data_queue, replay_buffer_size_secs, frames_erased, write_output_mutex, paused_time_offset); - } else { - fprintf(stderr, "Error: avcodec_send_frame failed, error: %s\n", av_error_to_string(ret)); - } + egl.glClear(0); + gsr_capture_capture(capture, video_frame, &color_conversion); + gsr_egl_swap_buffers(&egl); + + gsr_video_encoder_copy_textures_to_frame(video_encoder, video_frame); + + if(hdr && !hdr_metadata_set && replay_buffer_size_secs == -1 && add_hdr_metadata_to_video_stream(capture, video_stream)) + hdr_metadata_set = true; + + // TODO: Check if duplicate frame can be saved just by writing it with a different pts instead of sending it again + const int num_frames_to_encode = framerate_mode == FramerateMode::CONSTANT ? num_frames : 1; + for(int i = 0; i < num_frames_to_encode; ++i) { + if(framerate_mode == FramerateMode::CONSTANT) { + video_frame->pts = video_pts_counter + i; + } else { + video_frame->pts = (this_video_frame_time - record_start_time) * (double)AV_TIME_BASE; + const bool same_pts = video_frame->pts == video_prev_pts; + video_prev_pts = video_frame->pts; + if(same_pts) + continue; } - gsr_capture_capture_end(capture, video_frame); - video_pts_counter += num_frames; + int ret = avcodec_send_frame(video_codec_context, video_frame); + if(ret == 0) { + // TODO: Move to separate thread because this could write to network (for example when livestreaming) + receive_frames(video_codec_context, VIDEO_STREAM_INDEX, video_stream, video_frame->pts, av_format_context, + record_start_time, frame_data_queue, replay_buffer_size_secs, frames_erased, write_output_mutex, paused_time_offset); + } else { + fprintf(stderr, "Error: avcodec_send_frame failed, error: %s\n", av_error_to_string(ret)); + } } + + gsr_capture_capture_end(capture, video_frame); + video_pts_counter += num_frames; } if(toggle_pause == 1) { @@ -3409,11 +3428,15 @@ int main(int argc, char **argv) { save_replay_async(video_codec_context, VIDEO_STREAM_INDEX, audio_tracks, frame_data_queue, frames_erased, filename, container_format, file_extension, write_output_mutex, date_folders, hdr, capture); } - double frame_end = clock_get_monotonic_seconds(); - double frame_sleep_fps = 1.0 / update_fps; - double sleep_time = frame_sleep_fps - (frame_end - frame_start); - if(sleep_time > 0.0) - usleep(sleep_time * 1000.0 * 1000.0); + const double frame_end = clock_get_monotonic_seconds(); + const double frame_sleep_fps = 1.0 / update_fps; + const double sleep_time = frame_sleep_fps - (frame_end - frame_start); + if(sleep_time > 0.0) { + if(damaged) + av_usleep(sleep_time * 1000.0 * 1000.0); + else + av_usleep(2 * 1000.0); // 2 milliseconds + } } running = 0; @@ -3445,6 +3468,7 @@ int main(int argc, char **argv) { if(replay_buffer_size_secs == -1 && !(output_format->flags & AVFMT_NOFILE)) avio_close(av_format_context->pb); + gsr_damage_deinit(&damage); gsr_color_conversion_deinit(&color_conversion); gsr_video_encoder_destroy(video_encoder, video_codec_context); gsr_capture_destroy(capture, video_codec_context); @@ -3458,7 +3482,6 @@ int main(int argc, char **argv) { } //av_frame_free(&video_frame); - free((void*)window_str); free(empty_audio); // We do an _exit here because cuda uses at_exit to do _something_ that causes the program to freeze, // but only on some nvidia driver versions on some gpus (RTX?), and _exit exits the program without calling diff --git a/src/utils.c b/src/utils.c index 20b640c..3e4138a 100644 --- a/src/utils.c +++ b/src/utils.c @@ -91,7 +91,7 @@ void for_each_active_monitor_output_x11_not_cached(Display *display, active_moni .size = { .x = (int)crt_info->width, .y = (int)crt_info->height }, .connector_id = x11_output_get_connector_id(display, screen_res->outputs[i], randr_connector_id_atom), .rotation = x11_rotation_to_gsr_rotation(crt_info->rotation), - .monitor_identifier = 0 + .monitor_identifier = out_info->crtc }; callback(&monitor, userdata); } @@ -116,7 +116,7 @@ void for_each_active_monitor_output_x11(const gsr_egl *egl, active_monitor_callb .size = output->size, .connector_id = output->connector_id, .rotation = output->rotation, - .monitor_identifier = 0 + .monitor_identifier = output->monitor_identifier }; callback(&monitor, userdata); } -- cgit v1.2.3