Compare commits

...

7 Commits

Author SHA1 Message Date
dec05eba
0de75e5b7e 5.12.4 2026-02-12 01:24:26 +01:00
dec05eba
c79fb1e5c9 pipewire-video: do correct removal of modifier 2026-02-12 01:07:48 +01:00
eonphi
4a4af85b6d attempt EGLImage without modifiers when last failed 2026-02-12 00:41:21 +01:00
dec05eba
8f7608e7ee Add --list-monitors option to list only monitors, refactor 2026-02-09 15:03:10 +01:00
dec05eba
f3235ed1bf Improve manpage 2026-02-09 13:59:18 +01:00
dec05eba
3666bba518 aur -> official repo 2026-02-06 19:02:23 +01:00
dec05eba
5d8d14eeaf FAQ vlc 2026-02-04 01:43:27 +01:00
10 changed files with 137 additions and 101 deletions

View File

@@ -29,14 +29,14 @@ Supported image formats:
This software works on X11 and Wayland on AMD, Intel and NVIDIA.
# Installation
If you are running an Arch Linux based distro then you can find gpu screen recorder on aur under the name gpu-screen-recorder (`yay -S gpu-screen-recorder`).\
If you are running an Arch Linux based distro then you can find gpu screen recorder in the official repositories under the name gpu-screen-recorder (`sudo pacman -S gpu-screen-recorder`).\
If you are running another distro then you can run `sudo ./install.sh`, but you need to manually install the dependencies, as described below.\
You can also install gpu screen recorder ([the ui version](https://git.dec05eba.com/gpu-screen-recorder-gtk/)) from [flathub](https://flathub.org/apps/details/com.dec05eba.gpu_screen_recorder), which is the easiest method
to install GPU Screen Recorder on non-arch based distros.\
If you install GPU Screen Recorder flatpak, which is the gtk gui version then you can still run GPU Screen Recorder command line by using the flatpak command option, for example `flatpak run --command=gpu-screen-recorder com.dec05eba.gpu_screen_recorder -w screen -f 60 -o video.mp4`. Note that if you want to record your monitor on AMD/Intel then you need to install the flatpak system-wide (like so: `flatpak install --system com.dec05eba.gpu_screen_recorder`).
## Unofficial install methods
The only official ways to install GPU Screen Recorder is either from source, AUR or flathub. Other sources may be out of date and missing features or may not work correctly.\
The only official ways to install GPU Screen Recorder is either from source, arch linux extra repository or flathub. Other sources may be out of date and missing features or may not work correctly.\
If you install GPU Screen Recorder from somewhere else and have an issue then try installing it from one of the official sources before reporting it as an issue.\
If you still prefer to install GPU Screen Recorder with a package manager instead of from source or as a flatpak then you may be able to find a package for your distro.\
Here are some known unofficial packages:
@@ -261,3 +261,5 @@ If the root user is disabled on your system then you can instead record with `-w
## GPU usage is high on my laptop
GPU usage on battery powered devices is misleading. For example Intel iGPUs has multiple performance levels and the GPU usage reported on the system is the GPU usage at the current performance level.
The performance level changes depending on the GPU load, so it may say that GPU usage is 80%, but the actual GPU usage may be 5%.
## The video is too dark when capturing full-range video or 10-bit video
This is an issue in some broken video players such as vlc. Play the video with a video player such as mpv (or a mpv frontend such as celluloid) or a browser instead.

4
TODO
View File

@@ -399,3 +399,7 @@ Should -low-power option also use vaapi/vulkan low power, if available?
Should capture option x=bla;y=bla be scaled by -s (output resolution scale)? width and height is.
Certain webcam resolutions yuyv resolutions dont work (on amd at least), such as 800x600. Maybe it's because of alignment issue, 600 isn't divisible by 16.
Add option to capture all monitors automatically.
Make -w optional, to only capture audio.

View File

@@ -14,13 +14,17 @@ gpu-screen-recorder \- The fastest screen recording tool for Linux
|
.B \-\-version
|
.B \-\-info
|
.B \-\-list\-capture\-options
|
.B \-\-list\-monitors
|
.B \-\-list\-v4l2\-devices
|
.B \-\-list\-audio\-devices
|
.B \-\-list\-application\-audio
|
.B \-\-info
.SH DESCRIPTION
.B gpu-screen-recorder
is the fastest screen recording tool for Linux. It uses the GPU
@@ -92,10 +96,6 @@ Run
.B \-\-list\-capture\-options
to list available capture sources.
.PP
Run
.B \-\-list\-v4l2\-devices
to list available camera devices (V4L2).
.PP
Additional options can be passed to each capture source by splitting capture source with
.B ;
for example
@@ -283,10 +283,12 @@ Video codec:
Quality preset (medium, high, very_high, ultra) for QP/VBR mode, or bitrate (kbps) for CBR mode (default: very_high).
.TP
.BI \-bm " auto|qp|vbr|cbr"
Bitrate mode (default: auto → qp). CBR recommended for replay buffer.
Bitrate mode (default: auto → qp). CBR recommended for replay buffer and live streaming.
QP means to capture with constant quality, even in motion, while VBR and CBR means to capture with constant size.
.TP
.BI \-fm " cfr|vfr|content"
Frame rate mode: constant, variable, or match content (default: vfr). Content mode only on X11 or portal.
Frame rate mode: cfr (constant), vfr (variable), or content (match content) (default: vfr). Content mode is only available on X11 or portal.
Content mode syncs video to the captured content and is recommended for smoothest video when the game is running
at the same frame rate or lower than what you are trying to record at.
@@ -379,7 +381,7 @@ When enabled, writes a timestamp file with extra extension \fI.ts\fR next to the
monotonic_microsec realtime_microsec
<monotonic_microsec> <realtime_microsec>
.fi
(default: no). Ignored for livestreaming and when output is piped.
(default: no). Ignored for live streaming and when output is piped.
.SS Output Options
.TP
.BI \-o " output"
@@ -401,6 +403,9 @@ Show system info (codecs, capture options).
.B \-\-list\-capture\-options
List available capture sources (window, monitors, portal, v4l2 device path).
.TP
.B \-\-list\-monitors
List available monitors.
.TP
.B \-\-list\-v4l2\-devices
List available cameras devices (V4L2).
.TP

View File

@@ -63,6 +63,7 @@ typedef struct {
void (*list_application_audio)(void *userdata);
void (*list_v4l2_devices)(void *userdata);
void (*list_capture_options)(const char *card_path, void *userdata);
void (*list_monitors)(void *userdata);
} args_handlers;
typedef struct {

View File

@@ -106,6 +106,7 @@ typedef struct {
bool no_modifiers_fallback;
bool external_texture_fallback;
uint64_t renegotiated_modifier;
uint64_t modifiers[GSR_PIPEWIRE_VIDEO_MAX_MODIFIERS];
size_t num_modifiers;

View File

@@ -1,4 +1,4 @@
project('gpu-screen-recorder', ['c', 'cpp'], version : '5.12.3', default_options : ['warning_level=2'])
project('gpu-screen-recorder', ['c', 'cpp'], version : '5.12.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.12.3"
version = "5.12.4"
platforms = ["posix"]
[config]

View File

@@ -196,7 +196,8 @@ static void usage_header(void) {
"[-bm auto|qp|vbr|cbr] [-cr limited|full] [-tune performance|quality] [-df yes|no] [-sc <script_path>] [-p <plugin_path>] "
"[-cursor yes|no] [-keyint <value>] [-restore-portal-session yes|no] [-portal-session-token-filepath filepath] [-encoder gpu|cpu] "
"[-fallback-cpu-encoding yes|no] [-o <output_file>] [-ro <output_directory>] [-ffmpeg-opts <options>] [--list-capture-options [card_path]] "
"[--list-audio-devices] [--list-application-audio] [--list-v4l2-devices] [-write-first-frame-ts yes|no] [-low-power yes|no] [-v yes|no] [-gl-debug yes|no] [--version] [-h|--help]\n", program_name);
"[--list-monitors] [--list-audio-devices] [--list-application-audio] [--list-v4l2-devices] [-write-first-frame-ts yes|no] [-low-power yes|no] "
"[-v yes|no] [-gl-debug yes|no] [--version] [-h|--help]\n", program_name);
fflush(stdout);
}
@@ -500,6 +501,11 @@ bool args_parser_parse(args_parser *self, int argc, char **argv, const args_hand
}
}
if(strcmp(argv[1], "--list-monitors") == 0) {
arg_handlers->list_monitors(userdata);
return true;
}
if(argc == 2 && strcmp(argv[1], "--version") == 0) {
arg_handlers->version(userdata);
return true;

View File

@@ -1888,23 +1888,31 @@ static void camera_query_callback(const char *path, const gsr_capture_v4l2_suppo
printf("%s|%ux%u@%uhz|%s\n", path, setup->resolution.width, setup->resolution.height, gsr_capture_v4l2_framerate_to_number(setup->framerate), gsr_capture_v4l2_pixfmt_to_string(setup->pixfmt));
}
static void list_supported_capture_options(const gsr_window *window, const char *card_path, bool list_monitors) {
// Returns the number of monitors found
static int list_monitors(const gsr_window *window, const char *card_path) {
capture_options_callback options;
options.window = window;
options.num_monitors = 0;
const bool is_x11 = gsr_window_get_display_server(window) == GSR_DISPLAY_SERVER_X11;
const gsr_connection_type connection_type = is_x11 ? GSR_CONNECTION_X11 : GSR_CONNECTION_DRM;
for_each_active_monitor_output(window, card_path, connection_type, output_monitor_info, &options);
return options.num_monitors;
}
static void list_supported_capture_options(const gsr_window *window, const char *card_path, bool do_list_monitors) {
const bool wayland = gsr_window_get_display_server(window) == GSR_DISPLAY_SERVER_WAYLAND;
if(!wayland) {
puts("window");
puts("focused");
}
capture_options_callback options;
options.window = window;
options.num_monitors = 0;
if(list_monitors) {
const bool is_x11 = gsr_window_get_display_server(window) == GSR_DISPLAY_SERVER_X11;
const gsr_connection_type connection_type = is_x11 ? GSR_CONNECTION_X11 : GSR_CONNECTION_DRM;
for_each_active_monitor_output(window, card_path, connection_type, output_monitor_info, &options);
}
int num_monitors = 0;
if(do_list_monitors)
num_monitors = list_monitors(window, card_path);
if(options.num_monitors > 0)
if(num_monitors > 0)
puts("region");
gsr_capture_v4l2_list_devices(camera_query_callback, NULL);
@@ -1933,11 +1941,20 @@ static void version_command(void *userdata) {
_exit(0);
}
static void info_command(void *userdata) {
(void)userdata;
struct WindowingSetup {
Display *dpy;
gsr_window *window;
gsr_egl egl;
bool list_monitors;
};
static WindowingSetup setup_windowing(bool setup_egl) {
WindowingSetup setup;
memset(&setup, 0, sizeof(setup));
bool wayland = false;
Display *dpy = XOpenDisplay(nullptr);
if (!dpy) {
setup.dpy = XOpenDisplay(nullptr);
if (!setup.dpy) {
wayland = true;
fprintf(stderr, "gsr warning: failed to connect to the X server. Assuming wayland is running without Xwayland\n");
}
@@ -1946,7 +1963,7 @@ static void info_command(void *userdata) {
XSetIOErrorHandler(x11_io_error_handler);
if(!wayland)
wayland = is_xwayland(dpy);
wayland = is_xwayland(setup.dpy);
if(!wayland && is_using_prime_run()) {
// Disable prime-run and similar options as it doesn't work, the monitor to capture has to be run on the same device.
@@ -1956,46 +1973,56 @@ static void info_command(void *userdata) {
disable_prime_run();
}
gsr_window *window = gsr_window_create(dpy, wayland);
if(!window) {
setup.window = gsr_window_create(setup.dpy, wayland);
if(!setup.window) {
fprintf(stderr, "gsr error: failed to create window\n");
_exit(1);
}
gsr_egl egl;
if(!gsr_egl_load(&egl, window, false, false)) {
fprintf(stderr, "gsr error: failed to load opengl\n");
_exit(22);
}
setup.list_monitors = true;
bool list_monitors = true;
egl.card_path[0] = '\0';
if(monitor_capture_use_drm(window, egl.gpu_info.vendor)) {
// TODO: Allow specifying another card, and in other places
if(!gsr_get_valid_card_path(&egl, egl.card_path, true)) {
fprintf(stderr, "gsr error: no /dev/dri/cardX device found. Make sure that you have at least one monitor connected\n");
list_monitors = false;
if(setup_egl) {
if(!gsr_egl_load(&setup.egl, setup.window, false, false)) {
fprintf(stderr, "gsr error: failed to load opengl\n");
_exit(22);
}
setup.egl.card_path[0] = '\0';
if(monitor_capture_use_drm(setup.window, setup.egl.gpu_info.vendor)) {
// TODO: Allow specifying another card, and in other places
if(!gsr_get_valid_card_path(&setup.egl, setup.egl.card_path, true)) {
fprintf(stderr, "gsr error: no /dev/dri/cardX device found. Make sure that you have at least one monitor connected\n");
setup.list_monitors = false;
}
}
}
return setup;
}
static void info_command(void *userdata) {
(void)userdata;
WindowingSetup windowing_setup = setup_windowing(true);
const bool wayland = gsr_window_get_display_server(windowing_setup.window) == GSR_DISPLAY_SERVER_WAYLAND;
av_log_set_level(AV_LOG_FATAL);
puts("section=system_info");
list_system_info(wayland);
if(egl.gpu_info.is_steam_deck)
if(windowing_setup.egl.gpu_info.is_steam_deck)
puts("is_steam_deck|yes");
else
puts("is_steam_deck|no");
printf("gsr_version|%s\n", GSR_VERSION);
puts("section=gpu_info");
list_gpu_info(&egl);
list_gpu_info(&windowing_setup.egl);
puts("section=video_codecs");
list_supported_video_codecs(&egl, wayland);
list_supported_video_codecs(&windowing_setup.egl, wayland);
puts("section=image_formats");
puts("jpeg");
puts("png");
puts("section=capture_options");
list_supported_capture_options(window, egl.card_path, list_monitors);
list_supported_capture_options(windowing_setup.window, windowing_setup.egl.card_path, windowing_setup.list_monitors);
fflush(stdout);
@@ -2058,53 +2085,30 @@ static void list_v4l2_devices(void *userdata) {
// |card_path| can be NULL. If not NULL then |vendor| has to be valid
static void list_capture_options_command(const char *card_path, void *userdata) {
(void)userdata;
bool wayland = false;
Display *dpy = XOpenDisplay(nullptr);
if (!dpy) {
wayland = true;
fprintf(stderr, "gsr warning: failed to connect to the X server. Assuming wayland is running without Xwayland\n");
}
WindowingSetup windowing_setup = setup_windowing(card_path != nullptr);
XSetErrorHandler(x11_error_handler);
XSetIOErrorHandler(x11_io_error_handler);
if(card_path)
list_supported_capture_options(windowing_setup.window, card_path, true);
else
list_supported_capture_options(windowing_setup.window, windowing_setup.egl.card_path, windowing_setup.list_monitors);
if(!wayland)
wayland = is_xwayland(dpy);
fflush(stdout);
if(!wayland && is_using_prime_run()) {
// Disable prime-run and similar options as it doesn't work, the monitor to capture has to be run on the same device.
// This is fine on wayland since nvidia uses drm interface there and the monitor query checks the monitors connected
// to the drm device.
fprintf(stderr, "gsr warning: use of prime-run on X11 is not supported. Disabling prime-run\n");
disable_prime_run();
}
// Not needed as this will just slow down shutdown
//gsr_egl_unload(&egl);
//gsr_window_destroy(&window);
//if(dpy)
// XCloseDisplay(dpy);
gsr_window *window = gsr_window_create(dpy, wayland);
if(!window) {
fprintf(stderr, "gsr error: failed to create window\n");
_exit(1);
}
_exit(0);
}
if(card_path) {
list_supported_capture_options(window, card_path, true);
} else {
gsr_egl egl;
if(!gsr_egl_load(&egl, window, false, false)) {
fprintf(stderr, "gsr error: failed to load opengl\n");
_exit(1);
}
static void list_monitors_command(void *userdata) {
(void)userdata;
WindowingSetup windowing_setup = setup_windowing(true);
bool list_monitors = true;
egl.card_path[0] = '\0';
if(monitor_capture_use_drm(window, egl.gpu_info.vendor)) {
// TODO: Allow specifying another card, and in other places
if(!gsr_get_valid_card_path(&egl, egl.card_path, true)) {
fprintf(stderr, "gsr error: no /dev/dri/cardX device found. Make sure that you have at least one monitor connected\n");
list_monitors = false;
}
}
list_supported_capture_options(window, egl.card_path, list_monitors);
}
if(windowing_setup.list_monitors)
list_monitors(windowing_setup.window, windowing_setup.egl.card_path);
fflush(stdout);
@@ -3682,6 +3686,7 @@ int main(int argc, char **argv) {
arg_handlers.list_application_audio = list_application_audio_command;
arg_handlers.list_v4l2_devices = list_v4l2_devices;
arg_handlers.list_capture_options = list_capture_options_command;
arg_handlers.list_monitors = list_monitors_command;
args_parser arg_parser;
if(!args_parser_parse(&arg_parser, argc, argv, &arg_handlers, NULL))

View File

@@ -532,31 +532,36 @@ static bool spa_video_format_get_modifiers(gsr_pipewire_video *self, const enum
static void gsr_pipewire_video_init_modifiers(gsr_pipewire_video *self) {
for(size_t i = 0; i < GSR_PIPEWIRE_VIDEO_NUM_VIDEO_FORMATS; i++) {
self->supported_video_formats[i].format = video_formats[i];
int32_t num_modifiers = 0;
spa_video_format_get_modifiers(self, self->supported_video_formats[i].format, self->modifiers + self->num_modifiers, GSR_PIPEWIRE_VIDEO_MAX_MODIFIERS - self->num_modifiers, &num_modifiers);
int32_t num_modifiers_video_format = 0;
spa_video_format_get_modifiers(self, self->supported_video_formats[i].format, self->modifiers + self->num_modifiers, GSR_PIPEWIRE_VIDEO_MAX_MODIFIERS - self->num_modifiers, &num_modifiers_video_format);
self->supported_video_formats[i].modifiers_index = self->num_modifiers;
self->supported_video_formats[i].modifiers_size = num_modifiers;
self->num_modifiers += num_modifiers;
self->supported_video_formats[i].modifiers_size = num_modifiers_video_format;
self->num_modifiers += num_modifiers_video_format;
}
}
static void gsr_pipewire_video_format_remove_modifier(gsr_pipewire_video *self, gsr_video_format *video_format, uint64_t modifier) {
/* Returns the number of modifiers */
static size_t gsr_pipewire_video_format_remove_modifier(gsr_pipewire_video *self, gsr_video_format *video_format, uint64_t modifier) {
for(size_t i = 0; i < video_format->modifiers_size; ++i) {
if(self->modifiers[video_format->modifiers_index + i] != modifier)
continue;
if(self->modifiers[video_format->modifiers_index + i] == modifier) {
for(size_t j = i + 1; j < video_format->modifiers_size; ++j) {
self->modifiers[video_format->modifiers_index + j - 1] = self->modifiers[video_format->modifiers_index + j];
}
for(size_t j = i + 1; j < video_format->modifiers_size; ++j) {
self->modifiers[j - 1] = self->modifiers[j];
--video_format->modifiers_size;
break;
}
--video_format->modifiers_size;
return;
}
return video_format->modifiers_size;
}
static void gsr_pipewire_video_remove_modifier(gsr_pipewire_video *self, uint64_t modifier) {
self->num_modifiers = 0;
for(size_t i = 0; i < GSR_PIPEWIRE_VIDEO_NUM_VIDEO_FORMATS; i++) {
gsr_video_format *video_format = &self->supported_video_formats[i];
gsr_pipewire_video_format_remove_modifier(self, video_format, modifier);
const size_t num_modifiers_video_format = gsr_pipewire_video_format_remove_modifier(self, video_format, modifier);
video_format->modifiers_index = self->num_modifiers;
self->num_modifiers += num_modifiers_video_format;
}
}
@@ -687,6 +692,7 @@ bool gsr_pipewire_video_init(gsr_pipewire_video *self, int pipewire_fd, uint32_t
self->video_info.fps_num = fps;
self->video_info.fps_den = 1;
self->cursor.visible = capture_cursor;
self->renegotiated_modifier = DRM_FORMAT_MOD_INVALID;
if(!gsr_pipewire_video_setup_stream(self)) {
gsr_pipewire_video_deinit(self);
@@ -792,8 +798,14 @@ static EGLImage gsr_pipewire_video_create_egl_image_with_fallback(gsr_pipewire_v
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 if(self->renegotiated_modifier == self->format.info.raw.modifier) {
fprintf(stderr, "gsr error: gsr_pipewire_video_create_egl_image_with_fallback: modifier 0x%" PRIx64 " failed again after renegotiation, trying without modifiers as last resort\n", self->format.info.raw.modifier);
image = gsr_pipewire_video_create_egl_image(self, fds, offsets, pitches, modifiers, false);
if(image)
self->no_modifiers_fallback = true;
} 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");
fprintf(stderr, "gsr error: gsr_pipewire_video_create_egl_image_with_fallback: failed to create egl image with modifier 0x%" PRIx64 ", renegotiating with a different modifier\n", self->format.info.raw.modifier);
self->renegotiated_modifier = self->format.info.raw.modifier;
self->negotiated = false;
pw_thread_loop_lock(self->thread_loop);
gsr_pipewire_video_remove_modifier(self, self->format.info.raw.modifier);