Compare commits

...

9 Commits
5.7.2 ... 5.8.0

Author SHA1 Message Date
dec05eba
8c42c47627 Disable vaapi debug output 2025-10-05 13:26:09 +02:00
dec05eba
dee37433af 5.8.0 2025-10-05 13:00:16 +02:00
dec05eba
f9f0074f29 Update for ffmpeg 8: remove hevc vaapi padding (black bars), fix garbage output (workaround ffmpeg 8 issue) 2025-10-05 12:57:58 +02:00
dec05eba
50fbee2b2f 5.7.4 2025-10-01 18:17:53 +02:00
Theodoros Orfanidis
c60783fdcb Fix texture size when recording via portal 2025-10-01 17:47:54 +02:00
dec05eba
313d3227d8 TODO 2025-10-01 17:47:45 +02:00
dec05eba
3c5514480c Remove use of libavcodec/defs.h (fix compile on old ffmpeg) 2025-09-29 10:07:08 +02:00
dec05eba
afd140f33b m 2025-09-28 22:26:47 +02:00
dec05eba
c11dd77c44 App audio capture: fix audio sources getting paused when closing gsr 2025-09-25 18:45:43 +02:00
12 changed files with 162 additions and 23 deletions

View File

@@ -33,7 +33,7 @@ If you are running an Arch Linux based distro then you can find gpu screen recor
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 gtk gui 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 flathub --system com.dec05eba.gpu_screen_recorder`).
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.\
@@ -202,7 +202,9 @@ Browsers and discord don't support hevc video codec at the moment. Choose h264 v
Note that websites such as youtube support hevc so there is no need to choose h264 video codec if you intend to upload the video to youtube or if you want to play the video locally or if you intend to
edit the video with a video editor. Hevc allows for better video quality (especially at lower file sizes) so hevc (or av1) is recommended for source videos.
## I get a black bar/distorted colors on the sides in the video
This is mostly an issue on AMD. For av1 it's a hardware issue, see: https://gitlab.freedesktop.org/mesa/mesa/-/issues/9185. For hevc it's a software issue in the AMD driver that hasn't been fixed yet. This issue happens at certain video resolutions. If you get this issue then a workaround is to record with h264 video codec instead (using the -k h264 option).
This is mostly an issue on AMD. For av1 it's a hardware issue, see: https://gitlab.freedesktop.org/mesa/mesa/-/issues/9185. For hevc it's a software issue in ffmpeg that was fixed in ffmpeg version 8.\
If your ffmpeg version is older than 8 then you can use the flatpak version of GPU Screen Recorder which comes with ffmpeg version >= 8.\
Alternatively you can record with h264 codec (-k h264, which is also the default codec) to workaround this issue.
## The video doesn't display or has a green/yellow overlay
This can happen if your video player is missing the H264/HEVC video codecs. Either install the codecs or use mpv.
## I get stutter in the video

10
TODO
View File

@@ -189,13 +189,6 @@ Use wayland color management protocol when it's available: https://gitlab.freede
Use different exit codes for different errors. Use one for invalid -w option, another one for invalid -a option for audio devices, etc. This is to make UI error reporting better.
Document these exit codes in an exit code .md file, or finally create a manpage where this can be documented.
Ffmpeg fixed black bars in videos on amd when using hevc and when recording at some resolutions, such as 1080p:
https://github.com/FFmpeg/FFmpeg/commit/bcfbf2bac8f9eeeedc407b40596f5c7aaa0d5b47
https://github.com/FFmpeg/FFmpeg/commit/d0facac679faf45d3356dff2e2cb382580d7a521
Disable gpu screen recorder black bar handling when using hevc on amd when the libavcodec version is the one that comes after those commits.
Also consider the mesa version, to see if the gpu supports this.
The version is libavcodec >= 61.28.100
Use opengl compute shader instead of graphics shader. This might allow for better performance when games are using 100% of graphics unit which might fix issue with 100% gpu usage causing gpu screen recorder to run slow when not using vaapi to convert rgb to nv12(?).
Always disable prime run/dri prime and list all monitors to record from from all cards.
@@ -330,8 +323,9 @@ Set top level window argument for portal capture. Same for gpu-screen-recorder-g
Remove unix domain socket code from kms-client/server and use socketpair directly. To make this possible always execute the kms server permission setup in flatpak, before starting recording (in gpu-screen-recorder-gtk).
Application audio capture isn't good enough. It creates a sink that for some automatically gets selected as the default output device and it's visible as an output device.
When shutting down gpu screen recorder it will also cause audio applications to pause.
Fix some of these issues by setting gsr-app-sink media class to "Stream/Input/Audio" and node.virtual=true.
However that causes pulseaudio to be unable to record from gsr-app-sink, and it ends up being stuck in pa_sound_device_handle_reconnect in the loop with pa_mainloop_iterate.
Add -k best/best_hdr/best_10bit option, to automatically choose the best codec (prefer av1, then hevc and then h264. For webm files choose vp9 and then vp8).
Check if region capture works properly with fractional scaling on wayland.

View File

@@ -97,6 +97,8 @@ typedef struct {
struct pw_proxy **virtual_sink_proxies;
size_t num_virtual_sink_proxies;
size_t virtual_sink_proxies_capacity_items;
bool running;
} gsr_pipewire_audio;
bool gsr_pipewire_audio_init(gsr_pipewire_audio *self);

View File

@@ -58,6 +58,8 @@ typedef struct {
uint64_t modifiers;
bool using_external_image;
gsr_monitor_rotation rotation;
int texture_width;
int texture_height;
} gsr_map_texture_output;
typedef struct {

View File

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

View File

@@ -293,8 +293,9 @@ static void usage_full() {
printf(" 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");
printf("\n");
printf(" -fm Framerate mode. Should be either 'cfr' (constant frame rate), 'vfr' (variable frame rate) or 'content'. Optional, set to 'vfr' by default.\n");
printf(" '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");
printf(" 'vfr' is recommended for general recording for less issue with very high system load but some applications such as video editors may not support it properly.\n");
printf(" 'content' is currently only supported on X11 or when using portal capture option. The 'content' option matches the recording frame rate to the captured content.\n");
printf(" 'content' is the best option to use when possible as it avoids capturing duplicate frames, which results in a smoother video.\n");
printf("\n");
printf(" -bm Bitrate mode. Should be either 'auto', 'qp' (constant quality), 'vbr' (variable bitrate) or 'cbr' (constant bitrate). Optional, set to 'auto' by default which defaults to 'qp' on all devices\n");
printf(" except steam deck that has broken drivers and doesn't support qp.\n");

View File

@@ -356,9 +356,11 @@ static int gsr_capture_portal_capture(gsr_capture *cap, gsr_capture_metadata *ca
const bool is_scaled = self->params.output_resolution.x > 0 && self->params.output_resolution.y > 0;
vec2i output_size = is_scaled ? self->params.output_resolution : self->capture_size;
output_size = scale_keep_aspect_ratio(self->capture_size, output_size);
const vec2i target_pos = { max_int(0, capture_metadata->width / 2 - output_size.x / 2), max_int(0, capture_metadata->height / 2 - output_size.y / 2) };
const vec2i actual_texture_size = {self->pipewire_data.texture_width, self->pipewire_data.texture_height};
//self->params.egl->glFlush();
//self->params.egl->glFinish();
@@ -370,7 +372,7 @@ static int gsr_capture_portal_capture(gsr_capture *cap, gsr_capture_metadata *ca
gsr_color_conversion_draw(color_conversion, self->pipewire_data.using_external_image ? self->texture_map.external_texture_id : self->texture_map.texture_id,
target_pos, output_size,
(vec2i){self->pipewire_data.region.x, self->pipewire_data.region.y}, self->capture_size, self->capture_size,
(vec2i){self->pipewire_data.region.x, self->pipewire_data.region.y}, (vec2i){self->pipewire_data.region.width, self->pipewire_data.region.height}, actual_texture_size,
gsr_monitor_rotation_to_rotation(self->pipewire_data.rotation), GSR_SOURCE_COLOR_RGB, self->pipewire_data.using_external_image, fourcc_alpha);
if(self->params.record_cursor && self->texture_map.cursor_texture_id > 0 && self->pipewire_data.cursor_region.width > 0) {

View File

@@ -6,6 +6,8 @@
#include <libavutil/hwcontext_vaapi.h>
#include <libavutil/intreadwrite.h>
#include <va/va.h>
#include <va/va_drm.h>
#include <va/va_drmcommon.h>
#include <stdlib.h>
@@ -150,13 +152,102 @@ static bool gsr_video_encoder_vaapi_setup_textures(gsr_video_encoder_vaapi *self
static void gsr_video_encoder_vaapi_stop(gsr_video_encoder_vaapi *self, AVCodecContext *video_codec_context);
static bool supports_hevc_without_padding(const char *card_path) {
#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(61, 28, 100) && VA_CHECK_VERSION(1, 21, 0)
VAStatus va_status;
VAConfigID va_config = 0;
unsigned int num_surface_attr = 0;
VASurfaceAttrib *surface_attr_list = NULL;
bool supports_surface_attrib_alignment_size = false;
int va_major = 0;
int va_minor = 0;
bool initialized = false;
char render_path[128];
if(!gsr_card_path_get_render_path(card_path, render_path)) {
fprintf(stderr, "gsr error: supports_hevc_without_padding: failed to get /dev/dri/renderDXXX file from %s\n", card_path);
return false;
}
const int drm_fd = open(render_path, O_RDWR);
if(drm_fd == -1) {
fprintf(stderr, "gsr error: supports_hevc_without_padding: failed to open device %s\n", render_path);
return false;
}
const VADisplay va_dpy = vaGetDisplayDRM(drm_fd);
if(!va_dpy) {
fprintf(stderr, "gsr error: supports_hevc_without_padding: failed to get vaapi display for device %s\n", render_path);
goto done;
}
vaSetInfoCallback(va_dpy, NULL, NULL);
if(vaInitialize(va_dpy, &va_major, &va_minor) != VA_STATUS_SUCCESS) {
fprintf(stderr, "gsr error: supports_hevc_without_padding: vaInitialize failed\n");
goto done;
}
initialized = true;
va_status = vaCreateConfig(va_dpy, VAProfileHEVCMain, VAEntrypointEncSlice, NULL, 0, &va_config);
if(va_status != VA_STATUS_SUCCESS) {
va_status = vaCreateConfig(va_dpy, VAProfileHEVCMain, VAEntrypointEncSliceLP, NULL, 0, &va_config);
if(va_status != VA_STATUS_SUCCESS) {
fprintf(stderr, "gsr error: supports_hevc_without_padding: failed to create hevc vaapi config, error: %s (%d)\n", vaErrorStr(va_status), va_status);
return false;
}
}
va_status = vaQuerySurfaceAttributes(va_dpy, va_config, 0, &num_surface_attr);
if(va_status != VA_STATUS_SUCCESS) {
fprintf(stderr, "gsr error: supports_hevc_without_padding: failed to query vaapi surface attributes size, error: %s (%d)\n", vaErrorStr(va_status), va_status);
goto done;
}
surface_attr_list = malloc(num_surface_attr * sizeof(VASurfaceAttrib));
if(!surface_attr_list) {
fprintf(stderr, "gsr error: supports_hevc_without_padding: failed to allocate memory for %u vaapi surface attributes, error: %s (%d)\n", num_surface_attr, vaErrorStr(va_status), va_status);
goto done;
}
va_status = vaQuerySurfaceAttributes(va_dpy, va_config, surface_attr_list, &num_surface_attr);
if(va_status != VA_STATUS_SUCCESS) {
fprintf(stderr, "gsr error: supports_hevc_without_padding: failed to query vaapi surface attributes data, error: %s (%d)\n", vaErrorStr(va_status), va_status);
goto done;
}
for(unsigned int i = 0; i < num_surface_attr; ++i) {
if(surface_attr_list[i].type == VASurfaceAttribAlignmentSize) {
supports_surface_attrib_alignment_size = true;
break;
}
}
done:
free(surface_attr_list);
if(va_config > 0)
vaDestroyConfig(va_dpy, va_config);
if(initialized)
vaTerminate(va_dpy);
if(drm_fd > 0)
close(drm_fd);
return supports_surface_attrib_alignment_size;
#else
return false;
#endif
}
static bool gsr_video_encoder_vaapi_start(gsr_video_encoder *encoder, AVCodecContext *video_codec_context, AVFrame *frame) {
gsr_video_encoder_vaapi *self = encoder->priv;
if(self->params.egl->gpu_info.vendor == GSR_GPU_VENDOR_AMD && video_codec_context->codec_id == AV_CODEC_ID_HEVC) {
// TODO: dont do this if using ffmpeg reports that this is not needed (AMD driver bug that was fixed recently)
video_codec_context->width = FFALIGN(video_codec_context->width, 64);
video_codec_context->height = FFALIGN(video_codec_context->height, 16);
if(supports_hevc_without_padding(self->params.egl->card_path)) {
video_codec_context->width = FFALIGN(video_codec_context->width, 2);
video_codec_context->height = FFALIGN(video_codec_context->height, 2);
} else {
video_codec_context->width = FFALIGN(video_codec_context->width, 64);
video_codec_context->height = FFALIGN(video_codec_context->height, 16);
}
} else if(self->params.egl->gpu_info.vendor == GSR_GPU_VENDOR_AMD && video_codec_context->codec_id == AV_CODEC_ID_AV1) {
// TODO: Dont do this for VCN 5 and forward which should fix this hardware bug
video_codec_context->width = FFALIGN(video_codec_context->width, 64);

View File

@@ -48,7 +48,6 @@ extern "C" {
extern "C" {
#include <libavutil/pixfmt.h>
#include <libavcodec/avcodec.h>
#include <libavcodec/defs.h>
#include <libavformat/avformat.h>
#include <libavutil/opt.h>
#include <libswresample/swresample.h>
@@ -381,8 +380,9 @@ static AVCodecContext *create_video_codec_context(AVPixelFormat pix_fmt, const A
codec_context->colorspace = AVCOL_SPC_BT709;
}
//codec_context->chroma_sample_location = AVCHROMA_LOC_CENTER;
if(codec->id == AV_CODEC_ID_HEVC)
codec_context->codec_tag = MKTAG('h', 'v', 'c', '1'); // QuickTime on MacOS requires this or the video wont be playable
// Can't use this because it's fucking broken in ffmpeg 8 or new mesa. It produces garbage output
//if(codec->id == AV_CODEC_ID_HEVC)
// codec_context->codec_tag = MKTAG('h', 'v', 'c', '1'); // QuickTime on MacOS requires this or the video wont be playable
if(arg_parser.bitrate_mode == GSR_BITRATE_MODE_CBR) {
codec_context->bit_rate = arg_parser.video_bitrate;

View File

@@ -392,12 +392,12 @@ static void registry_event_global(void *data, uint32_t id, uint32_t permissions,
const struct spa_dict *props)
{
//fprintf(stderr, "add: id: %d, type: %s\n", (int)id, type);
if(!props || !type)
gsr_pipewire_audio *self = (gsr_pipewire_audio*)data;
if(!props || !type || !self->running)
return;
//pw_properties_new_dict(props);
gsr_pipewire_audio *self = (gsr_pipewire_audio*)data;
if(strcmp(type, PW_TYPE_INTERFACE_Node) == 0) {
const char *node_name = spa_dict_lookup(props, PW_KEY_NODE_NAME);
const char *media_class = spa_dict_lookup(props, PW_KEY_MEDIA_CLASS);
@@ -547,6 +547,7 @@ static const struct pw_registry_events registry_events = {
bool gsr_pipewire_audio_init(gsr_pipewire_audio *self) {
memset(self, 0, sizeof(*self));
self->running = true;
pw_init(NULL, NULL);
@@ -594,8 +595,49 @@ bool gsr_pipewire_audio_init(gsr_pipewire_audio *self) {
return true;
}
static gsr_pipewire_audio_link* gsr_pipewire_audio_get_first_link_to_node(gsr_pipewire_audio *self, uint32_t node_id) {
for(size_t i = 0; i < self->num_links; ++i) {
if(self->links[i].input_node_id == node_id)
return &self->links[i];
}
return NULL;
}
static void gsr_pipewire_audio_destroy_requested_links(gsr_pipewire_audio *self) {
pw_thread_loop_lock(self->thread_loop);
self->server_version_sync = pw_core_sync(self->core, PW_ID_CORE, self->server_version_sync);
pw_thread_loop_wait(self->thread_loop);
for(size_t requested_link_index = 0; requested_link_index < self->num_requested_links; ++requested_link_index) {
const gsr_pipewire_audio_node_type requested_link_node_type = self->requested_links[requested_link_index].input_type == GSR_PIPEWIRE_AUDIO_LINK_INPUT_TYPE_STREAM ? GSR_PIPEWIRE_AUDIO_NODE_TYPE_STREAM_INPUT : GSR_PIPEWIRE_AUDIO_NODE_TYPE_SINK_OR_SOURCE;
const gsr_pipewire_audio_node *stream_input_node = gsr_pipewire_audio_get_node_by_name_case_insensitive(self, self->requested_links[requested_link_index].input_name, requested_link_node_type);
if(!stream_input_node)
continue;
for(;;) {
gsr_pipewire_audio_link *link = gsr_pipewire_audio_get_first_link_to_node(self, stream_input_node->id);
if(!link)
break;
pw_registry_destroy(self->registry, link->id);
self->server_version_sync = pw_core_sync(self->core, PW_ID_CORE, self->server_version_sync);
pw_thread_loop_wait(self->thread_loop);
usleep(10 * 1000);
}
}
pw_thread_loop_unlock(self->thread_loop);
}
void gsr_pipewire_audio_deinit(gsr_pipewire_audio *self) {
self->running = false;
if(self->thread_loop) {
/* We need to manually destroy links first, otherwise the linked audio sources will be paused when closing the program */
gsr_pipewire_audio_destroy_requested_links(self);
//pw_thread_loop_wait(self->thread_loop);
pw_thread_loop_stop(self->thread_loop);
}

View File

@@ -871,6 +871,9 @@ bool gsr_pipewire_video_map_texture(gsr_pipewire_video *self, gsr_texture_map te
gsr_pipewire_video_update_cursor_texture(self, texture_map);
output->texture_width = self->format.info.raw.size.width;
output->texture_height = self->format.info.raw.size.height;
output->region.x = 0;
output->region.y = 0;