Compare commits

..

13 Commits
5.8.2 ... 5.9.1

16 changed files with 152 additions and 167 deletions

View File

@@ -102,11 +102,8 @@ You can see a list of capture options to record if you run `gpu-screen-recorder
```
in this case you could record a window or a monitor with the name `DP-1`.\
To list available audio devices that you can use you can run `gpu-screen-recorder --list-audio-devices` and the name to use is on the left size of the `|`.\
To list available audio application names that you can use you can run `gpu-screen-recorder --list-application-audio`.
## Streaming
Streaming works the same way as recording, but the `-o` argument should be path to the live streaming service you want to use (including your live streaming key). Take a look at `scripts/twitch-stream.sh` to see an example of how to stream to twitch.\
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.
To list available audio application names that you can use you can run `gpu-screen-recorder --list-application-audio`.\
You can run `gpu-screen-recorder --info` to list more information about the system, such as the device that is used for capture and video encoding and supported codecs. These commands can be parsed by scripts/programs.
## Replay mode
Run `gpu-screen-recorder` with the `-c mp4` and `-r` option, for example: `gpu-screen-recorder -w screen -f 60 -r 30 -c mp4 -o ~/Videos`. Note that in this case, `-o` should point to a directory.\
If `-df yes` is set, replays are save in folders based on the date.
@@ -117,6 +114,10 @@ This can be used for example to show a notification when a replay has been saved
The replay buffer is stored in ram (as encoded video) by default, so don't use a too large replay time and/or video quality unless you have enough ram to store it.\
You can use the `-replay-storage disk` option to store the replay buffer on disk instead of ram (in the same location as the output video).\
By default videos are recorded with constant quality, but with replay mode you might want to record in constant bitrate mode instead for consistent ram/disk usage in high motion scenes. You can do that by using the `-bm cbr` option (along with `-q` option, for example `-bm cbr -q 20000`).
## Streaming
Streaming works the same way as recording, but the `-o` argument should be path to the live streaming service you want to use (including your live streaming key). Take a look at `scripts/twitch-stream.sh` to see an example of how to stream to twitch.\
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.\
@@ -150,12 +151,28 @@ If you record at your monitors refresh rate and enabled vsync in a game then the
This is an issue in some games.
If you experience this issue then you might want to either disable vsync in the game or use the `-fm content` option to sync capture to the content on the screen. For example: `gpu-screen-recorder -w screen -fm content -o video.mp4`.\
Note that this option is currently only available on X11, or with desktop portal capture on Wayland (`-w portal`).
# Performance
On a system with an i5 4690k CPU and a GTX 1080 GPU:\
When recording Legend of Zelda Breath of the Wild at 4k, fps drops from 30 to 7 when using OBS Studio + nvenc, however when using this screen recorder the fps remains at 30.\
When recording GTA V at 4k on highest settings, fps drops from 60 to 23 when using obs-nvfbc + nvenc, however when using this screen recorder the fps only drops to 58.\
On a system with an AMD Ryzen 9 5900X CPU and an RX 7800XT GPU I dont see any fps drop at all, even when recording at 4k 60fps with AV1 codec with 10-bit colors.\
GPU Screen Recorder also produces much smoother videos than OBS when GPU utilization is close to 100%, see comparison here: [https://www.youtube.com/watch?v=zfj4sNVLLLg](https://www.youtube.com/watch?v=zfj4sNVLLLg) and [https://www.youtube.com/watch?v=aK67RSZw2ZQ](https://www.youtube.com/watch?v=aK67RSZw2ZQ).\
GPU Screen Recorder has much better performance than OBS Studio even with version 30.2 that does "zero-copy" recording and encoding, see: [https://www.youtube.com/watch?v=jdroRjibsDw](https://www.youtube.com/watch?v=jdroRjibsDw).\
It is recommended to save the video to a SSD because of the large file size, which a slow HDD might not be fast enough to handle. Using variable framerate mode (-fm vfr) which is the default is also recommended as this reduces encoding load. Ultra quality is also overkill most of the time, very high (the default) or lower quality is usually enough.\
Note that for best performance you should close other screen recorders such as OBS Studio when using GPU Screen Recorder even if they are not recording, since they can affect performance even when idle. This is the case with OBS Studio.
## Note about optimal performance on NVIDIA
NVIDIA driver has a "feature" (read: bug) where it will downclock memory transfer rate when a program uses cuda (or nvenc, which uses cuda), such as GPU Screen Recorder. To work around this bug, GPU Screen Recorder can overclock your GPU memory transfer rate to it's normal optimal level.\
To enable overclocking for optimal performance use the `-oc` option when running GPU Screen Recorder. You also need to have "Coolbits" NVIDIA X setting set to "12" to enable overclocking. You can automatically add this option if you run `sudo nvidia-xconfig --cool-bits=12` and then reboot your computer.\
Note that this only works when Xorg server is running as root, and using this option will only give you a performance boost if the game you are recording is bottlenecked by your GPU.\
Note! use at your own risk!
# Issues
## NVIDIA
Nvidia drivers have an issue where CUDA breaks if CUDA is running when suspend/hibernation happens, and it remains broken until you reload the nvidia driver. `extra/gsr-nvidia.conf` will be installed by default when you install GPU Screen Recorder and that should fix this issue. If this doesn't fix the issue for you then your distro may use a different path for modprobe files. In that case you have to install that `extra/gsr-nvidia.conf` yourself into that location.
You have to reboot your computer after installing GPU Screen Recorder for the first time for the fix to have any effect.
# TEMPORARY ISSUES
## TEMPORARY ISSUES
1) Videos are in variable framerate format. Use MPV to play such videos, otherwise you might experience stuttering in the video if you are using a buggy video player. You can try saving the video into a .mkv file instead as some software may have better support for .mkv files (such as kdenlive). You can use the "-fm cfr" option to to use constant framerate mode.
2) FLAC audio codec is disabled at the moment because of temporary issues.
@@ -167,19 +184,7 @@ When recording a window or when using the `-w portal` option no special user per
however when recording a monitor the program needs root permission (to access KMS).\
This is safe in GPU Screen Recorder as the part that needs root access has been moved to its own small program that only does one thing.\
For you as a user this only means that if you installed GPU Screen Recorder as a flatpak then a prompt asking for root password will show up once when you start recording.
# Performance
On a system with a i5 4690k CPU and a GTX 1080 GPU:\
When recording Legend of Zelda Breath of the Wild at 4k, fps drops from 30 to 7 when using OBS Studio + nvenc, however when using this screen recorder the fps remains at 30.\
When recording GTA V at 4k on highest settings, fps drops from 60 to 23 when using obs-nvfbc + nvenc, however when using this screen recorder the fps only drops to 58.\
GPU Screen Recorder also produces much smoother videos than OBS when GPU utilization is close to 100%, see comparison here: [https://www.youtube.com/watch?v=zfj4sNVLLLg](https://www.youtube.com/watch?v=zfj4sNVLLLg) and [https://www.youtube.com/watch?v=aK67RSZw2ZQ](https://www.youtube.com/watch?v=aK67RSZw2ZQ).\
GPU Screen Recorder has much better performance than OBS Studio even with version 30.2 that does "zero-copy" recording and encoding, see: [https://www.youtube.com/watch?v=jdroRjibsDw](https://www.youtube.com/watch?v=jdroRjibsDw).\
It is recommended to save the video to a SSD because of the large file size, which a slow HDD might not be fast enough to handle. Using variable framerate mode (-fm vfr) which is the default is also recommended as this reduces encoding load. Ultra quality is also overkill most of the time, very high (the default) or lower quality is usually enough.\
Note that for best performance you should close other screen recorders such as OBS Studio when using GPU Screen Recorder even if they are not recording, since they can affect performance even when idle. This is the case with OBS Studio.
## Note about optimal performance on NVIDIA
NVIDIA driver has a "feature" (read: bug) where it will downclock memory transfer rate when a program uses cuda (or nvenc, which uses cuda), such as GPU Screen Recorder. To work around this bug, GPU Screen Recorder can overclock your GPU memory transfer rate to it's normal optimal level.\
To enable overclocking for optimal performance use the `-oc` option when running GPU Screen Recorder. You also need to have "Coolbits" NVIDIA X setting set to "12" to enable overclocking. You can automatically add this option if you run `sudo nvidia-xconfig --cool-bits=12` and then reboot your computer.\
Note that this only works when Xorg server is running as root, and using this option will only give you a performance boost if the game you are recording is bottlenecked by your GPU.\
Note! use at your own risk!
# VRR/G-SYNC
This should work fine on AMD/Intel X11 or Wayland. On Nvidia X11 G-SYNC only works with the -w screen-direct option, but because of bugs in the Nvidia driver this option is not always recommended.
For example it can cause your computer to freeze when recording certain games.

8
TODO
View File

@@ -61,8 +61,6 @@ Remove follow focused option.
Exit if X11/Wayland killed (if drm plane dead or something?)
Use SRC_W and SRC_H for screen plane instead of crtc_w and crtc_h.
Test if p2 state can be worked around by using pure nvenc api and overwriting cuInit/cuCtxCreate* to not do anything. Cuda might be loaded when using nvenc but it might not be used, with certain record options? (such as h264 p5).
nvenc uses cuda when using b frames and rgb->yuv conversion, so convert the image ourselves instead.-
@@ -81,8 +79,6 @@ When vulkan encode is added, mention minimum nvidia driver required. (550.54.14?
Investigate if there is a way to do gpu->gpu copy directly without touching system ram to enable video encoding on a different gpu. On nvidia this is possible with cudaMemcpyPeer, but how about from an intel/amd gpu to an nvidia gpu or the other way around or any combination of iGPU and dedicated GPU?
Maybe something with clEnqueueMigrateMemObjects? on AMD something with DirectGMA maybe?
Go back to using pure vaapi without opengl for video encoding? rotation (transpose) can be done if its done after (rgb to yuv) color conversion.
Use lanczos resampling for better scaling quality. Lanczos resampling can also be used for YUV chroma for better color quality on small text.
Flac is disabled because the frame sizes are too large which causes big audio/video desync.
@@ -349,4 +345,6 @@ Support pausing recording when recording while replay/streaming.
Maybe use VK_VALVE_video_encode_rgb_conversion with vulkan encoding for shader-less rgb to yuv conversion. That would allow screen capture with no gpu processing.
Cursor sometimes doesn't have color when capturing region scaled (on kde plasma wayland at least).
Cursor sometimes doesn't have color when capturing region scaled (on kde plasma wayland at least).
Remove drm_monitor_get_display_server_data and do that work in the drm monitor query.

View File

@@ -14,13 +14,17 @@ typedef struct AVContentLightMetadata AVContentLightMetadata;
typedef struct gsr_capture gsr_capture;
typedef struct {
int width;
int height;
// Width and height of the video
int video_width;
int video_height;
// Width and height of the frame at the start of capture, the target size
int recording_width;
int recording_height;
int fps;
} gsr_capture_metadata;
struct gsr_capture {
/* These methods should not be called manually. Call gsr_capture_* instead. |capture_metdata->width| and |capture_metadata->height| should be set by this function */
/* These methods should not be called manually. Call gsr_capture_* instead. |capture_metadata->video_width| and |capture_metadata->video_height| should be set by this function */
int (*start)(gsr_capture *cap, gsr_capture_metadata *capture_metadata);
void (*on_event)(gsr_capture *cap, gsr_egl *egl); /* can be NULL */
void (*tick)(gsr_capture *cap); /* can be NULL. If there is an event then |on_event| is called before this */

View File

@@ -10,23 +10,14 @@ typedef enum {
GSR_IMAGE_FORMAT_PNG
} gsr_image_format;
typedef enum {
GSR_IMAGE_WRITER_SOURCE_OPENGL,
GSR_IMAGE_WRITER_SOURCE_MEMORY
} gsr_image_writer_source;
typedef struct {
gsr_image_writer_source source;
gsr_egl *egl;
int width;
int height;
unsigned int texture;
const void *memory; /* Reference */
} gsr_image_writer;
bool gsr_image_writer_init_opengl(gsr_image_writer *self, gsr_egl *egl, int width, int height);
/* |memory| is taken as a reference. The data is expected to be in rgba8 format (8 bit rgba) */
bool gsr_image_writer_init_memory(gsr_image_writer *self, const void *memory, int width, int height);
void gsr_image_writer_deinit(gsr_image_writer *self);
/* Quality is between 1 and 100 where 100 is the max quality. Quality doesn't apply to lossless formats */

View File

@@ -59,8 +59,8 @@ struct gsr_kms_response_item {
gsr_kms_rotation rotation;
int x;
int y;
int crtc_w;
int crtc_h;
int src_w;
int src_h;
struct hdr_output_metadata hdr_metadata;
};

View File

@@ -141,21 +141,21 @@ typedef enum {
PLANE_PROPERTY_Y = 1 << 1,
PLANE_PROPERTY_SRC_X = 1 << 2,
PLANE_PROPERTY_SRC_Y = 1 << 3,
PLANE_PROPERTY_CRTC_W = 1 << 4,
PLANE_PROPERTY_CRTC_H = 1 << 5,
PLANE_PROPERTY_SRC_W = 1 << 4,
PLANE_PROPERTY_SRC_H = 1 << 5,
PLANE_PROPERTY_IS_CURSOR = 1 << 6,
PLANE_PROPERTY_IS_PRIMARY = 1 << 7,
PLANE_PROPERTY_ROTATION = 1 << 8,
} plane_property_mask;
/* Returns plane_property_mask */
static uint32_t plane_get_properties(int drmfd, uint32_t plane_id, int *x, int *y, int *src_x, int *src_y, int *crtc_w, int *crtc_h, gsr_kms_rotation *rotation) {
static uint32_t plane_get_properties(int drmfd, uint32_t plane_id, int *x, int *y, int *src_x, int *src_y, int *src_w, int *src_h, gsr_kms_rotation *rotation) {
*x = 0;
*y = 0;
*src_x = 0;
*src_y = 0;
*crtc_w = 0;
*crtc_h = 0;
*src_w = 0;
*src_h = 0;
*rotation = KMS_ROT_0;
plane_property_mask property_mask = 0;
@@ -184,12 +184,12 @@ static uint32_t plane_get_properties(int drmfd, uint32_t plane_id, int *x, int *
} else if((type & DRM_MODE_PROP_RANGE) && strcmp(prop->name, "SRC_Y") == 0) {
*src_y = (int)(props->prop_values[i] >> 16);
property_mask |= PLANE_PROPERTY_SRC_Y;
} else if((type & DRM_MODE_PROP_RANGE) && strcmp(prop->name, "CRTC_W") == 0) {
*crtc_w = props->prop_values[i];
property_mask |= PLANE_PROPERTY_CRTC_W;
} else if((type & DRM_MODE_PROP_RANGE) && strcmp(prop->name, "CRTC_H") == 0) {
*crtc_h = props->prop_values[i];
property_mask |= PLANE_PROPERTY_CRTC_H;
} else if((type & DRM_MODE_PROP_RANGE) && strcmp(prop->name, "SRC_W") == 0) {
*src_w = (int)(props->prop_values[i] >> 16);
property_mask |= PLANE_PROPERTY_SRC_W;
} else if((type & DRM_MODE_PROP_RANGE) && strcmp(prop->name, "SRC_H") == 0) {
*src_h = (int)(props->prop_values[i] >> 16);
property_mask |= PLANE_PROPERTY_SRC_H;
} else if((type & DRM_MODE_PROP_ENUM) && strcmp(prop->name, "type") == 0) {
const uint64_t current_enum_value = props->prop_values[i];
for(int j = 0; j < prop->count_enums; ++j) {
@@ -351,9 +351,9 @@ static int kms_get_fb(gsr_drm *drm, gsr_kms_response *response) {
// TODO: Check if dimensions have changed by comparing width and height to previous time this was called.
// TODO: Support other plane formats than rgb (with multiple planes, such as direct YUV420 on wayland).
int x = 0, y = 0, src_x = 0, src_y = 0, crtc_w = 0, crtc_h = 0;
int x = 0, y = 0, src_x = 0, src_y = 0, src_w = 0, src_h = 0;
gsr_kms_rotation rotation = KMS_ROT_0;
const uint32_t property_mask = plane_get_properties(drm->drmfd, plane->plane_id, &x, &y, &src_x, &src_y, &crtc_w, &crtc_h, &rotation);
const uint32_t property_mask = plane_get_properties(drm->drmfd, plane->plane_id, &x, &y, &src_x, &src_y, &src_w, &src_h, &rotation);
if(!(property_mask & PLANE_PROPERTY_IS_PRIMARY) && !(property_mask & PLANE_PROPERTY_IS_CURSOR))
continue;
@@ -392,13 +392,13 @@ static int kms_get_fb(gsr_drm *drm, gsr_kms_response *response) {
if(property_mask & PLANE_PROPERTY_IS_CURSOR) {
response->items[item_index].x = x;
response->items[item_index].y = y;
response->items[item_index].crtc_w = 0;
response->items[item_index].crtc_h = 0;
response->items[item_index].src_w = 0;
response->items[item_index].src_h = 0;
} else {
response->items[item_index].x = src_x;
response->items[item_index].y = src_y;
response->items[item_index].crtc_w = crtc_w;
response->items[item_index].crtc_h = crtc_h;
response->items[item_index].src_w = src_w;
response->items[item_index].src_h = src_h;
}
++response->num_items;

View File

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

View File

@@ -209,14 +209,14 @@ static int gsr_capture_kms_start(gsr_capture *cap, gsr_capture_metadata *capture
if(self->params.output_resolution.x > 0 && self->params.output_resolution.y > 0) {
self->params.output_resolution = scale_keep_aspect_ratio(self->capture_size, self->params.output_resolution);
capture_metadata->width = self->params.output_resolution.x;
capture_metadata->height = self->params.output_resolution.y;
capture_metadata->video_width = self->params.output_resolution.x;
capture_metadata->video_height = self->params.output_resolution.y;
} else if(self->params.region_size.x > 0 && self->params.region_size.y > 0) {
capture_metadata->width = self->params.region_size.x;
capture_metadata->height = self->params.region_size.y;
capture_metadata->video_width = self->params.region_size.x;
capture_metadata->video_height = self->params.region_size.y;
} else {
capture_metadata->width = self->capture_size.x;
capture_metadata->height = self->capture_size.y;
capture_metadata->video_width = self->capture_size.x;
capture_metadata->video_height = self->capture_size.y;
}
self->last_time_monitor_check = clock_get_monotonic_seconds();
@@ -542,7 +542,7 @@ static void render_x11_cursor(gsr_capture_kms *self, gsr_color_conversion *color
}
static void gsr_capture_kms_update_capture_size_change(gsr_capture_kms *self, gsr_color_conversion *color_conversion, vec2i target_pos, const gsr_kms_response_item *drm_fd) {
if(target_pos.x != self->prev_target_pos.x || target_pos.y != self->prev_target_pos.y || drm_fd->crtc_w != self->prev_plane_size.x || drm_fd->crtc_h != self->prev_plane_size.y) {
if(target_pos.x != self->prev_target_pos.x || target_pos.y != self->prev_target_pos.y || drm_fd->src_w != self->prev_plane_size.x || drm_fd->src_h != self->prev_plane_size.y) {
self->prev_target_pos = target_pos;
self->prev_plane_size = self->capture_size;
gsr_color_conversion_clear(color_conversion);
@@ -621,15 +621,12 @@ static int gsr_capture_kms_capture(gsr_capture *cap, gsr_capture_metadata *captu
if(drm_fd->has_hdr_metadata && self->params.hdr && hdr_metadata_is_supported_format(&drm_fd->hdr_metadata))
gsr_kms_set_hdr_metadata(self, drm_fd);
self->capture_size = rotate_capture_size_if_rotated(self, (vec2i){ drm_fd->crtc_w, drm_fd->crtc_h });
self->capture_size = rotate_capture_size_if_rotated(self, (vec2i){ drm_fd->src_w, drm_fd->src_h });
if(self->params.region_size.x > 0 && self->params.region_size.y > 0)
self->capture_size = self->params.region_size;
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 output_size = scale_keep_aspect_ratio(self->capture_size, (vec2i){capture_metadata->recording_width, capture_metadata->recording_height});
const vec2i target_pos = { max_int(0, capture_metadata->video_width / 2 - output_size.x / 2), max_int(0, capture_metadata->video_height / 2 - output_size.y / 2) };
gsr_capture_kms_update_capture_size_change(self, color_conversion, target_pos, drm_fd);
vec2i capture_pos = self->capture_pos;
@@ -649,7 +646,7 @@ static int gsr_capture_kms_capture(gsr_capture *cap, gsr_capture_metadata *captu
}
const gsr_monitor_rotation plane_rotation = kms_rotation_to_gsr_monitor_rotation(drm_fd->rotation);
const gsr_monitor_rotation rotation = sub_rotations(self->monitor_rotation, plane_rotation);
const gsr_monitor_rotation rotation = capture_is_combined_plane ? GSR_MONITOR_ROT_0 : sub_rotations(self->monitor_rotation, plane_rotation);
gsr_color_conversion_draw(color_conversion, self->external_texture_fallback ? self->external_input_texture_id : self->input_texture_id,
target_pos, output_size,
@@ -668,7 +665,7 @@ static int gsr_capture_kms_capture(gsr_capture *cap, gsr_capture_metadata *captu
cursor_monitor_offset.y += self->params.region_position.y;
render_x11_cursor(self, color_conversion, cursor_monitor_offset, target_pos, output_size);
} else if(cursor_drm_fd) {
const vec2i framebuffer_size = rotate_capture_size_if_rotated(self, (vec2i){ drm_fd->crtc_w, drm_fd->crtc_h });
const vec2i framebuffer_size = rotate_capture_size_if_rotated(self, (vec2i){ drm_fd->src_w, drm_fd->src_h });
render_drm_cursor(self, color_conversion, cursor_drm_fd, target_pos, output_size, framebuffer_size);
}
}

View File

@@ -283,16 +283,16 @@ static int gsr_capture_nvfbc_start(gsr_capture *cap, gsr_capture_metadata *captu
goto error_cleanup;
}
capture_metadata->width = self->tracking_width;
capture_metadata->height = self->tracking_height;
capture_metadata->video_width = self->tracking_width;
capture_metadata->video_height = self->tracking_height;
if(self->params.output_resolution.x > 0 && self->params.output_resolution.y > 0) {
self->params.output_resolution = scale_keep_aspect_ratio((vec2i){capture_metadata->width, capture_metadata->height}, self->params.output_resolution);
capture_metadata->width = self->params.output_resolution.x;
capture_metadata->height = self->params.output_resolution.y;
self->params.output_resolution = scale_keep_aspect_ratio((vec2i){capture_metadata->recording_width, capture_metadata->recording_height}, self->params.output_resolution);
capture_metadata->video_width = self->params.output_resolution.x;
capture_metadata->video_height = self->params.output_resolution.y;
} else if(self->params.region_size.x > 0 && self->params.region_size.y > 0) {
capture_metadata->width = self->params.region_size.x;
capture_metadata->height = self->params.region_size.y;
capture_metadata->video_width = self->params.region_size.x;
capture_metadata->video_height = self->params.region_size.y;
}
return 0;
@@ -333,11 +333,8 @@ static int gsr_capture_nvfbc_capture(gsr_capture *cap, gsr_capture_metadata *cap
if(self->params.region_size.x > 0 && self->params.region_size.y > 0)
frame_size = self->params.region_size;
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 : frame_size;
output_size = scale_keep_aspect_ratio(frame_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 output_size = scale_keep_aspect_ratio(frame_size, (vec2i){capture_metadata->recording_width, capture_metadata->recording_height});
const vec2i target_pos = { max_int(0, capture_metadata->video_width / 2 - output_size.x / 2), max_int(0, capture_metadata->video_height / 2 - output_size.y / 2) };
NVFBC_FRAME_GRAB_INFO frame_info;
memset(&frame_info, 0, sizeof(frame_info));

View File

@@ -293,12 +293,12 @@ static int gsr_capture_portal_start(gsr_capture *cap, gsr_capture_metadata *capt
}
if(self->params.output_resolution.x == 0 && self->params.output_resolution.y == 0) {
capture_metadata->width = self->capture_size.x;
capture_metadata->height = self->capture_size.y;
capture_metadata->video_width = self->capture_size.x;
capture_metadata->video_height = self->capture_size.y;
} else {
self->params.output_resolution = scale_keep_aspect_ratio(self->capture_size, self->params.output_resolution);
capture_metadata->width = self->params.output_resolution.x;
capture_metadata->height = self->params.output_resolution.y;
capture_metadata->video_width = self->params.output_resolution.x;
capture_metadata->video_height = self->params.output_resolution.y;
}
return 0;
@@ -353,11 +353,8 @@ 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 output_size = scale_keep_aspect_ratio(self->capture_size, (vec2i){capture_metadata->recording_width, capture_metadata->recording_height});
const vec2i target_pos = { max_int(0, capture_metadata->video_width / 2 - output_size.x / 2), max_int(0, capture_metadata->video_height / 2 - output_size.y / 2) };
const vec2i actual_texture_size = {self->pipewire_data.texture_width, self->pipewire_data.texture_height};

View File

@@ -104,11 +104,11 @@ static int gsr_capture_xcomposite_start(gsr_capture *cap, gsr_capture_metadata *
self->texture_size.y = self->window_texture.window_height;
if(self->params.output_resolution.x == 0 && self->params.output_resolution.y == 0) {
capture_metadata->width = self->texture_size.x;
capture_metadata->height = self->texture_size.y;
capture_metadata->video_width = self->texture_size.x;
capture_metadata->video_height = self->texture_size.y;
} else {
capture_metadata->width = self->params.output_resolution.x;
capture_metadata->height = self->params.output_resolution.y;
capture_metadata->video_width = self->params.output_resolution.x;
capture_metadata->video_height = self->params.output_resolution.y;
}
self->window_resize_timer = clock_get_monotonic_seconds();
@@ -224,7 +224,7 @@ static bool gsr_capture_xcomposite_should_stop(gsr_capture *cap, bool *err) {
return false;
}
static int gsr_capture_xcomposite_capture(gsr_capture *cap, gsr_capture_metadata *capture_metdata, gsr_color_conversion *color_conversion) {
static int gsr_capture_xcomposite_capture(gsr_capture *cap, gsr_capture_metadata *capture_metadata, gsr_color_conversion *color_conversion) {
gsr_capture_xcomposite *self = cap->priv;
if(self->clear_background) {
@@ -232,11 +232,8 @@ static int gsr_capture_xcomposite_capture(gsr_capture *cap, gsr_capture_metadata
gsr_color_conversion_clear(color_conversion);
}
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->texture_size;
output_size = scale_keep_aspect_ratio(self->texture_size, output_size);
const vec2i target_pos = { max_int(0, capture_metdata->width / 2 - output_size.x / 2), max_int(0, capture_metdata->height / 2 - output_size.y / 2) };
const vec2i output_size = scale_keep_aspect_ratio(self->texture_size, (vec2i){capture_metadata->recording_width, capture_metadata->recording_height});
const vec2i target_pos = { max_int(0, capture_metadata->video_width / 2 - output_size.x / 2), max_int(0, capture_metadata->video_height / 2 - output_size.y / 2) };
//self->params.egl->glFlush();
//self->params.egl->glFinish();

View File

@@ -59,14 +59,14 @@ static int gsr_capture_ximage_start(gsr_capture *cap, gsr_capture_metadata *capt
if(self->params.output_resolution.x > 0 && self->params.output_resolution.y > 0) {
self->params.output_resolution = scale_keep_aspect_ratio(self->capture_size, self->params.output_resolution);
capture_metadata->width = self->params.output_resolution.x;
capture_metadata->height = self->params.output_resolution.y;
capture_metadata->video_width = self->params.output_resolution.x;
capture_metadata->video_height = self->params.output_resolution.y;
} else if(self->params.region_size.x > 0 && self->params.region_size.y > 0) {
capture_metadata->width = self->params.region_size.x;
capture_metadata->height = self->params.region_size.y;
capture_metadata->video_width = self->params.region_size.x;
capture_metadata->video_height = self->params.region_size.y;
} else {
capture_metadata->width = self->capture_size.x;
capture_metadata->height = self->capture_size.y;
capture_metadata->video_width = self->capture_size.x;
capture_metadata->video_height = self->capture_size.y;
}
self->texture_id = gl_create_texture(self->params.egl, self->capture_size.x, self->capture_size.y, GL_RGB8, GL_RGB, GL_LINEAR);
@@ -147,14 +147,11 @@ static bool gsr_capture_ximage_upload_to_texture(gsr_capture_ximage *self, int x
return success;
}
static int gsr_capture_ximage_capture(gsr_capture *cap, gsr_capture_metadata *capture_metdata, gsr_color_conversion *color_conversion) {
static int gsr_capture_ximage_capture(gsr_capture *cap, gsr_capture_metadata *capture_metadata, gsr_color_conversion *color_conversion) {
gsr_capture_ximage *self = cap->priv;
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_metdata->width / 2 - output_size.x / 2), max_int(0, capture_metdata->height / 2 - output_size.y / 2) };
const vec2i output_size = scale_keep_aspect_ratio(self->capture_size, (vec2i){capture_metadata->recording_width, capture_metadata->recording_height});
const vec2i target_pos = { max_int(0, capture_metadata->video_width / 2 - output_size.x / 2), max_int(0, capture_metadata->video_height / 2 - output_size.y / 2) };
gsr_capture_ximage_upload_to_texture(self, self->capture_pos.x + self->params.region_position.x, self->capture_pos.y + self->params.region_position.y, self->capture_size.x, self->capture_size.y);
gsr_color_conversion_draw(color_conversion, self->texture_id,

View File

@@ -245,8 +245,8 @@ static bool gsr_video_encoder_vaapi_start(gsr_video_encoder *encoder, AVCodecCon
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);
video_codec_context->width = FFALIGN(video_codec_context->width, 256);
video_codec_context->height = FFALIGN(video_codec_context->height, 256);
}
} 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

View File

@@ -13,7 +13,6 @@
/* TODO: Support hdr/10-bit */
bool gsr_image_writer_init_opengl(gsr_image_writer *self, gsr_egl *egl, int width, int height) {
memset(self, 0, sizeof(*self));
self->source = GSR_IMAGE_WRITER_SOURCE_OPENGL;
self->egl = egl;
self->width = width;
self->height = height;
@@ -25,15 +24,6 @@ bool gsr_image_writer_init_opengl(gsr_image_writer *self, gsr_egl *egl, int widt
return true;
}
bool gsr_image_writer_init_memory(gsr_image_writer *self, const void *memory, int width, int height) {
memset(self, 0, sizeof(*self));
self->source = GSR_IMAGE_WRITER_SOURCE_OPENGL;
self->width = width;
self->height = height;
self->memory = memory;
return true;
}
void gsr_image_writer_deinit(gsr_image_writer *self) {
if(self->texture) {
self->egl->glDeleteTextures(1, &self->texture);
@@ -64,7 +54,6 @@ static bool gsr_image_writer_write_memory_to_file(gsr_image_writer *self, const
}
static bool gsr_image_writer_write_opengl_texture_to_file(gsr_image_writer *self, const char *filepath, gsr_image_format image_format, int quality) {
assert(self->source == GSR_IMAGE_WRITER_SOURCE_OPENGL);
uint8_t *frame_data = malloc(self->width * self->height * 4);
if(!frame_data) {
fprintf(stderr, "gsr error: gsr_image_writer_write_to_file: failed to allocate memory for image frame\n");
@@ -90,11 +79,5 @@ static bool gsr_image_writer_write_opengl_texture_to_file(gsr_image_writer *self
}
bool gsr_image_writer_write_to_file(gsr_image_writer *self, const char *filepath, gsr_image_format image_format, int quality) {
switch(self->source) {
case GSR_IMAGE_WRITER_SOURCE_OPENGL:
return gsr_image_writer_write_opengl_texture_to_file(self, filepath, image_format, quality);
case GSR_IMAGE_WRITER_SOURCE_MEMORY:
return gsr_image_writer_write_memory_to_file(self, filepath, image_format, quality, self->memory);
}
return false;
return gsr_image_writer_write_opengl_texture_to_file(self, filepath, image_format, quality);
}

View File

@@ -122,12 +122,12 @@ static void get_monitor_by_position_callback(const gsr_monitor *monitor, void *u
std::swap(monitor_size.x, monitor_size.y);
}
if(!data->output_name && data->position.x >= monitor_position.x && data->position.x <= monitor_position.x + monitor->size.x
&& data->position.y >= monitor_position.y && data->position.y <= monitor_position.y + monitor->size.y)
if(!data->output_name && data->position.x >= monitor_position.x && data->position.x <= monitor_position.x + monitor_size.x
&& data->position.y >= monitor_position.y && data->position.y <= monitor_position.y + monitor_size.y)
{
data->output_name = strdup(monitor->name);
data->monitor_pos = monitor_position;
data->monitor_size = monitor->size;
data->monitor_size = monitor_size;
}
}
@@ -1268,6 +1268,11 @@ static RecordingStartAudio* get_recording_start_item_by_stream_index(RecordingSt
return nullptr;
}
struct AudioPtsOffset {
int64_t pts_offset = 0;
int stream_index = 0;
};
static void save_replay_async(AVCodecContext *video_codec_context, int video_stream_index, const std::vector<AudioTrack> &audio_tracks, gsr_replay_buffer *replay_buffer, std::string output_dir, const char *container_format, const std::string &file_extension, bool date_folders, bool hdr, gsr_capture *capture, int current_save_replay_seconds) {
if(save_replay_thread.valid())
return;
@@ -1279,14 +1284,15 @@ static void save_replay_async(AVCodecContext *video_codec_context, int video_str
return;
}
const gsr_replay_buffer_iterator audio_start_iterator = gsr_replay_buffer_find_keyframe(replay_buffer, video_start_iterator, video_stream_index, true);
// if(audio_start_index == (size_t)-1) {
// fprintf(stderr, "gsr error: failed to save replay: failed to find an audio keyframe. perhaps replay was saved too fast, before anything has been recorded\n");
// return;
// }
const int64_t video_pts_offset = gsr_replay_buffer_iterator_get_packet(replay_buffer, video_start_iterator)->pts;
const int64_t audio_pts_offset = audio_start_iterator.packet_index == (size_t)-1 ? 0 : gsr_replay_buffer_iterator_get_packet(replay_buffer, audio_start_iterator)->pts;
std::vector<AudioPtsOffset> audio_pts_offsets;
audio_pts_offsets.reserve(audio_tracks.size());
for(const AudioTrack &audio_track : audio_tracks) {
const gsr_replay_buffer_iterator audio_start_iterator = gsr_replay_buffer_find_keyframe(replay_buffer, video_start_iterator, audio_track.stream_index, false);
const int64_t audio_pts_offset = audio_start_iterator.packet_index == (size_t)-1 ? 0 : gsr_replay_buffer_iterator_get_packet(replay_buffer, audio_start_iterator)->pts;
audio_pts_offsets.push_back(AudioPtsOffset{audio_pts_offset, audio_track.stream_index});
}
gsr_replay_buffer *cloned_replay_buffer = gsr_replay_buffer_clone(replay_buffer);
if(!cloned_replay_buffer) {
@@ -1302,7 +1308,7 @@ static void save_replay_async(AVCodecContext *video_codec_context, int video_str
save_replay_output_filepath = std::move(output_filepath);
save_replay_thread = std::async(std::launch::async, [video_stream_index, recording_start_result, video_start_iterator, video_pts_offset, audio_pts_offset, video_codec_context, cloned_replay_buffer]() mutable {
save_replay_thread = std::async(std::launch::async, [video_stream_index, recording_start_result, video_start_iterator, video_pts_offset, audio_pts_offsets{std::move(audio_pts_offsets)}, video_codec_context, cloned_replay_buffer]() mutable {
gsr_replay_buffer_iterator replay_iterator = video_start_iterator;
for(;;) {
AVPacket *replay_packet = gsr_replay_buffer_iterator_get_packet(cloned_replay_buffer, replay_iterator);
@@ -1350,8 +1356,10 @@ static void save_replay_async(AVCodecContext *video_codec_context, int video_str
stream = recording_start_audio->stream;
codec_context = audio_track->codec_context;
av_packet.pts -= audio_pts_offset;
av_packet.dts -= audio_pts_offset;
const AudioPtsOffset &audio_pts_offset = audio_pts_offsets[av_packet.stream_index - 1];
assert(audio_pts_offset.stream_index == av_packet.stream_index);
av_packet.pts -= audio_pts_offset.pts_offset;
av_packet.dts -= audio_pts_offset.pts_offset;
}
//av_packet.stream_index = stream->index;
@@ -2302,7 +2310,7 @@ static int video_quality_to_image_quality_value(gsr_video_quality video_quality)
case GSR_VIDEO_QUALITY_HIGH:
return 85;
case GSR_VIDEO_QUALITY_VERY_HIGH:
return 90;
return 91; // Quality above 90 makes the jpeg image encoder (stb_image_writer) use yuv444 instead of yuv420, which greatly improves small colored text quality on dark background
case GSR_VIDEO_QUALITY_ULTRA:
return 97;
}
@@ -2318,8 +2326,10 @@ static void capture_image_to_file(args_parser &arg_parser, gsr_egl *egl, gsr_ima
gsr_capture *capture = create_capture_impl(arg_parser, egl, prefer_ximage);
gsr_capture_metadata capture_metadata;
capture_metadata.width = 0;
capture_metadata.height = 0;
capture_metadata.video_width = 0;
capture_metadata.video_height = 0;
capture_metadata.recording_width = 0;
capture_metadata.recording_height = 0;
capture_metadata.fps = fps;
int capture_result = gsr_capture_start(capture, &capture_metadata);
@@ -2328,8 +2338,11 @@ static void capture_image_to_file(args_parser &arg_parser, gsr_egl *egl, gsr_ima
_exit(capture_result);
}
capture_metadata.recording_width = capture_metadata.video_width;
capture_metadata.recording_height = capture_metadata.video_height;
gsr_image_writer image_writer;
if(!gsr_image_writer_init_opengl(&image_writer, egl, capture_metadata.width, capture_metadata.height)) {
if(!gsr_image_writer_init_opengl(&image_writer, egl, capture_metadata.video_width, capture_metadata.video_height)) {
fprintf(stderr, "gsr error: capture_image_to_file_wayland: gsr_image_write_gl_init failed\n");
_exit(1);
}
@@ -2341,7 +2354,7 @@ static void capture_image_to_file(args_parser &arg_parser, gsr_egl *egl, gsr_ima
color_conversion_params.load_external_image_shader = gsr_capture_uses_external_image(capture);
color_conversion_params.destination_textures[0] = image_writer.texture;
color_conversion_params.destination_textures_size[0] = { capture_metadata.width, capture_metadata.height };
color_conversion_params.destination_textures_size[0] = { capture_metadata.video_width, capture_metadata.video_height };
color_conversion_params.num_destination_textures = 1;
color_conversion_params.destination_color = GSR_DESTINATION_COLOR_RGB8;
@@ -2818,7 +2831,7 @@ static const AVCodec* pick_video_codec(gsr_egl *egl, args_parser *args_parser, b
/* Returns -1 if none is available */
static gsr_video_codec select_appropriate_video_codec_automatically(gsr_capture_metadata capture_metadata, const gsr_supported_video_codecs *supported_video_codecs) {
const vec2i capture_size = {capture_metadata.width, capture_metadata.height};
const vec2i capture_size = {capture_metadata.video_width, capture_metadata.video_height};
if(supported_video_codecs->h264.supported && codec_supports_resolution(supported_video_codecs->h264.max_resolution, capture_size)) {
fprintf(stderr, "gsr info: using h264 encoder because a codec was not specified\n");
return GSR_VIDEO_CODEC_H264;
@@ -2902,7 +2915,7 @@ static const AVCodec* select_video_codec_with_fallback(gsr_capture_metadata capt
const AVCodec *codec = pick_video_codec(egl, args_parser, is_flv, low_power, &supported_video_codecs);
const vec2i codec_max_resolution = codec_get_max_resolution(args_parser->video_codec, args_parser->video_encoder == GSR_VIDEO_ENCODER_HW_CPU, &supported_video_codecs);
const vec2i capture_size = {capture_metadata.width, capture_metadata.height};
const vec2i capture_size = {capture_metadata.video_width, capture_metadata.video_height};
if(!codec_supports_resolution(codec_max_resolution, capture_size)) {
const char *video_codec_name = video_codec_to_string(args_parser->video_codec);
fprintf(stderr, "gsr error: The max resolution for video codec %s is %dx%d while you are trying to capture at resolution %dx%d. Change capture resolution or video codec and try again\n",
@@ -3105,6 +3118,7 @@ int main(int argc, char **argv) {
// Linux nvidia driver 580.105.08 added the environment variable CUDA_DISABLE_PERF_BOOST to disable the p2 power level issue,
// where running cuda (which includes nvenc) causes the gpu to be forcefully set to p2 power level which on many nvidia gpus
// decreases gpu performance in games. On my GTX 1080 it decreased game performance by 10% for absolutely no reason.
// TODO: This only seems to allow the gpu to go to lower power level states, but not higher than p2.
setenv("CUDA_DISABLE_PERF_BOOST", "1", true);
// Stop nvidia driver from buffering frames
setenv("__GL_MaxFramesAllowed", "1", true);
@@ -3301,8 +3315,10 @@ int main(int argc, char **argv) {
gsr_capture *capture = create_capture_impl(arg_parser, &egl, false);
gsr_capture_metadata capture_metadata;
capture_metadata.width = 0;
capture_metadata.height = 0;
capture_metadata.video_width = 0;
capture_metadata.video_height = 0;
capture_metadata.recording_width = 0;
capture_metadata.recording_height = 0;
capture_metadata.fps = arg_parser.fps;
int capture_result = gsr_capture_start(capture, &capture_metadata);
@@ -3327,7 +3343,7 @@ int main(int argc, char **argv) {
const AVCodec *video_codec_f = select_video_codec_with_fallback(capture_metadata, &arg_parser, file_extension.c_str(), &egl, &low_power);
const enum AVPixelFormat video_pix_fmt = get_pixel_format(arg_parser.video_codec, egl.gpu_info.vendor, arg_parser.video_encoder == GSR_VIDEO_ENCODER_HW_CPU);
AVCodecContext *video_codec_context = create_video_codec_context(video_pix_fmt, video_codec_f, egl, arg_parser, capture_metadata.width, capture_metadata.height);
AVCodecContext *video_codec_context = create_video_codec_context(video_pix_fmt, video_codec_f, egl, arg_parser, capture_metadata.video_width, capture_metadata.video_height);
if(!is_replaying)
video_stream = create_stream(av_format_context, video_codec_context);
@@ -3337,8 +3353,8 @@ int main(int argc, char **argv) {
_exit(1);
}
video_frame->format = video_codec_context->pix_fmt;
video_frame->width = capture_metadata.width;
video_frame->height = capture_metadata.height;
video_frame->width = capture_metadata.video_width;
video_frame->height = capture_metadata.video_height;
video_frame->color_range = video_codec_context->color_range;
video_frame->color_primaries = video_codec_context->color_primaries;
video_frame->color_trc = video_codec_context->color_trc;
@@ -3363,9 +3379,12 @@ int main(int argc, char **argv) {
_exit(1);
}
capture_metadata.recording_width = capture_metadata.video_width;
capture_metadata.recording_height = capture_metadata.video_height;
// TODO: What if this updated resolution is above max resolution?
capture_metadata.width = video_codec_context->width;
capture_metadata.height = video_codec_context->height;
capture_metadata.video_width = video_codec_context->width;
capture_metadata.video_height = video_codec_context->height;
const Arg *plugin_arg = args_parser_get_arg(&arg_parser, "-p");
assert(plugin_arg);
@@ -3378,8 +3397,8 @@ int main(int argc, char **argv) {
assert(color_depth == GSR_COLOR_DEPTH_8_BITS || color_depth == GSR_COLOR_DEPTH_10_BITS);
const gsr_plugin_init_params plugin_init_params = {
(unsigned int)capture_metadata.width,
(unsigned int)capture_metadata.height,
(unsigned int)capture_metadata.video_width,
(unsigned int)capture_metadata.video_height,
(unsigned int)arg_parser.fps,
color_depth == GSR_COLOR_DEPTH_8_BITS ? GSR_PLUGIN_COLOR_DEPTH_8_BITS : GSR_PLUGIN_COLOR_DEPTH_10_BITS,
egl.context_type == GSR_GL_CONTEXT_TYPE_GLX ? GSR_PLUGIN_GRAPHICS_API_GLX : GSR_PLUGIN_GRAPHICS_API_EGL_ES,
@@ -3819,9 +3838,9 @@ int main(int argc, char **argv) {
if(plugins.num_plugins > 0) {
gsr_plugins_draw(&plugins);
gsr_color_conversion_draw(&color_conversion, plugins.texture,
{0, 0}, {capture_metadata.width, capture_metadata.height},
{0, 0}, {capture_metadata.width, capture_metadata.height},
{capture_metadata.width, capture_metadata.height}, GSR_ROT_0, GSR_SOURCE_COLOR_RGB, false);
{0, 0}, {capture_metadata.video_width, capture_metadata.video_height},
{0, 0}, {capture_metadata.video_width, capture_metadata.video_height},
{capture_metadata.video_width, capture_metadata.video_height}, GSR_ROT_0, GSR_SOURCE_COLOR_RGB, false);
}
if(capture_has_synchronous_task) {