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 --- README.md | 1 + TODO | 8 +- include/capture/capture.h | 8 +- include/capture/xcomposite.h | 1 - include/damage.h | 23 ++++- include/egl.h | 8 +- include/utils.h | 2 +- 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 +- 14 files changed, 440 insertions(+), 208 deletions(-) diff --git a/README.md b/README.md index e13ad71..74904b1 100644 --- a/README.md +++ b/README.md @@ -68,6 +68,7 @@ These are the dependencies needed to build GPU Screen Recorder: * libdrm * libcap * wayland-client +* wayland-egl ## Runtime dependencies There are also additional dependencies needed at runtime depending on your GPU vendor: diff --git a/TODO b/TODO index 6e15e40..2781db4 100644 --- a/TODO +++ b/TODO @@ -45,8 +45,6 @@ Use separate plane (which has offset and pitch) from combined plane instead of t Both twitch and youtube support variable bitrate but twitch recommends constant bitrate to reduce stream buffering/dropped frames when going from low motion to high motion: https://help.twitch.tv/s/article/broadcasting-guidelines?language=en_US. Info for youtube: https://support.google.com/youtube/answer/2853702?hl=en#zippy=%2Cvariable-bitrate-with-custom-stream-keys-in-live-control-room%2Ck-p-fps%2Cp-fps. -Limit fps recording with x damage. This is good when running replay mode 24/7 and being afk or when not much is happening on the screen. - On nvidia some games apparently causes the game to appear to stutter (without dropping fps) when recording a monitor but not using when using direct screen capture. Observed in Deus Ex and Apex Legends. @@ -119,8 +117,6 @@ Support vfr matching games exact fps all the time. On x11 use damage tracking, o Support selecting which gpu to use. This can be done in egl with eglQueryDevicesEXT and then eglGetPlatformDisplayEXT. This will automatically work on AMD and Intel as vaapi uses the same device. On nvidia we need to use eglQueryDeviceAttribEXT with EGL_CUDA_DEVICE_NV. Maybe on glx (nvidia x11 nvfbc) we need to use __NV_PRIME_RENDER_OFFLOAD, __NV_PRIME_RENDER_OFFLOAD_PROVIDER, __GLX_VENDOR_LIBRARY_NAME, __VK_LAYER_NV_optimus, VK_ICD_FILENAMES instead. Just look at prime-run /usr/bin/prime-run. -Remove is_damaged and clear_damage and return a value from capture function instead that states if the image has been updated or not. - When adding support for steam deck, add option to send video to another computer. New gpu screen recorder gui should have the option to cut the video directly, maybe running an ffmpeg command or implementing that ourselves. Only support gpu screen recorder video files. @@ -158,4 +154,6 @@ Enable 2-pass encoding. Add vbr/cbr option. -Restart replay/update video resolution if monitor resolution changes. \ No newline at end of file +Restart replay/update video resolution if monitor resolution changes. + +Support damage tracking on wayland. diff --git a/include/capture/capture.h b/include/capture/capture.h index 278c431..7a42909 100644 --- a/include/capture/capture.h +++ b/include/capture/capture.h @@ -4,6 +4,7 @@ #include "../color_conversion.h" #include #include +#include typedef struct AVCodecContext AVCodecContext; typedef struct AVStream AVStream; @@ -15,15 +16,15 @@ typedef struct AVContentLightMetadata AVContentLightMetadata; struct gsr_capture { /* These methods should not be called manually. Call gsr_capture_* instead */ int (*start)(gsr_capture *cap, AVCodecContext *video_codec_context, AVFrame *frame); - void (*tick)(gsr_capture *cap, AVCodecContext *video_codec_context); /* can be NULL */ - bool (*is_damaged)(gsr_capture *cap); /* can be NULL */ - void (*clear_damage)(gsr_capture *cap); /* can be NULL */ + void (*on_event)(gsr_capture *cap, gsr_egl *egl); /* can be NULL */ + void (*tick)(gsr_capture *cap, AVCodecContext *video_codec_context); /* can be NULL. If there is an event then |on_event| is called before this */ bool (*should_stop)(gsr_capture *cap, bool *err); /* can be NULL. If NULL, return false */ int (*capture)(gsr_capture *cap, AVFrame *frame, gsr_color_conversion *color_conversion); void (*capture_end)(gsr_capture *cap, AVFrame *frame); /* can be NULL */ gsr_source_color (*get_source_color)(gsr_capture *cap); bool (*uses_external_image)(gsr_capture *cap); /* can be NULL. If NULL, return false */ bool (*set_hdr_metadata)(gsr_capture *cap, AVMasteringDisplayMetadata *mastering_display_metadata, AVContentLightMetadata *light_metadata); /* can be NULL. If NULL, return false */ + uint64_t (*get_window_id)(gsr_capture *cap); /* can be NULL. Returns 0 if unknown */ void (*destroy)(gsr_capture *cap, AVCodecContext *video_codec_context); void *priv; /* can be NULL */ @@ -31,6 +32,7 @@ struct gsr_capture { }; int gsr_capture_start(gsr_capture *cap, AVCodecContext *video_codec_context, AVFrame *frame); +void gsr_capture_on_event(gsr_capture *cap, gsr_egl *egl); void gsr_capture_tick(gsr_capture *cap, AVCodecContext *video_codec_context); bool gsr_capture_should_stop(gsr_capture *cap, bool *err); int gsr_capture_capture(gsr_capture *cap, AVFrame *frame, gsr_color_conversion *color_conversion); diff --git a/include/capture/xcomposite.h b/include/capture/xcomposite.h index 98b8766..8c87404 100644 --- a/include/capture/xcomposite.h +++ b/include/capture/xcomposite.h @@ -11,7 +11,6 @@ typedef struct { vec2i region_size; /* This is currently only used with |follow_focused| */ gsr_color_range color_range; bool record_cursor; - bool track_damage; gsr_color_depth color_depth; } gsr_capture_xcomposite_params; diff --git a/include/damage.h b/include/damage.h index 8f89e1d..68cd0d2 100644 --- a/include/damage.h +++ b/include/damage.h @@ -1,24 +1,43 @@ #ifndef GSR_DAMAGE_H #define GSR_DAMAGE_H +#include "utils.h" #include #include typedef struct _XDisplay Display; typedef union _XEvent XEvent; +typedef enum { + GSR_DAMAGE_TRACK_NONE, + GSR_DAMAGE_TRACK_WINDOW, + GSR_DAMAGE_TRACK_MONITOR +} gsr_damage_track_type; + typedef struct { - Display *display; + gsr_egl *egl; + bool track_cursor; + gsr_damage_track_type track_type; + int damage_event; int damage_error; + uint64_t window; uint64_t damage; bool damaged; + + int randr_event; + int randr_error; + + vec2i cursor_position; /* Relative to |window| */ + gsr_monitor monitor; + char monitor_name[32]; } gsr_damage; -bool gsr_damage_init(gsr_damage *self, Display *display); +bool gsr_damage_init(gsr_damage *self, gsr_egl *egl, bool track_cursor); void gsr_damage_deinit(gsr_damage *self); bool gsr_damage_set_target_window(gsr_damage *self, uint64_t window); +bool gsr_damage_set_target_monitor(gsr_damage *self, const char *monitor_name); void gsr_damage_update(gsr_damage *self, XEvent *xev); /* Also returns true if damage tracking is not available */ bool gsr_damage_is_damaged(gsr_damage *self); diff --git a/include/egl.h b/include/egl.h index d9d555a..9e50ad0 100644 --- a/include/egl.h +++ b/include/egl.h @@ -156,6 +156,7 @@ typedef struct { vec2i size; uint32_t connector_id; gsr_monitor_rotation rotation; + uint32_t monitor_identifier; /* crtc id */ } gsr_x11_output; typedef struct { @@ -163,6 +164,7 @@ typedef struct { Window window; gsr_x11_output outputs[GSR_MAX_OUTPUTS]; int num_outputs; + XEvent xev; } gsr_x11; typedef struct { @@ -311,10 +313,12 @@ struct gsr_egl { bool gsr_egl_load(gsr_egl *self, Display *dpy, bool wayland, bool is_monitor_capture); void gsr_egl_unload(gsr_egl *self); -void gsr_egl_update(gsr_egl *self); +/* Returns true if an event is available */ +bool gsr_egl_update(gsr_egl *self); /* Does opengl swap with egl or glx, depending on which one is active */ void gsr_egl_swap_buffers(gsr_egl *self); -gsr_display_server gsr_egl_get_display_server(const gsr_egl *egl); +gsr_display_server gsr_egl_get_display_server(const gsr_egl *self); +XEvent* gsr_egl_get_event_data(gsr_egl *self); #endif /* GSR_EGL_H */ diff --git a/include/utils.h b/include/utils.h index cadde8f..99503e2 100644 --- a/include/utils.h +++ b/include/utils.h @@ -14,7 +14,7 @@ typedef struct { vec2i size; uint32_t connector_id; /* Only on x11 and drm */ gsr_monitor_rotation rotation; /* Only on x11 and wayland */ - uint32_t monitor_identifier; /* Only on drm and wayland */ + uint32_t monitor_identifier; /* On x11 this is the crtc id */ } gsr_monitor; typedef struct { 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