From ab78e5687297ff5c27dc8b33e7d9f93a873af34f Mon Sep 17 00:00:00 2001 From: dec05eba Date: Fri, 21 Jun 2024 22:18:23 +0200 Subject: Add -fm 'content' option to match fps to captured content, only x11 window capture currently supported --- README.md | 6 +-- TODO | 2 + include/capture/capture.h | 1 + include/capture/xcomposite.h | 7 +++ include/cursor.h | 6 ++- meson.build | 2 + project.conf | 4 +- src/capture/xcomposite.c | 101 +++++++++++++++++++++++++++++++++-------- src/capture/xcomposite_cuda.c | 6 +++ src/capture/xcomposite_vaapi.c | 6 +++ src/cursor.c | 81 +++++++++++++++++++++++++++++---- src/main.cpp | 33 ++++++++++---- 12 files changed, 214 insertions(+), 41 deletions(-) diff --git a/README.md b/README.md index 93a1a03..603eadf 100644 --- a/README.md +++ b/README.md @@ -56,7 +56,7 @@ If you install GPU Screen Recorder flatpak, which is the gtk gui version then yo libglvnd (which provides libgl and libegl)\ mesa\ ffmpeg (libavcodec, libavformat, libavutil, libswresample, libavfilter)\ -x11 (libx11, libxcomposite, libxrandr, xfixes)\ +x11 (libx11, libxcomposite, libxrandr, xfixes, xi2)\ libpulse\ vaapi (libva, libva-mesa-driver)\ libdrm\ @@ -66,7 +66,7 @@ wayland-client libglvnd (which provides libgl and libegl)\ mesa\ ffmpeg (libavcodec, libavformat, libavutil, libswresample, libavfilter)\ -x11 (libx11, libxcomposite, libxrandr, xfixes)\ +x11 (libx11, libxcomposite, libxrandr, xfixes, xi2)\ libpulse\ vaapi (libva, intel-media-driver/libva-intel-driver)\ libdrm\ @@ -75,7 +75,7 @@ wayland-client ## NVIDIA libglvnd (which provides libgl and libegl)\ ffmpeg (libavcodec, libavformat, libavutil, libswresample, libavfilter)\ -x11 (libx11, libxcomposite, libxrandr, xfixes)\ +x11 (libx11, libxcomposite, libxrandr, xfixes, xi2)\ libpulse\ cuda runtime (libcuda.so.1) (libnvidia-compute)\ nvenc (libnvidia-encode)\ diff --git a/TODO b/TODO index 8b62029..05d56a5 100644 --- a/TODO +++ b/TODO @@ -131,3 +131,5 @@ Flac is disabled because the frame sizes are too large which causes big audio/vi Add 10-bit capture option. This is good because it reduces banding and quality in very dark areas while reducing the file size compared to doing the same thing with 8-bits. Enable b-frames. + +Support vfr matching games exact fps all the time. On x11 use damage tracking, on wayland? maybe there is drm plane damage tracking. But that may not be accurate as the compositor may update it every monitor hz anyways. On wayland maybe only support it for desktop portal + pipewire capture. diff --git a/include/capture/capture.h b/include/capture/capture.h index 2eb8e42..8719c40 100644 --- a/include/capture/capture.h +++ b/include/capture/capture.h @@ -21,6 +21,7 @@ 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 (*consume_damage)(gsr_capture *cap); /* can be NULL */ bool (*should_stop)(gsr_capture *cap, bool *err); /* can be NULL */ int (*capture)(gsr_capture *cap, AVFrame *frame); void (*capture_end)(gsr_capture *cap, AVFrame *frame); /* can be NULL */ diff --git a/include/capture/xcomposite.h b/include/capture/xcomposite.h index e933c35..83731fa 100644 --- a/include/capture/xcomposite.h +++ b/include/capture/xcomposite.h @@ -15,6 +15,7 @@ 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_capture_xcomposite_params; typedef struct { @@ -37,6 +38,11 @@ typedef struct { Atom net_active_window_atom; gsr_cursor cursor; + + int damage_event; + int damage_error; + XID damage; + bool damaged; } gsr_capture_xcomposite; void gsr_capture_xcomposite_init(gsr_capture_xcomposite *self, const gsr_capture_xcomposite_params *params); @@ -44,6 +50,7 @@ void gsr_capture_xcomposite_init(gsr_capture_xcomposite *self, const gsr_capture int gsr_capture_xcomposite_start(gsr_capture_xcomposite *self, AVCodecContext *video_codec_context, AVFrame *frame); void gsr_capture_xcomposite_stop(gsr_capture_xcomposite *self); void gsr_capture_xcomposite_tick(gsr_capture_xcomposite *self, AVCodecContext *video_codec_context); +bool gsr_capture_xcomposite_consume_damage(gsr_capture_xcomposite *self); bool gsr_capture_xcomposite_should_stop(gsr_capture_xcomposite *self, bool *err); int gsr_capture_xcomposite_capture(gsr_capture_xcomposite *self, AVFrame *frame); diff --git a/include/cursor.h b/include/cursor.h index b1ec6bd..2f26dfd 100644 --- a/include/cursor.h +++ b/include/cursor.h @@ -8,6 +8,7 @@ typedef struct { gsr_egl *egl; Display *display; int x_fixes_event_base; + int xi_opcode; unsigned int texture_id; vec2i size; @@ -15,12 +16,15 @@ typedef struct { vec2i position; bool cursor_image_set; + bool visible; + bool cursor_moved; } gsr_cursor; int gsr_cursor_init(gsr_cursor *self, gsr_egl *egl, Display *display); void gsr_cursor_deinit(gsr_cursor *self); -void gsr_cursor_update(gsr_cursor *self, XEvent *xev); +/* Returns true if the cursor image has updated or if the cursor has moved */ +bool gsr_cursor_update(gsr_cursor *self, XEvent *xev); void gsr_cursor_tick(gsr_cursor *self, Window relative_to); #endif /* GSR_CURSOR_H */ diff --git a/meson.build b/meson.build index e41cfa0..a188f16 100644 --- a/meson.build +++ b/meson.build @@ -38,6 +38,8 @@ dep = [ dependency('xcomposite'), dependency('xrandr'), dependency('xfixes'), + dependency('xdamage'), + dependency('xi'), dependency('libpulse'), dependency('libswresample'), dependency('libavfilter'), diff --git a/project.conf b/project.conf index 9940827..a7e2757 100644 --- a/project.conf +++ b/project.conf @@ -16,6 +16,8 @@ x11 = ">=1" xcomposite = ">=0.2" xrandr = ">=1" xfixes = ">=2" +xdamage = ">=1" +xi = ">=1" libpulse = ">=13" libswresample = ">=3" libavfilter = ">=5" @@ -23,4 +25,4 @@ libva = ">=1" libcap = ">=2" libdrm = ">=2" wayland-egl = ">=15" -wayland-client = ">=1" +wayland-client = ">=1" \ No newline at end of file diff --git a/src/capture/xcomposite.c b/src/capture/xcomposite.c index 59df070..c68acaa 100644 --- a/src/capture/xcomposite.c +++ b/src/capture/xcomposite.c @@ -6,6 +6,7 @@ #include #include #include +#include #include #include #include @@ -36,6 +37,23 @@ static Window get_focused_window(Display *display, Atom net_active_window_atom) return None; } +static void gsr_capture_xcomposite_setup_damage(gsr_capture_xcomposite *self, Window window) { + if(self->damage_event == 0) + return; + + if(self->damage) { + XDamageDestroy(self->params.egl->x11.dpy, self->damage); + self->damage = None; + } + + self->damage = XDamageCreate(self->params.egl->x11.dpy, window, XDamageReportNonEmpty); + if(self->damage) { + XDamageSubtract(self->params.egl->x11.dpy, self->damage, None, None); + } else { + fprintf(stderr, "gsr warning: gsr_capture_xcomposite_setup_damage: XDamageCreate failed\n"); + } +} + int gsr_capture_xcomposite_start(gsr_capture_xcomposite *self, AVCodecContext *video_codec_context, AVFrame *frame) { self->base.video_codec_context = video_codec_context; self->base.egl = self->params.egl; @@ -51,6 +69,20 @@ int gsr_capture_xcomposite_start(gsr_capture_xcomposite *self, AVCodecContext *v self->window = self->params.window; } + if(self->params.track_damage) { + if(!XDamageQueryExtension(self->params.egl->x11.dpy, &self->damage_event, &self->damage_error)) { + fprintf(stderr, "gsr warning: gsr_capture_xcomposite_start: XDamage is not supported by your X11 server\n"); + self->damage_event = 0; + self->damage_error = 0; + } + } else { + self->damage_event = 0; + self->damage_error = 0; + } + + self->damaged = true; + gsr_capture_xcomposite_setup_damage(self, self->window); + /* TODO: Do these in tick, and allow error if follow_focused */ XWindowAttributes attr; @@ -130,6 +162,11 @@ int gsr_capture_xcomposite_start(gsr_capture_xcomposite *self, AVCodecContext *v } void gsr_capture_xcomposite_stop(gsr_capture_xcomposite *self) { + if(self->damage) { + XDamageDestroy(self->params.egl->x11.dpy, self->damage); + self->damage = None; + } + window_texture_deinit(&self->window_texture); gsr_cursor_deinit(&self->cursor); gsr_capture_base_stop(&self->base); @@ -180,7 +217,20 @@ void gsr_capture_xcomposite_tick(gsr_capture_xcomposite *self, AVCodecContext *v } } - gsr_cursor_update(&self->cursor, &self->xev); + if(self->damage_event && self->xev.type == self->damage_event + XDamageNotify) { + XDamageNotifyEvent *de = (XDamageNotifyEvent*)&self->xev; + XserverRegion region = XFixesCreateRegion(self->params.egl->x11.dpy, NULL, 0); + // Subtract all the damage, repairing the window + XDamageSubtract(self->params.egl->x11.dpy, de->damage, None, region); + XFixesDestroyRegion(self->params.egl->x11.dpy, region); + self->damaged = true; + } + + if(gsr_cursor_update(&self->cursor, &self->xev)) { + if(self->params.record_cursor && self->cursor.visible) { + self->damaged = true; + } + } } if(self->params.follow_focused && !self->follow_focused_initialized) { @@ -207,6 +257,7 @@ void gsr_capture_xcomposite_tick(gsr_capture_xcomposite *self, AVCodecContext *v window_texture_deinit(&self->window_texture); window_texture_init(&self->window_texture, self->params.egl->x11.dpy, self->window, self->params.egl); // TODO: Do not do the below window_texture_on_resize after this + gsr_capture_xcomposite_setup_damage(self, self->window); } } @@ -230,6 +281,18 @@ void gsr_capture_xcomposite_tick(gsr_capture_xcomposite *self, AVCodecContext *v self->params.egl->glBindTexture(GL_TEXTURE_2D, 0); gsr_color_conversion_clear(&self->base.color_conversion); + gsr_capture_xcomposite_setup_damage(self, self->window); + } +} + +bool gsr_capture_xcomposite_consume_damage(gsr_capture_xcomposite *self) { + if(self->damage_event) { + const bool damaged = self->damaged; + self->damaged = false; + //fprintf(stderr, "consume: %s\n", damaged ? "yes" : "no"); + return damaged; + } else { + return true; } } @@ -251,36 +314,36 @@ int gsr_capture_xcomposite_capture(gsr_capture_xcomposite *self, AVFrame *frame) const int target_x = max_int(0, frame->width / 2 - self->texture_size.x / 2); const int target_y = max_int(0, frame->height / 2 - self->texture_size.y / 2); - // TODO: Can we do this a better way than to call it every capture? - if(self->params.record_cursor) - gsr_cursor_tick(&self->cursor, self->window); - const vec2i cursor_pos = { target_x + self->cursor.position.x - self->cursor.hotspot.x, target_y + self->cursor.position.y - self->cursor.hotspot.y }; - const bool cursor_inside_window = - cursor_pos.x + self->cursor.size.x >= target_x && - cursor_pos.x <= target_x + self->texture_size.x && - cursor_pos.y + self->cursor.size.y >= target_y && - cursor_pos.y <= target_y + self->texture_size.y; - gsr_color_conversion_draw(&self->base.color_conversion, window_texture_get_opengl_texture_id(&self->window_texture), (vec2i){target_x, target_y}, self->texture_size, (vec2i){0, 0}, self->texture_size, 0.0f, false); - if(cursor_inside_window && self->params.record_cursor) { - self->base.egl->glEnable(GL_SCISSOR_TEST); - self->base.egl->glScissor(target_x, target_y, self->texture_size.x, self->texture_size.y); + if(self->params.record_cursor && self->cursor.visible) { + gsr_cursor_tick(&self->cursor, self->window); - gsr_color_conversion_draw(&self->base.color_conversion, self->cursor.texture_id, - cursor_pos, self->cursor.size, - (vec2i){0, 0}, self->cursor.size, - 0.0f, false); + const bool cursor_inside_window = + cursor_pos.x + self->cursor.size.x >= target_x && + cursor_pos.x <= target_x + self->texture_size.x && + cursor_pos.y + self->cursor.size.y >= target_y && + cursor_pos.y <= target_y + self->texture_size.y; - self->base.egl->glDisable(GL_SCISSOR_TEST); + if(cursor_inside_window) { + self->base.egl->glEnable(GL_SCISSOR_TEST); + self->base.egl->glScissor(target_x, target_y, self->texture_size.x, self->texture_size.y); + + gsr_color_conversion_draw(&self->base.color_conversion, self->cursor.texture_id, + cursor_pos, self->cursor.size, + (vec2i){0, 0}, self->cursor.size, + 0.0f, false); + + self->base.egl->glDisable(GL_SCISSOR_TEST); + } } self->params.egl->eglSwapBuffers(self->params.egl->egl_display, self->params.egl->egl_surface); diff --git a/src/capture/xcomposite_cuda.c b/src/capture/xcomposite_cuda.c index 6e13d2a..01e1f12 100644 --- a/src/capture/xcomposite_cuda.c +++ b/src/capture/xcomposite_cuda.c @@ -76,6 +76,11 @@ static void gsr_capture_xcomposite_cuda_tick(gsr_capture *cap, AVCodecContext *v gsr_capture_xcomposite_tick(&cap_xcomp->xcomposite, video_codec_context); } +static bool gsr_capture_xcomposite_cuda_consume_damage(gsr_capture *cap) { + gsr_capture_xcomposite_cuda *cap_xcomp = cap->priv; + return gsr_capture_xcomposite_consume_damage(&cap_xcomp->xcomposite); +} + static bool gsr_capture_xcomposite_cuda_should_stop(gsr_capture *cap, bool *err) { gsr_capture_xcomposite_cuda *cap_xcomp = cap->priv; return gsr_capture_xcomposite_should_stop(&cap_xcomp->xcomposite, err); @@ -144,6 +149,7 @@ gsr_capture* gsr_capture_xcomposite_cuda_create(const gsr_capture_xcomposite_cud *cap = (gsr_capture) { .start = gsr_capture_xcomposite_cuda_start, .tick = gsr_capture_xcomposite_cuda_tick, + .consume_damage = gsr_capture_xcomposite_cuda_consume_damage, .should_stop = gsr_capture_xcomposite_cuda_should_stop, .capture = gsr_capture_xcomposite_cuda_capture, .capture_end = NULL, diff --git a/src/capture/xcomposite_vaapi.c b/src/capture/xcomposite_vaapi.c index 8c9c56c..b8d4544 100644 --- a/src/capture/xcomposite_vaapi.c +++ b/src/capture/xcomposite_vaapi.c @@ -43,6 +43,11 @@ static void gsr_capture_xcomposite_vaapi_tick(gsr_capture *cap, AVCodecContext * gsr_capture_xcomposite_tick(&cap_xcomp->xcomposite, video_codec_context); } +static bool gsr_capture_xcomposite_vaapi_consume_damage(gsr_capture *cap) { + gsr_capture_xcomposite_vaapi *cap_xcomp = cap->priv; + return gsr_capture_xcomposite_consume_damage(&cap_xcomp->xcomposite); +} + static bool gsr_capture_xcomposite_vaapi_should_stop(gsr_capture *cap, bool *err) { gsr_capture_xcomposite_vaapi *cap_xcomp = cap->priv; return gsr_capture_xcomposite_should_stop(&cap_xcomp->xcomposite, err); @@ -98,6 +103,7 @@ gsr_capture* gsr_capture_xcomposite_vaapi_create(const gsr_capture_xcomposite_va *cap = (gsr_capture) { .start = gsr_capture_xcomposite_vaapi_start, .tick = gsr_capture_xcomposite_vaapi_tick, + .consume_damage = gsr_capture_xcomposite_vaapi_consume_damage, .should_stop = gsr_capture_xcomposite_vaapi_should_stop, .capture = gsr_capture_xcomposite_vaapi_capture, .capture_end = NULL, diff --git a/src/cursor.c b/src/cursor.c index 737c33b..9825ad2 100644 --- a/src/cursor.c +++ b/src/cursor.c @@ -6,10 +6,15 @@ #include #include +#include +#include -static bool gsr_cursor_set_from_x11_cursor_image(gsr_cursor *self, XFixesCursorImage *x11_cursor_image) { +// TODO: Test cursor visibility with XFixesHideCursor + +static bool gsr_cursor_set_from_x11_cursor_image(gsr_cursor *self, XFixesCursorImage *x11_cursor_image, bool *visible) { uint8_t *cursor_data = NULL; uint8_t *out = NULL; + *visible = false; if(!x11_cursor_image) goto err; @@ -34,8 +39,11 @@ static bool gsr_cursor_set_from_x11_cursor_image(gsr_cursor *self, XFixesCursorI uint32_t pixel = *pixels++; uint8_t *in = (uint8_t*)&pixel; uint8_t alpha = in[3]; - if(alpha == 0) + if(alpha == 0) { alpha = 1; + } else { + *visible = true; + } *out++ = (unsigned)*in++ * 255/alpha; *out++ = (unsigned)*in++ * 255/alpha; @@ -63,6 +71,26 @@ static bool gsr_cursor_set_from_x11_cursor_image(gsr_cursor *self, XFixesCursorI return false; } +static bool xinput_is_supported(Display *dpy, int *xi_opcode) { + *xi_opcode = 0; + int query_event = 0; + int query_error = 0; + if(!XQueryExtension(dpy, "XInputExtension", xi_opcode, &query_event, &query_error)) { + fprintf(stderr, "gsr error: gsr_cursor_init: X Input extension not available\n"); + return false; + } + + int major = 2; + int minor = 1; + int retval = XIQueryVersion(dpy, &major, &minor); + if (retval != Success) { + fprintf(stderr, "gsr error: gsr_cursor_init: XInput 2.1 is not supported\n"); + return false; + } + + return true; +} + int gsr_cursor_init(gsr_cursor *self, gsr_egl *egl, Display *display) { int x_fixes_error_base = 0; @@ -79,11 +107,31 @@ int gsr_cursor_init(gsr_cursor *self, gsr_egl *egl, Display *display) { return -1; } + if(!xinput_is_supported(self->display, &self->xi_opcode)) { + gsr_cursor_deinit(self); + return -1; + } + + unsigned char mask[XIMaskLen(XI_LASTEVENT)]; + memset(mask, 0, sizeof(mask)); + XISetMask(mask, XI_RawMotion); + + XIEventMask xi_masks; + xi_masks.deviceid = XIAllMasterDevices; + xi_masks.mask_len = sizeof(mask); + xi_masks.mask = mask; + if(XISelectEvents(self->display, DefaultRootWindow(self->display), &xi_masks, 1) != Success) { + fprintf(stderr, "gsr error: gsr_cursor_init: XISelectEvents failed\n"); + gsr_cursor_deinit(self); + return -1; + } + self->egl->glGenTextures(1, &self->texture_id); XFixesSelectCursorInput(self->display, DefaultRootWindow(self->display), XFixesDisplayCursorNotifyMask); - gsr_cursor_set_from_x11_cursor_image(self, XFixesGetCursorImage(self->display)); + gsr_cursor_set_from_x11_cursor_image(self, XFixesGetCursorImage(self->display), &self->visible); self->cursor_image_set = true; + self->cursor_moved = true; return 0; } @@ -97,29 +145,46 @@ void gsr_cursor_deinit(gsr_cursor *self) { self->texture_id = 0; } + XISelectEvents(self->display, DefaultRootWindow(self->display), NULL, 0); XFixesSelectCursorInput(self->display, DefaultRootWindow(self->display), 0); self->display = NULL; self->egl = NULL; } -void gsr_cursor_update(gsr_cursor *self, XEvent *xev) { +bool gsr_cursor_update(gsr_cursor *self, XEvent *xev) { + bool updated = false; + XGenericEventCookie *cookie = (XGenericEventCookie*)&xev->xcookie; + const Bool got_event_data = XGetEventData(self->display, cookie); + if(got_event_data && cookie->type == GenericEvent && cookie->extension == self->xi_opcode && cookie->evtype == XI_RawMotion) { + updated = true; + self->cursor_moved = true; + } + if(got_event_data) + XFreeEventData(self->display, cookie); + if(xev->type == self->x_fixes_event_base + XFixesCursorNotify) { XFixesCursorNotifyEvent *cursor_notify_event = (XFixesCursorNotifyEvent*)xev; if(cursor_notify_event->subtype == XFixesDisplayCursorNotify && cursor_notify_event->window == DefaultRootWindow(self->display)) { - self->cursor_image_set = true; - gsr_cursor_set_from_x11_cursor_image(self, XFixesGetCursorImage(self->display)); + self->cursor_image_set = false; } } if(!self->cursor_image_set) { self->cursor_image_set = true; - gsr_cursor_set_from_x11_cursor_image(self, XFixesGetCursorImage(self->display)); + gsr_cursor_set_from_x11_cursor_image(self, XFixesGetCursorImage(self->display), &self->visible); + updated = true; } + + return updated; } void gsr_cursor_tick(gsr_cursor *self, Window relative_to) { - /* TODO: Use XInput2 instead. However that doesn't work when the pointer is grabbed. Maybe check for focused window change and XSelectInput PointerMask */ + if(!self->cursor_moved) + return; + + self->cursor_moved = false; + Window dummy_window; int dummy_i; unsigned int dummy_u; diff --git a/src/main.cpp b/src/main.cpp index aedb900..e4763ee 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -101,7 +101,8 @@ enum class PixelFormat { enum class FramerateMode { CONSTANT, - VARIABLE + VARIABLE, + CONTENT }; static int x11_error_handler(Display*, XErrorEvent*) { @@ -792,6 +793,7 @@ static void open_video(AVCodecContext *codec_context, VideoQuality video_quality if(codec_context->codec_id == AV_CODEC_ID_H264) { av_dict_set(&options, "profile", "high", 0); + // Removed because it causes stutter in games for some people //av_dict_set_int(&options, "quality", 5, 0); // quality preset } else if(codec_context->codec_id == AV_CODEC_ID_AV1) { av_dict_set(&options, "profile", "main", 0); // TODO: use professional instead? @@ -822,7 +824,7 @@ static void open_video(AVCodecContext *codec_context, VideoQuality video_quality static void usage_header() { const bool inside_flatpak = getenv("FLATPAK_ID") != NULL; const char *program_name = inside_flatpak ? "flatpak run --command=gpu-screen-recorder com.dec05eba.gpu_screen_recorder" : "gpu-screen-recorder"; - fprintf(stderr, "usage: %s -w [-c ] [-s WxH] -f [-a ] [-q ] [-r ] [-k h264|hevc|hevc_hdr|av1|av1_hdr] [-ac aac|opus|flac] [-ab ] [-oc yes|no] [-fm cfr|vfr] [-cr limited|full] [-v yes|no] [-h|--help] [-o ] [-mf yes|no] [-sc ] [-cursor yes|no] [-keyint ]\n", program_name); + fprintf(stderr, "usage: %s -w [-c ] [-s WxH] -f [-a ] [-q ] [-r ] [-k h264|hevc|hevc_hdr|av1|av1_hdr] [-ac aac|opus|flac] [-ab ] [-oc yes|no] [-fm cfr|vfr|content] [-cr limited|full] [-v yes|no] [-h|--help] [-o ] [-mf yes|no] [-sc ] [-cursor yes|no] [-keyint ]\n", program_name); } static void usage_full() { @@ -846,6 +848,7 @@ static void usage_full() { fprintf(stderr, " -f Frame rate to record at. Recording will only capture frames at this target frame rate.\n"); fprintf(stderr, " For constant frame rate mode this option is the frame rate every frame will be captured at and if the capture frame rate is below this target frame rate then the frames will be duplicated.\n"); fprintf(stderr, " For variable frame rate mode this option is the max frame rate and if the capture frame rate is below this target frame rate then frames will not be duplicated.\n"); + fprintf(stderr, " Content frame rate is similar to variable frame rate mode, except the frame rate will match the frame rate of the captured content when possible, but not capturing above the frame rate set in this -f option.\n"); fprintf(stderr, "\n"); fprintf(stderr, " -a Audio device to record from (pulse audio device). Can be specified multiple times. Each time this is specified a new audio track is added for the specified audio device.\n"); fprintf(stderr, " A name can be given to the audio input device by prefixing the audio input with /, for example \"dummy/alsa_output.pci-0000_00_1b.0.analog-stereo.monitor\".\n"); @@ -877,8 +880,9 @@ static void usage_full() { fprintf(stderr, " is dropped when you record a game. Only needed if you are recording a game that is bottlenecked by GPU. The same issue exists on Wayland but overclocking is not possible on Wayland.\n"); fprintf(stderr, " Works only if your have \"Coolbits\" set to \"12\" in NVIDIA X settings, see README for more information. Note! use at your own risk! Optional, disabled by default.\n"); fprintf(stderr, "\n"); - fprintf(stderr, " -fm Framerate mode. Should be either 'cfr' (constant frame rate) or 'vfr' (variable frame rate). Defaults to 'vfr'.\n"); + fprintf(stderr, " -fm Framerate mode. Should be either 'cfr' (constant frame rate), 'vfr' (variable frame rate) or 'content'. Defaults to 'vfr'.\n"); fprintf(stderr, " 'vfr' is recommended for recording for less issue with very high system load but some applications such as video editors may not support it properly.\n"); + fprintf(stderr, " 'content' is currently only supported when recording a single window, on X11. The 'content' option matches the recording frame rate to the captured content.\n"); fprintf(stderr, "\n"); fprintf(stderr, " -cr Color range. Should be either 'limited' (aka mpeg) or 'full' (aka jpeg). Defaults to 'limited'.\n"); fprintf(stderr, " Limited color range means that colors are in range 16-235 (4112-60395 for hdr) while full color range means that colors are in range 0-255 (0-65535 for hdr).\n"); @@ -1468,7 +1472,7 @@ static void list_supported_video_codecs() { XCloseDisplay(dpy); } -static gsr_capture* create_capture_impl(const char *window_str, const char *screen_region, bool wayland, gsr_egl &egl, int fps, bool overclock, VideoCodec video_codec, gsr_color_range color_range, bool record_cursor) { +static gsr_capture* create_capture_impl(const char *window_str, const char *screen_region, bool wayland, gsr_egl &egl, int fps, bool overclock, VideoCodec video_codec, gsr_color_range color_range, bool record_cursor, bool track_damage) { vec2i region_size = { 0, 0 }; Window src_window_id = None; bool follow_focused = false; @@ -1611,6 +1615,7 @@ static gsr_capture* create_capture_impl(const char *window_str, const char *scre xcomposite_params.base.region_size = region_size; xcomposite_params.base.color_range = color_range; xcomposite_params.base.record_cursor = record_cursor; + xcomposite_params.base.track_damage = track_damage; capture = gsr_capture_xcomposite_vaapi_create(&xcomposite_params); if(!capture) _exit(1); @@ -1624,6 +1629,7 @@ static gsr_capture* create_capture_impl(const char *window_str, const char *scre xcomposite_params.base.region_size = region_size; xcomposite_params.base.color_range = color_range; xcomposite_params.base.record_cursor = record_cursor; + xcomposite_params.base.track_damage = track_damage; xcomposite_params.overclock = overclock; capture = gsr_capture_xcomposite_cuda_create(&xcomposite_params); if(!capture) @@ -2037,8 +2043,10 @@ int main(int argc, char **argv) { framerate_mode = FramerateMode::CONSTANT; } else if(strcmp(framerate_mode_str, "vfr") == 0) { framerate_mode = FramerateMode::VARIABLE; + } else if(strcmp(framerate_mode_str, "content") == 0) { + framerate_mode = FramerateMode::CONTENT; } else { - fprintf(stderr, "Error: -fm should either be either 'cfr' or 'vfr', got: '%s'\n", framerate_mode_str); + fprintf(stderr, "Error: -fm should either be either 'cfr', 'vfr' or 'content', got: '%s'\n", framerate_mode_str); usage(); } @@ -2320,7 +2328,7 @@ int main(int argc, char **argv) { _exit(2); } - gsr_capture *capture = create_capture_impl(window_str, screen_region, wayland, egl, fps, overclock, video_codec, color_range, record_cursor); + gsr_capture *capture = create_capture_impl(window_str, screen_region, wayland, egl, fps, overclock, video_codec, color_range, record_cursor, framerate_mode == FramerateMode::CONTENT); // (Some?) livestreaming services require at least one audio track to work. // If not audio is provided then create one silent audio track. @@ -2477,6 +2485,7 @@ 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; bool paused = false; double paused_time_offset = 0.0; @@ -2668,7 +2677,6 @@ int main(int argc, char **argv) { running = 0; break; } - ++fps_counter; // TODO: Move to another thread, since this shouldn't be locked to video encoding fps { @@ -2693,19 +2701,26 @@ int main(int argc, char **argv) { } } + ++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; if (elapsed >= 1.0) { if(verbose) { - fprintf(stderr, "update fps: %d\n", fps_counter); + fprintf(stderr, "update fps: %d, damage fps: %d\n", fps_counter, damage_fps_counter); } fps_start_time = time_now; fps_counter = 0; + damage_fps_counter = 0; + } + + const bool damaged = !capture->consume_damage || capture->consume_damage(capture); + if(damaged) { + ++damage_fps_counter; } double frame_time_overflow = frame_timer_elapsed - target_fps; - if (frame_time_overflow >= 0.0) { + if (frame_time_overflow >= 0.0 && damaged) { frame_time_overflow = std::min(frame_time_overflow, target_fps); frame_timer_start = time_now - frame_time_overflow; -- cgit v1.2.3