Compare commits

...

8 Commits
5.9.2 ... 5.9.4

Author SHA1 Message Date
dec05eba
af941f602b 5.9.4 2025-12-04 20:15:44 +01:00
dec05eba
c1614e4f30 nvfbc: mitigate x11 display leak on monitor off
When the monitor is turned off gsr will attempt to recreate the nvfbc
session once a second. If that fails (because the monitor is still
turned off) then nvfbc will leak an x11 display. This seems to be a bug
in the nvidia driver.

Mitigate this by only attempting to recreate the nvfbc session if the
capture target monitor can be found (predicting if nvfbc recreate will
fail).

Thanks to Lim Ding Wen for finding the issue and suggesting the
mitigation.
2025-12-04 20:06:47 +01:00
dec05eba
f00dec683e Mention flickering cursor 2025-12-01 02:17:46 +01:00
dec05eba
11930c355f Update README 2025-11-26 18:27:49 +01:00
dec05eba
716dc0b736 5.9.3 2025-11-25 19:02:58 +01:00
dec05eba
059e3dbbc0 pipewire video: check if has modifier 2025-11-24 21:26:02 +01:00
dec05eba
990dfc7589 pipewire video: re-negotiate modifiers multiple times until we get a good one 2025-11-24 21:15:35 +01:00
dec05eba
2d551e7b1f Proper fallback for vulkan video 2025-11-23 14:40:54 +01:00
9 changed files with 72 additions and 49 deletions

View File

@@ -119,8 +119,8 @@ Streaming works the same way as recording, but the `-o` argument should be path
GPU Screen Recorder uses Ffmpeg so GPU Screen Recorder supports all protocols that Ffmpeg supports.\
If you want to reduce latency one thing you can do is to use the `-keyint` option, for example `-keyint 0.5`. Lower value means lower latency at the cost of increased bitrate/decreased quality.
## Recording while using replay/streaming
You can record a regular video while using replay/streaming by launching GPU Screen Recorder with the `-ro` option to specify a directory where to save the recording.\
To start/stop (and save) recording use the SIGRTMIN signal, for example `pkill -SIGRTMIN -f gpu-screen-recorder`. The name of the video will be displayed in stdout when saving the video.\
You can record a regular video while using replay/streaming by launching GPU Screen Recorder with the `-ro` option to specify a directory where to save the recording (for example: `gpu-screen-recorder -w screen -c mp4 -r 60 -o "$HOME/Videos/replays" -ro "$HOME/Videos/recordings"`).\
To start/stop (and save) recording use the SIGRTMIN signal, for example `pkill -SIGRTMIN -f gpu-screen-recorder`. The path to the video will be displayed in stdout when saving the video.\
This way of recording while using replay/streaming is more efficient than running GPU Screen Recorder multiple times since this way it only records the screen and encodes the video once.
## Controlling GPU Screen Recorder remotely
To save a video in replay mode, you need to send signal SIGUSR1 to gpu screen recorder. You can do this by running `pkill -SIGUSR1 -f gpu-screen-recorder`.\
@@ -242,3 +242,10 @@ This also affects other screen recording software such as obs studio.\
Capture a monitor directly instead to workaround this issue until kde plasma devs fix it, or use another wayland compositor that doesn't have this issue.
## System notifications get disabled when recording with desktop portal option
Some desktop environments such as KDE Plasma turn off notifications when you record the screen with the desktop portal option. You can disable this by going into KDE Plasma settings -> search for notifications and then under "Do Not Disturb mode" untick "During screen sharing".
## The recorded video lags or I get dropped frames in the video
This is likely not an issue in the recorded video itself, but the video player you use. GPU Screen Recorder doesn't record by dropping frames. Some video players dont play videos with hardware acceleration by default,
especially if you record with HEVC/AV1 video codec. In such cases it's recommended to play the video with mpv instead with hardware acceleration enabled (for example: `mpv --vo=gpu --hwdec=auto video.mp4`).
Some corporate distros such as Fedora (or some Fedora based distros) also disable hardware accelerated video codecs on AMD/Intel GPUs, so you might need to install mpv (or another video player) with flathub instead, which bypasses this restriction.
## My cursor is flickering in the recorded video
This is likely an AMD gpu driver issue. It only happens to certain generations of AMD GPUs. On Wayland you can record with the desktop portal option (`-w portal`) to workaround this issue.
This issue hasn't been observed on X11 yet, but if you do observe it you can either record a window (`-w $(xdotool selectwindow)`) or change your xorg config to use software cursor instead (Add `Option "SWcursor" "true"` under modesetting "Device" section in your xorg config file).

5
TODO
View File

@@ -356,3 +356,8 @@ Support youtube sso.
Remove -fm content (support it but remove it from documentation and output deprecation notice when its used) and use it when using -fm vbr (which is the default option).
But first -fm content needs to be support on wayland as well, by checking if there is a difference between frames (checksum the frame content).
-fm content also needs to have a minimum fps to prevent live stream from timing out when nothing changes on the screen.
There is a leak in nvfbc. When a monitor is turned off and then on there will be an x11 display leak inside nvfbc. This seems to be a bug in nvfbc.
Right now a mitigation has been added to not try to recreate the nvfbc session if the capture target (monitor) isn't connected (predict if nvfbc session create will fail).
One possible reason this happens is because bExternallyManagedContext is set to true.
This also means that nvfbc leaks connection when destroying nvfbc, even if the monitor is connected (this is not an issue right now because exit is done, but if gsr was turned into a library it would be).

View File

@@ -79,8 +79,8 @@ typedef struct {
struct spa_video_info format;
int server_version_sync;
bool negotiated;
bool renegotiated;
bool damaged;
bool has_modifier;
struct {
bool visible;

View File

@@ -1,4 +1,4 @@
project('gpu-screen-recorder', ['c', 'cpp'], version : '5.9.2', default_options : ['warning_level=2'])
project('gpu-screen-recorder', ['c', 'cpp'], version : '5.9.4', default_options : ['warning_level=2'])
add_project_arguments('-Wshadow', language : ['c', 'cpp'])
if get_option('buildtype') == 'debug'

View File

@@ -1,7 +1,7 @@
[package]
name = "gpu-screen-recorder"
type = "executable"
version = "5.9.2"
version = "5.9.4"
platforms = ["posix"]
[config]

View File

@@ -251,7 +251,7 @@ static void usage_full() {
printf(" Run GPU Screen Recorder with the --list-application-audio option to list valid application names. It's possible to use an application name that is not listed in --list-application-audio,\n");
printf(" for example when trying to record audio from an application that hasn't started yet.\n");
printf("\n");
printf(" -q Video quality. Should be either 'medium', 'high', 'very_high' or 'ultra' when using '-bm qp' or '-bm vbr' options, and '-bm qp' is the default option used.\n");
printf(" -q Video/image quality. Should be either 'medium', 'high', 'very_high' or 'ultra' when using '-bm qp' or '-bm vbr' options, and '-bm qp' is the default option used.\n");
printf(" 'high' is the recommended option when live streaming or when you have a slower harddrive.\n");
printf(" When using '-bm cbr' option then this is option is instead used to specify the video bitrate in kbps.\n");
printf(" Optional when using '-bm qp' or '-bm vbr' options, set to 'very_high' be default.\n");

View File

@@ -13,6 +13,7 @@
#include <assert.h>
#include <X11/Xlib.h>
#include <X11/extensions/Xrandr.h>
typedef struct {
gsr_capture_nvfbc_params params;
@@ -302,6 +303,35 @@ static int gsr_capture_nvfbc_start(gsr_capture *cap, gsr_capture_metadata *captu
return -1;
}
static bool gsr_capture_nvfbc_is_capture_monitor_connected(gsr_capture_nvfbc *self) {
Display *dpy = gsr_window_get_display(self->params.egl->window);
int num_monitors = 0;
XRRMonitorInfo *monitors = XRRGetMonitors(dpy, DefaultRootWindow(dpy), True, &num_monitors);
if(!monitors)
return false;
bool capture_monitor_connected = false;
if(strcmp(self->params.display_to_capture, "screen") == 0) {
capture_monitor_connected = num_monitors > 0;
} else {
for(int i = 0; i < num_monitors; ++i) {
char *monitor_name = XGetAtomName(dpy, monitors[i].name);
if(!monitor_name)
continue;
if(strcmp(monitor_name, self->params.display_to_capture) == 0) {
capture_monitor_connected = true;
XFree(monitor_name);
break;
}
XFree(monitor_name);
}
}
XRRFreeMonitors(monitors);
return capture_monitor_connected;
}
static int gsr_capture_nvfbc_capture(gsr_capture *cap, gsr_capture_metadata *capture_metadata, gsr_color_conversion *color_conversion) {
gsr_capture_nvfbc *self = cap->priv;
@@ -310,6 +340,13 @@ static int gsr_capture_nvfbc_capture(gsr_capture *cap, gsr_capture_metadata *cap
const double now = clock_get_monotonic_seconds();
if(now - self->nvfbc_dead_start >= nvfbc_recreate_retry_time_seconds) {
self->nvfbc_dead_start = now;
/*
Do not attempt to recreate the nvfbc session if the monitor isn't turned on/connected.
This is to predict if the nvfbc session create below will fail since if it fails it leaks an x11 display (a bug in the nvidia driver).
*/
if(!gsr_capture_nvfbc_is_capture_monitor_connected(self))
return 0;
gsr_capture_nvfbc_destroy_session_and_handle(self);
if(gsr_capture_nvfbc_setup_handle(self) != 0) {
@@ -322,6 +359,7 @@ static int gsr_capture_nvfbc_capture(gsr_capture *cap, gsr_capture_metadata *cap
return -1;
}
fprintf(stderr, "gsr info: gsr_capture_nvfbc_capture: recreated nvfbc session after modeset recovery\n");
self->nvfbc_needs_recreate = false;
} else {
return 0;

View File

@@ -1645,41 +1645,6 @@ static bool get_supported_video_codecs(gsr_egl *egl, gsr_video_codec video_codec
return false;
}
static void force_cpu_encoding(args_parser *args_parser) {
args_parser->video_codec = GSR_VIDEO_CODEC_H264;
args_parser->video_encoder = GSR_VIDEO_ENCODER_HW_CPU;
if(args_parser->bitrate_mode == GSR_BITRATE_MODE_VBR) {
fprintf(stderr, "gsr warning: bitrate mode has been forcefully set to qp because software encoding option doesn't support vbr option\n");
args_parser->bitrate_mode = GSR_BITRATE_MODE_QP;
}
}
static bool get_supported_video_codecs_with_cpu_fallback(gsr_egl *egl, args_parser *args_parser, bool cleanup, gsr_supported_video_codecs *video_codecs) {
if(get_supported_video_codecs(egl, args_parser->video_codec, args_parser->video_encoder == GSR_VIDEO_ENCODER_HW_CPU, cleanup, video_codecs)) {
if(args_parser->video_encoder == GSR_VIDEO_ENCODER_HW_CPU || !args_parser->fallback_cpu_encoding)
return true;
else if(args_parser->video_encoder == GSR_VIDEO_ENCODER_HW_GPU && video_codecs->h264.supported && (args_parser->video_codec == (gsr_video_codec)GSR_VIDEO_CODEC_AUTO || args_parser->video_codec == GSR_VIDEO_CODEC_H264))
return true;
}
if(args_parser->video_encoder == GSR_VIDEO_ENCODER_HW_CPU || !args_parser->fallback_cpu_encoding)
return false;
fprintf(stderr, "gsr warning: gpu encoding is not available on your system, trying cpu encoding instead because -fallback-cpu-encoding is enabled. Install the proper vaapi drivers on your system (if supported) if you experience performance issues\n");
if(get_supported_video_codecs(egl, GSR_VIDEO_CODEC_H264, true, cleanup, video_codecs)) {
if(args_parser->video_codec != (gsr_video_codec)GSR_VIDEO_CODEC_AUTO && args_parser->video_codec != GSR_VIDEO_CODEC_H264) {
fprintf(stderr, "gsr warning: cpu encoding is used but video codec isn't set to h264. Forcing video codec to h264\n");
args_parser->video_codec = GSR_VIDEO_CODEC_H264;
}
force_cpu_encoding(args_parser);
return true;
}
return false;
}
static void xwayland_check_callback(const gsr_monitor *monitor, void *userdata) {
bool *xwayland_found = (bool*)userdata;
if(monitor->name_len >= 8 && strncmp(monitor->name, "XWAYLAND", 8) == 0)
@@ -2769,6 +2734,15 @@ static void print_codec_error(gsr_video_codec video_codec) {
" If your GPU doesn't support hardware accelerated video encoding then you can use '-fallback-cpu-encoding yes' option to encode with your cpu instead.\n", video_codec_name, video_codec_name, video_codec_name);
}
static void force_cpu_encoding(args_parser *args_parser) {
args_parser->video_codec = GSR_VIDEO_CODEC_H264;
args_parser->video_encoder = GSR_VIDEO_ENCODER_HW_CPU;
if(args_parser->bitrate_mode == GSR_BITRATE_MODE_VBR) {
fprintf(stderr, "gsr warning: bitrate mode has been forcefully set to qp because software encoding option doesn't support vbr option\n");
args_parser->bitrate_mode = GSR_BITRATE_MODE_QP;
}
}
static const AVCodec* pick_video_codec(gsr_egl *egl, args_parser *args_parser, bool use_fallback_codec, bool *low_power, gsr_supported_video_codecs *supported_video_codecs) {
// TODO: software encoder for hevc, av1, vp8 and vp9
*low_power = false;
@@ -2806,7 +2780,7 @@ static const AVCodec* pick_video_codec(gsr_egl *egl, args_parser *args_parser, b
fprintf(stderr, "gsr warning: selected video codec h264_vulkan is not supported, trying h264 instead\n");
args_parser->video_codec = GSR_VIDEO_CODEC_H264;
// Need to do a query again because this time it's without vulkan
if(!get_supported_video_codecs_with_cpu_fallback(egl, args_parser, true, supported_video_codecs)) {
if(!get_supported_video_codecs(egl, args_parser->video_codec, false, true, supported_video_codecs)) {
fprintf(stderr, "gsr error: failed to query for supported video codecs\n");
print_codec_error(args_parser->video_codec);
_exit(11);
@@ -2817,7 +2791,7 @@ static const AVCodec* pick_video_codec(gsr_egl *egl, args_parser *args_parser, b
fprintf(stderr, "gsr warning: selected video codec hevc_vulkan is not supported, trying hevc instead\n");
args_parser->video_codec = GSR_VIDEO_CODEC_HEVC;
// Need to do a query again because this time it's without vulkan
if(!get_supported_video_codecs_with_cpu_fallback(egl, args_parser, true, supported_video_codecs)) {
if(!get_supported_video_codecs(egl, args_parser->video_codec, false, true, supported_video_codecs)) {
fprintf(stderr, "gsr error: failed to query for supported video codecs\n");
print_codec_error(args_parser->video_codec);
_exit(11);

View File

@@ -280,7 +280,8 @@ static void on_param_changed_cb(void *user_data, uint32_t id, const struct spa_p
self->format.info.raw.format,
spa_debug_type_find_name(spa_type_video_format, self->format.info.raw.format));
if(has_modifier) {
self->has_modifier = has_modifier;
if(self->has_modifier) {
fprintf(stderr, "gsr info: pipewire: Modifier: 0x%" PRIx64 "\n", self->format.info.raw.modifier);
}
@@ -736,7 +737,6 @@ void gsr_pipewire_video_deinit(gsr_pipewire_video *self) {
self->dmabuf_num_planes = 0;
self->negotiated = false;
self->renegotiated = false;
if(self->mutex_initialized) {
pthread_mutex_destroy(&self->mutex);
@@ -783,21 +783,20 @@ static EGLImage gsr_pipewire_video_create_egl_image_with_fallback(gsr_pipewire_v
}
EGLImage image = NULL;
if(self->no_modifiers_fallback) {
if(self->no_modifiers_fallback || !self->has_modifier) {
image = gsr_pipewire_video_create_egl_image(self, fds, offsets, pitches, modifiers, false);
} else {
image = gsr_pipewire_video_create_egl_image(self, fds, offsets, pitches, modifiers, true);
if(!image) {
if(self->renegotiated || self->format.info.raw.modifier == DRM_FORMAT_MOD_INVALID) {
if(self->format.info.raw.modifier == DRM_FORMAT_MOD_INVALID) {
fprintf(stderr, "gsr error: gsr_pipewire_video_create_egl_image_with_fallback: failed to create egl image with modifiers, trying without modifiers\n");
self->no_modifiers_fallback = true;
image = gsr_pipewire_video_create_egl_image(self, fds, offsets, pitches, modifiers, false);
} else {
fprintf(stderr, "gsr error: gsr_pipewire_video_create_egl_image_with_fallback: failed to create egl image with modifiers, renegotiating with a different modifier\n");
self->negotiated = false;
self->renegotiated = true;
gsr_pipewire_video_remove_modifier(self, self->format.info.raw.modifier);
pw_thread_loop_lock(self->thread_loop);
gsr_pipewire_video_remove_modifier(self, self->format.info.raw.modifier);
pw_loop_signal_event(pw_thread_loop_get_loop(self->thread_loop), self->reneg);
pw_thread_loop_unlock(self->thread_loop);
}