Compare commits

...

24 Commits
4.2.2 ... 4.2.6

Author SHA1 Message Date
dec05eba
74d30f16ad Set buffer size 2024-11-05 17:49:49 +01:00
dec05eba
030947e33a Default replay service to cbr for better file size (less ram usage) and 60 seconds 2024-11-05 10:25:48 +01:00
dec05eba
35390796cd usage 2024-11-05 09:31:32 +01:00
dec05eba
70b9e3d6ff Revert bitrate calculation 2024-11-05 09:23:39 +01:00
dec05eba
953953eac7 m 2024-11-05 01:45:15 +01:00
dec05eba
10fe4f6c51 4.2.5 2024-11-05 01:09:25 +01:00
dec05eba
aac9b9cde7 Fix incorrect bitrate calculation for constant bitrate 2024-11-05 01:08:56 +01:00
dec05eba
258f690a89 Update issues link 2024-11-01 09:18:27 +01:00
dec05eba
ea139ab9bb Mention opensuse 2024-10-28 23:05:56 +01:00
dec05eba
05c74e5a77 Mention the new UI 2024-10-27 23:11:11 +01:00
dec05eba
07ce374201 Fix cursor position when scaling output with multiple monitors on monitor capture on x11 2024-10-26 23:55:41 +02:00
dec05eba
112640282d Add option to change output resolution (-s) 2024-10-26 20:58:00 +02:00
dec05eba
5ffa725367 Add unofficial sources list 2024-10-24 23:31:33 +02:00
dec05eba
a03463082f Revert "Debug output temp"
This reverts commit 86c57c85ac.
2024-10-24 15:26:47 +02:00
dec05eba
86c57c85ac Debug output temp 2024-10-24 15:11:47 +02:00
dec05eba
1aae305a36 Revert "Temp debug output"
This reverts commit 919c87768e.
2024-10-24 15:08:43 +02:00
dec05eba
919c87768e Temp debug output 2024-10-24 14:55:39 +02:00
dec05eba
ff77c64309 Mesa && AMD check 2024-10-23 22:01:03 +02:00
dec05eba
293ec193ba Support old amd gpus that need radeon driver 2024-10-23 21:26:05 +02:00
dec05eba
422f214283 4.2.4 2024-10-22 18:47:23 +02:00
dec05eba
768f1bd61e Fix shadow variable 2024-10-22 18:45:18 +02:00
dec05eba
7428f9ffee Allow recording virtual audio devices that have forward slash (/) in the name 2024-10-22 18:39:01 +02:00
dec05eba
10165977f6 4.2.3 2024-10-19 00:48:53 +02:00
dec05eba
62509a4b60 nixos fix: look for gsr-kms-server in PATH if not found in same directory as gpu-screen-recorder 2024-10-19 00:48:04 +02:00
24 changed files with 333 additions and 157 deletions

View File

@@ -7,6 +7,8 @@ similar to shadowplay on windows. This is the fastest screen recording tool for
This screen recorder can be used for recording your desktop offline, for live streaming and for nvidia shadowplay-like instant replay,
where only the last few minutes are saved.
This is a cli-only tool, if you want an UI for this check out [GPU Screen Recorder GTK](https://git.dec05eba.com/gpu-screen-recorder-gtk/) or if you prefer a ShadowPlay-like UI then check out [GPU Screen Recorder UI](https://git.dec05eba.com/gpu-screen-recorder-ui/).
Supported video codecs:
* H264 (default)
* HEVC (Optionally with HDR)
@@ -46,14 +48,22 @@ This should work fine on AMD/Intel X11 or Wayland. On Nvidia X11 G-SYNC only wor
For example it can cause your computer to freeze when recording certain games.
# 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-git (`yay -S gpu-screen-recorder-git`).\
If you are running an Arch Linux based distro then you can find gpu screen recorder on aur under the name gpu-screen-recorder-git (`yay -S gpu-screen-recorder-git`).\
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.\
The only official ways to install GPU Screen Recorder is either from source, AUR or flathub. 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 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`).
## 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.\
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:
* Debian/Ubuntu: [Pacstall](https://pacstall.dev/packages/gpu-screen-recorder)
* Nix: [NixOS wiki](https://wiki.nixos.org/wiki/Gpu-screen-recorder)
* openSUSE: [openSUSE software repository](https://software.opensuse.org/package/gpu-screen-recorder)
* Gentoo: [Guru](https://github.com/gentoo/guru/blob/master/media-video/gpu-screen-recorder/gpu-screen-recorder-9999.ebuild)
# Dependencies
GPU Screen Recorder uses meson build system so you need to install `meson` to build GPU Screen Recorder.
@@ -94,7 +104,9 @@ When compiling GPU Screen Recorder with portal support (`-Dportal=true`, which i
* libpipewire (and libspa which is usually part of libpipewire)
# How to use
Run `gpu-screen-recorder --help` to see all options and also examples.
Run `gpu-screen-recorder --help` to see all options and also examples.\
There is also a gui for the gpu screen recorder called [GPU Screen Recorder GTK](https://git.dec05eba.com/gpu-screen-recorder-gtk/).\
There is also a new alternative UI for GPU Screen Recorder in the style of ShadowPlay called [GPU Screen Recorder UI](https://git.dec05eba.com/gpu-screen-recorder-ui/).
## Recording
Here is an example of how to record your monitor and the default audio output: `gpu-screen-recorder -w screen -f 60 -a default_output -o ~/Videos/test_video.mp4`.
Yyou can stop and save the recording with `Ctrl+C` or by running `killall -SIGINT gpu-screen-recorder`.
@@ -112,15 +124,14 @@ The replay buffer is stored in ram (as encoded video), so don't use a too large
To save a video in replay mode, you need to send signal SIGUSR1 to gpu screen recorder. You can do this by running `killall -SIGUSR1 gpu-screen-recorder`.\
To stop recording send SIGINT to gpu screen recorder. You can do this by running `killall -SIGINT gpu-screen-recorder` or pressing `Ctrl-C` in the terminal that runs gpu screen recorder. When recording a regular non-replay video this will also save the video.\
To pause/unpause recording send SIGUSR2 to gpu screen recorder. You can do this by running `killall -SIGUSR2 gpu-screen-recorder`. This is only applicable and useful when recording (not streaming nor replay).\
## Finding audio device name
You can find the default output audio device (headset, speakers (in other words, desktop audio)) with the command `pactl get-default-sink`. Add `monitor` to the end of that to use that as an audio input in gpu screen recorder.\
You can find the default input audio device (microphone) with the command `pactl get-default-source`. This input should not have `monitor` added to the end when used in gpu screen recorder.\
Example of recording both desktop audio and microphone: `gpu-screen-recorder -w screen -f 60 -a "$(pactl get-default-sink).monitor" -a "$(pactl get-default-source)" -o ~/Videos/test_video.mp4`.\
A name (that is visible to pipewire) can be given to an audio input device by prefixing the audio input with `<name>/`, for example `dummy/$(pactl get-default-sink).monitor`.\
Note that if you use multiple audio inputs then they are each recorded into separate audio tracks in the video file. If you want to merge multiple audio inputs into one audio track then separate the audio inputs by "|" in one -a argument,
for example `-a "$(pactl get-default-sink).monitor|$(pactl get-default-source)"`.
## Audio device name
To record the default output device (desktop audio) you can use the `default_output` option, for example `-a default_output`.\
To record the default input device (microphone) you can use the `default_input` option, for example `-a default_input`.\
To list all available audio devices run `gpu-screen-recorder --list-audio-devices`. The name to use with GPU Screen Recorder will be on the left side and the human readable name is on the right side.\
To record multiple audio devices to multiple audio tracks specify the `-a` option multiple times, for example `-a default_output -a default_input`.\
To record multiple audio devices into one audio track (merged) specify the `-a` option once split with `|` for each audio device, for example `-a "default_output|default_input"`.\
In wireplumber the name of the audio will be in the format `gsr-<audio_device>`, but you can change that name by prefixing the audio device with a name and then a forward slash, for example: `-a "name/default_output"`.
There is also a gui for the gpu screen recorder called [gpu-screen-recorder-gtk](https://git.dec05eba.com/gpu-screen-recorder-gtk/).
## Simple way to run replay without gui
Run the script `scripts/start-replay.sh` to start replay and then `scripts/save-replay.sh` to save a replay and `scripts/stop-replay.sh` to stop the replay. The videos are saved to `$HOME/Videos`.
You can use these scripts to start replay at system startup if you add `scripts/start-replay.sh` to startup (this can be done differently depending on your desktop environment / window manager) and then go into
@@ -138,7 +149,7 @@ You have to reboot your computer after installing GPU Screen Recorder for the fi
Look at the [scripts](https://git.dec05eba.com/gpu-screen-recorder/tree/scripts) directory for script examples. For example if you want to automatically save a recording/replay into a folder with the same name as the game you are recording.
# Reporting bugs
Issues are reported on this Github page: [https://github.com/dec05eba/gpu-screen-recorder-issues/issues](https://github.com/dec05eba/gpu-screen-recorder-issues/issues).
Issues are reported on this Github page: [https://github.com/dec05eba/gpu-screen-recorder-issues](https://github.com/dec05eba/gpu-screen-recorder-issues).
# Contributing patches
See [https://git.dec05eba.com/?p=about](https://git.dec05eba.com/?p=about) for contribution steps.
# Donations

14
TODO
View File

@@ -74,8 +74,6 @@ Make it possible to select which /dev/dri/card* to use, but that requires opengl
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.-
Mesa doesn't support global headers (AV_CODEC_FLAG_GLOBAL_HEADER) with h264... which also breaks mkv since mkv requires global header. Right now gpu screen recorder will forcefully set video codec to hevc when h264 is requested for mkv files.
Drop frames if live streaming cant keep up with target fps, or dynamically change resolution/quality.
Support low power option.
@@ -103,7 +101,7 @@ Investigate if there is a way to do gpu->gpu copy directly without touching syst
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.
Implement scaling and use lanczos resampling for better quality. Lanczos resampling can also be used for YUV chroma for better color quality on small text.
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.
@@ -178,4 +176,12 @@ Support ROI (AV_FRAME_DATA_REGIONS_OF_INTEREST).
Default to hevc if capture size is larger than 4096 in width or height.
Set low latency mode on vulkan encoding.
Set low latency mode on vulkan encoding.
Support pipewire audio capture which also allows capturing audio from a single application. This can also be done with pulseaudio by creating a virtual sink:
pactl load-module module-combine-sink sink_name=gsr2 slaves=$(pactl get-default-sink) sink_properties=device.description="gsr"
pactl move-sink-input 2944 gsr2 # 2944 comes from 'pactl list sink-inputs'
and then record gsr2.monitor.
Or use pa_stream_set_monitor_stream, which also takes the sink-input as input. However need to track when the sink disconnects to mute and then reconnect again.
Support recording/replay/livestreaming at the same time by allowing commands to be run on an existing gpu screen recorder instance.

View File

@@ -1,6 +1,5 @@
WINDOW=screen
CONTAINER=mp4
QUALITY=very_high
CODEC=h264
AUDIO_CODEC=opus
AUDIO_DEVICE=default_output

View File

@@ -5,22 +5,23 @@ Description=GPU Screen Recorder Service
EnvironmentFile=-%h/.config/gpu-screen-recorder.env
Environment=WINDOW=screen
Environment=CONTAINER=mp4
Environment=QUALITY=very_high
Environment=BITRATE_MODE=qp
Environment=QUALITY=50000
Environment=BITRATE_MODE=cbr
Environment=CODEC=auto
Environment=AUDIO_CODEC=opus
Environment=AUDIO_DEVICE=default_output
Environment=SECONDARY_AUDIO_DEVICE=
Environment=FRAMERATE=60
Environment=REPLAYDURATION=30
Environment=REPLAYDURATION=60
Environment=OUTPUTDIR=%h/Videos
Environment=MAKEFOLDERS=no
Environment=COLOR_RANGE=limited
Environment=KEYINT=2
Environment=ENCODER=gpu
Environment=RESTORE_PORTAL_SESSION=yes
Environment=OUTPUT_RESOLUTION=0x0
Environment=ADDITIONAL_ARGS=
ExecStart=gpu-screen-recorder -v no -w "${WINDOW}" -c "${CONTAINER}" -q "${QUALITY}" -k "${CODEC}" -ac "${AUDIO_CODEC}" -a "${AUDIO_DEVICE}" -a "${SECONDARY_AUDIO_DEVICE}" -f "${FRAMERATE}" -r "${REPLAYDURATION}" -o "${OUTPUTDIR}" -df "${MAKEFOLDERS}" $ADDITIONAL_ARGS -cr "${COLOR_RANGE}" -keyint "${KEYINT}" -restore-portal-session "${RESTORE_PORTAL_SESSION}" -encoder "${ENCODER}" -bm "${BITRATE_MODE}"
ExecStart=gpu-screen-recorder -v no -w "${WINDOW}" -s "${OUTPUT_RESOLUTION}" -c "${CONTAINER}" -q "${QUALITY}" -k "${CODEC}" -ac "${AUDIO_CODEC}" -a "${AUDIO_DEVICE}" -a "${SECONDARY_AUDIO_DEVICE}" -f "${FRAMERATE}" -r "${REPLAYDURATION}" -o "${OUTPUTDIR}" -df "${MAKEFOLDERS}" $ADDITIONAL_ARGS -cr "${COLOR_RANGE}" -keyint "${KEYINT}" -restore-portal-session "${RESTORE_PORTAL_SESSION}" -encoder "${ENCODER}" -bm "${BITRATE_MODE}"
KillSignal=SIGINT
Restart=on-failure
RestartSec=5s

View File

@@ -11,6 +11,7 @@ typedef struct {
bool hdr;
bool record_cursor;
int fps;
vec2i output_resolution;
} gsr_capture_kms_params;
gsr_capture* gsr_capture_kms_create(const gsr_capture_kms_params *params);

View File

@@ -15,6 +15,7 @@ typedef struct {
gsr_color_range color_range;
bool record_cursor;
bool use_software_video_encoder;
vec2i output_resolution;
} gsr_capture_nvfbc_params;
gsr_capture* gsr_capture_nvfbc_create(const gsr_capture_nvfbc_params *params);

View File

@@ -11,6 +11,7 @@ typedef struct {
bool restore_portal_session;
/* If this is set to NULL then this defaults to $XDG_CONFIG_HOME/gpu-screen-recorder/restore_token ($XDG_CONFIG_HOME defaults to $HOME/.config) */
const char *portal_session_token_filepath;
vec2i output_resolution;
} gsr_capture_portal_params;
gsr_capture* gsr_capture_portal_create(const gsr_capture_portal_params *params);

View File

@@ -8,10 +8,10 @@ typedef struct {
gsr_egl *egl;
unsigned long window;
bool follow_focused; /* If this is set then |window| is ignored */
vec2i region_size; /* This is currently only used with |follow_focused| */
gsr_color_range color_range;
bool record_cursor;
gsr_color_depth color_depth;
vec2i output_resolution;
} gsr_capture_xcomposite_params;
gsr_capture* gsr_capture_xcomposite_create(const gsr_capture_xcomposite_params *params);

View File

@@ -51,4 +51,6 @@ bool video_codec_context_is_vaapi(AVCodecContext *video_codec_context);
bool vaapi_copy_drm_planes_to_video_surface(AVCodecContext *video_codec_context, AVFrame *video_frame, vec2i source_pos, vec2i source_size, vec2i dest_pos, vec2i dest_size, uint32_t format, vec2i size, const int *fds, const uint32_t *offsets, const uint32_t *pitches, const uint64_t *modifiers, int num_planes);
bool vaapi_copy_egl_image_to_video_surface(gsr_egl *egl, EGLImage image, vec2i source_pos, vec2i source_size, vec2i dest_pos, vec2i dest_size, AVCodecContext *video_codec_context, AVFrame *video_frame);
vec2i scale_keep_aspect_ratio(vec2i from, vec2i to);
#endif /* GSR_UTILS_H */

View File

@@ -9,4 +9,8 @@ typedef struct {
float x, y;
} vec2f;
typedef struct {
double x, y;
} vec2d;
#endif /* VEC2_H */

View File

@@ -183,6 +183,40 @@ static void file_get_directory(char *filepath) {
*end = '\0';
}
static bool find_program_in_path(const char *program_name, char *filepath, int filepath_len) {
const char *path = getenv("PATH");
if(!path)
return false;
int program_name_len = strlen(program_name);
const char *end = path + strlen(path);
while(path != end) {
const char *part_end = strchr(path, ':');
const char *next = part_end;
if(part_end) {
next = part_end + 1;
} else {
part_end = end;
next = end;
}
int len = part_end - path;
if(len + 1 + program_name_len < filepath_len) {
memcpy(filepath, path, len);
filepath[len] = '/';
memcpy(filepath + len + 1, program_name, program_name_len);
filepath[len + 1 + program_name_len] = '\0';
if(access(filepath, F_OK) == 0)
return true;
}
path = next;
}
return false;
}
int gsr_kms_client_init(gsr_kms_client *self, const char *card_path) {
int result = -1;
self->kms_server_pid = -1;
@@ -212,8 +246,11 @@ int gsr_kms_client_init(gsr_kms_client *self, const char *card_path) {
}
if(access(server_filepath, F_OK) != 0) {
fprintf(stderr, "gsr error: gsr_kms_client_init: gsr-kms-server is not installed (%s not found)\n", server_filepath);
return -1;
fprintf(stderr, "gsr info: gsr_kms_client_init: gsr-kms-server is not installed in the same directory as gpu-screen-recorder (%s not found), looking for gsr-kms-server in PATH instead\n", server_filepath);
if(!find_program_in_path("gsr-kms-server", server_filepath, sizeof(server_filepath)) || access(server_filepath, F_OK) != 0) {
fprintf(stderr, "gsr error: gsr_kms_client_init: gsr-kms-server was not found in PATH. Please install gpu-screen-recorder properly\n");
return -1;
}
}
fprintf(stderr, "gsr info: gsr_kms_client_init: setting up connection to %s\n", server_filepath);

View File

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

View File

@@ -1,6 +1,6 @@
#!/bin/sh
window=$(xdotool selectwindow)
window_name=$(xdotool getwindowclassname "$window" || xdotool getwindowname "$window" || echo "Game")
window_name=$(xdotool getwindowname "$window" || xdotool getwindowclassname "$window" || echo "Game")
window_name="$(echo "$window_name" | tr '/\\' '_')"
gpu-screen-recorder -w "$window" -f 60 -a default_output -o "$HOME/Videos/recording/$window_name/$(date +"Video_%Y-%m-%d_%H-%M-%S.mp4")"

View File

@@ -4,7 +4,7 @@
# gpu-screen-recorder -w screen -f 60 -a default_output -r 60 -sc scripts/record-save-application-name.sh -c mp4 -o "$HOME/Videos"
window=$(xdotool getwindowfocus)
window_name=$(xdotool getwindowclassname "$window" || xdotool getwindowname "$window" || echo "Game")
window_name=$(xdotool getwindowname "$window" || xdotool getwindowclassname "$window" || echo "Game")
window_name="$(echo "$window_name" | tr '/\\' '_')"
video_dir="$HOME/Videos/Replays/$window_name"

View File

@@ -1,6 +1,6 @@
#!/bin/sh
window=$(xdotool selectwindow)
window_name=$(xdotool getwindowclassname "$window" || xdotool getwindowname "$window" || echo "Game")
window_name=$(xdotool getwindowname "$window" || xdotool getwindowclassname "$window" || echo "Game")
window_name="$(echo "$window_name" | tr '/\\' '_')"
gpu-screen-recorder -w "$window" -f 60 -c mkv -a default_output -r 60 -o "$HOME/Videos/Replays/$window_name"

View File

@@ -214,8 +214,15 @@ static int gsr_capture_kms_start(gsr_capture *cap, AVCodecContext *video_codec_c
/* Disable vsync */
self->params.egl->eglSwapInterval(self->params.egl->egl_display, 0);
video_codec_context->width = FFALIGN(self->capture_size.x, 2);
video_codec_context->height = FFALIGN(self->capture_size.y, 2);
if(self->params.output_resolution.x == 0 && self->params.output_resolution.y == 0) {
self->params.output_resolution = self->capture_size;
video_codec_context->width = FFALIGN(self->capture_size.x, 2);
video_codec_context->height = FFALIGN(self->capture_size.y, 2);
} else {
self->params.output_resolution = scale_keep_aspect_ratio(self->capture_size, self->params.output_resolution);
video_codec_context->width = FFALIGN(self->params.output_resolution.x, 2);
video_codec_context->height = FFALIGN(self->params.output_resolution.y, 2);
}
frame->width = video_codec_context->width;
frame->height = video_codec_context->height;
@@ -429,7 +436,12 @@ static gsr_kms_response_item* find_cursor_drm_if_on_monitor(gsr_capture_kms *sel
return cursor_drm_fd;
}
static void render_drm_cursor(gsr_capture_kms *self, gsr_color_conversion *color_conversion, const gsr_kms_response_item *cursor_drm_fd, vec2i target_pos, float texture_rotation) {
static void render_drm_cursor(gsr_capture_kms *self, gsr_color_conversion *color_conversion, const gsr_kms_response_item *cursor_drm_fd, vec2i target_pos, float texture_rotation, vec2i output_size) {
const vec2d scale = {
self->capture_size.x == 0 ? 0 : (double)output_size.x / (double)self->capture_size.x,
self->capture_size.y == 0 ? 0 : (double)output_size.y / (double)self->capture_size.y
};
const bool cursor_texture_id_is_external = self->params.egl->gpu_info.vendor == GSR_GPU_VENDOR_NVIDIA;
const vec2i cursor_size = {cursor_drm_fd->width, cursor_drm_fd->height};
@@ -458,6 +470,9 @@ static void render_drm_cursor(gsr_capture_kms *self, gsr_color_conversion *color
break;
}
cursor_pos.x *= scale.x;
cursor_pos.y *= scale.y;
cursor_pos.x += target_pos.x;
cursor_pos.y += target_pos.y;
@@ -487,32 +502,37 @@ static void render_drm_cursor(gsr_capture_kms *self, gsr_color_conversion *color
self->params.egl->eglDestroyImage(self->params.egl->egl_display, cursor_image);
self->params.egl->glEnable(GL_SCISSOR_TEST);
self->params.egl->glScissor(target_pos.x, target_pos.y, self->capture_size.x, self->capture_size.y);
self->params.egl->glScissor(target_pos.x, target_pos.y, output_size.x, output_size.y);
gsr_color_conversion_draw(color_conversion, self->cursor_texture_id,
cursor_pos, cursor_size,
cursor_pos, (vec2i){cursor_size.x * scale.x, cursor_size.y * scale.y},
(vec2i){0, 0}, cursor_size,
texture_rotation, cursor_texture_id_is_external);
self->params.egl->glDisable(GL_SCISSOR_TEST);
}
static void render_x11_cursor(gsr_capture_kms *self, gsr_color_conversion *color_conversion, vec2i capture_pos, vec2i target_pos) {
static void render_x11_cursor(gsr_capture_kms *self, gsr_color_conversion *color_conversion, vec2i capture_pos, vec2i target_pos, vec2i output_size) {
if(!self->x11_cursor.visible)
return;
const vec2d scale = {
self->capture_size.x == 0 ? 0 : (double)output_size.x / (double)self->capture_size.x,
self->capture_size.y == 0 ? 0 : (double)output_size.y / (double)self->capture_size.y
};
gsr_cursor_tick(&self->x11_cursor, DefaultRootWindow(self->params.egl->x11.dpy));
const vec2i cursor_pos = {
target_pos.x + self->x11_cursor.position.x - self->x11_cursor.hotspot.x - capture_pos.x,
target_pos.y + self->x11_cursor.position.y - self->x11_cursor.hotspot.y - capture_pos.y
target_pos.x + (self->x11_cursor.position.x - self->x11_cursor.hotspot.x - capture_pos.x) * scale.x,
target_pos.y + (self->x11_cursor.position.y - self->x11_cursor.hotspot.y - capture_pos.y) * scale.y
};
self->params.egl->glEnable(GL_SCISSOR_TEST);
self->params.egl->glScissor(target_pos.x, target_pos.y, self->capture_size.x, self->capture_size.y);
self->params.egl->glScissor(target_pos.x, target_pos.y, output_size.x, output_size.y);
gsr_color_conversion_draw(color_conversion, self->x11_cursor.texture_id,
cursor_pos, self->x11_cursor.size,
cursor_pos, (vec2i){self->x11_cursor.size.x * scale.x, self->x11_cursor.size.y * scale.y},
(vec2i){0, 0}, self->x11_cursor.size,
0.0f, false);
@@ -562,8 +582,12 @@ static int gsr_capture_kms_capture(gsr_capture *cap, AVFrame *frame, gsr_color_c
" If you are experience performance problems in the video then record a single window on X11 or use portal capture option instead\n");
}
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 float texture_rotation = monitor_rotation_to_radians(self->monitor_rotation);
const vec2i target_pos = { max_int(0, frame->width / 2 - self->capture_size.x / 2), max_int(0, frame->height / 2 - self->capture_size.y / 2) };
const vec2i target_pos = { max_int(0, frame->width / 2 - output_size.x / 2), max_int(0, frame->height / 2 - output_size.y / 2) };
self->capture_size = rotate_capture_size_if_rotated(self, (vec2i){ drm_fd->src_w, drm_fd->src_h });
gsr_capture_kms_update_capture_size_change(self, color_conversion, target_pos, drm_fd);
@@ -586,7 +610,7 @@ static int gsr_capture_kms_capture(gsr_capture *cap, AVFrame *frame, gsr_color_c
pitches[i] = drm_fd->dma_buf[i].pitch;
modifiers[i] = drm_fd->modifier;
}
if(!vaapi_copy_drm_planes_to_video_surface(self->video_codec_context, frame, (vec2i){capture_pos.x, capture_pos.y}, self->capture_size, target_pos, self->capture_size, drm_fd->pixel_format, (vec2i){drm_fd->width, drm_fd->height}, fds, offsets, pitches, modifiers, drm_fd->num_dma_bufs)) {
if(!vaapi_copy_drm_planes_to_video_surface(self->video_codec_context, frame, (vec2i){capture_pos.x, capture_pos.y}, self->capture_size, target_pos, output_size, drm_fd->pixel_format, (vec2i){drm_fd->width, drm_fd->height}, fds, offsets, pitches, modifiers, drm_fd->num_dma_bufs)) {
fprintf(stderr, "gsr error: gsr_capture_kms_capture: vaapi_copy_drm_planes_to_video_surface failed, falling back to opengl copy. Please report this as an issue at https://github.com/dec05eba/gpu-screen-recorder-issues\n");
self->fast_path_failed = true;
}
@@ -602,7 +626,7 @@ static int gsr_capture_kms_capture(gsr_capture *cap, AVFrame *frame, gsr_color_c
}
gsr_color_conversion_draw(color_conversion, self->external_texture_fallback ? self->external_input_texture_id : self->input_texture_id,
target_pos, self->capture_size,
target_pos, output_size,
capture_pos, self->capture_size,
texture_rotation, self->external_texture_fallback);
}
@@ -613,9 +637,9 @@ static int gsr_capture_kms_capture(gsr_capture *cap, AVFrame *frame, gsr_color_c
// the cursor plane is not available when the cursor is on the monitor controlled by the nvidia device.
if(self->is_x11) {
const vec2i cursor_monitor_offset = self->capture_pos;
render_x11_cursor(self, color_conversion, cursor_monitor_offset, target_pos);
render_x11_cursor(self, color_conversion, cursor_monitor_offset, target_pos, output_size);
} else if(cursor_drm_fd) {
render_drm_cursor(self, color_conversion, cursor_drm_fd, target_pos, texture_rotation);
render_drm_cursor(self, color_conversion, cursor_drm_fd, target_pos, texture_rotation, output_size);
}
}

View File

@@ -240,6 +240,11 @@ static int gsr_capture_nvfbc_setup_handle(gsr_capture_nvfbc *self) {
}
}
if(!self->capture_region) {
self->width = self->tracking_width;
self->height = self->tracking_height;
}
return 0;
error_cleanup:
@@ -351,6 +356,14 @@ static int gsr_capture_nvfbc_start(gsr_capture *cap, AVCodecContext *video_codec
video_codec_context->height = FFALIGN(self->tracking_height, 2);
}
if(self->params.output_resolution.x == 0 && self->params.output_resolution.y == 0) {
self->params.output_resolution = (vec2i){video_codec_context->width, video_codec_context->height};
} else {
self->params.output_resolution = scale_keep_aspect_ratio((vec2i){video_codec_context->width, video_codec_context->height}, self->params.output_resolution);
video_codec_context->width = FFALIGN(self->params.output_resolution.x, 2);
video_codec_context->height = FFALIGN(self->params.output_resolution.y, 2);
}
frame->width = video_codec_context->width;
frame->height = video_codec_context->height;
@@ -390,6 +403,13 @@ static int gsr_capture_nvfbc_capture(gsr_capture *cap, AVFrame *frame, gsr_color
}
}
const vec2i frame_size = (vec2i){self->width, self->height};
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, frame->width / 2 - output_size.x / 2), max_int(0, frame->height / 2 - output_size.y / 2) };
NVFBC_FRAME_GRAB_INFO frame_info;
memset(&frame_info, 0, sizeof(frame_info));
@@ -412,8 +432,8 @@ static int gsr_capture_nvfbc_capture(gsr_capture *cap, AVFrame *frame, gsr_color
self->params.egl->glFinish();
gsr_color_conversion_draw(color_conversion, self->setup_params.dwTextures[grab_params.dwTextureIndex],
(vec2i){0, 0}, (vec2i){frame->width, frame->height},
(vec2i){0, 0}, (vec2i){frame->width, frame->height},
target_pos, (vec2i){output_size.x, output_size.y},
(vec2i){0, 0}, frame_size,
0.0f, false);
self->params.egl->glFlush();

View File

@@ -300,8 +300,15 @@ static int gsr_capture_portal_start(gsr_capture *cap, AVCodecContext *video_code
/* Disable vsync */
self->params.egl->eglSwapInterval(self->params.egl->egl_display, 0);
video_codec_context->width = FFALIGN(self->capture_size.x, 2);
video_codec_context->height = FFALIGN(self->capture_size.y, 2);
if(self->params.output_resolution.x == 0 && self->params.output_resolution.y == 0) {
self->params.output_resolution = self->capture_size;
video_codec_context->width = FFALIGN(self->capture_size.x, 2);
video_codec_context->height = FFALIGN(self->capture_size.y, 2);
} else {
self->params.output_resolution = scale_keep_aspect_ratio(self->capture_size, self->params.output_resolution);
video_codec_context->width = FFALIGN(self->params.output_resolution.x, 2);
video_codec_context->height = FFALIGN(self->params.output_resolution.y, 2);
}
frame->width = video_codec_context->width;
frame->height = video_codec_context->height;
@@ -334,8 +341,12 @@ static int gsr_capture_portal_capture(gsr_capture *cap, AVFrame *frame, gsr_colo
} else {
return 0;
}
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, frame->width / 2 - self->capture_size.x / 2), max_int(0, frame->height / 2 - self->capture_size.y / 2) };
const vec2i target_pos = { max_int(0, frame->width / 2 - output_size.x / 2), max_int(0, frame->height / 2 - output_size.y / 2) };
self->params.egl->glFlush();
self->params.egl->glFinish();
@@ -354,7 +365,7 @@ static int gsr_capture_portal_capture(gsr_capture *cap, AVFrame *frame, gsr_colo
pitches[i] = self->dmabuf_data[i].stride;
modifiers[i] = pipewire_modifiers;
}
if(!vaapi_copy_drm_planes_to_video_surface(self->video_codec_context, frame, (vec2i){region.x, region.y}, self->capture_size, target_pos, self->capture_size, pipewire_fourcc, self->capture_size, fds, offsets, pitches, modifiers, self->num_dmabuf_data)) {
if(!vaapi_copy_drm_planes_to_video_surface(self->video_codec_context, frame, (vec2i){region.x, region.y}, self->capture_size, target_pos, output_size, pipewire_fourcc, self->capture_size, fds, offsets, pitches, modifiers, self->num_dmabuf_data)) {
fprintf(stderr, "gsr error: gsr_capture_portal_capture: vaapi_copy_drm_planes_to_video_surface failed, falling back to opengl copy. Please report this as an issue at https://github.com/dec05eba/gpu-screen-recorder-issues\n");
self->fast_path_failed = true;
}
@@ -364,21 +375,26 @@ static int gsr_capture_portal_capture(gsr_capture *cap, AVFrame *frame, gsr_colo
if(self->fast_path_failed) {
gsr_color_conversion_draw(color_conversion, using_external_image ? self->texture_map.external_texture_id : self->texture_map.texture_id,
target_pos, self->capture_size,
target_pos, output_size,
(vec2i){region.x, region.y}, self->capture_size,
0.0f, using_external_image);
}
if(self->params.record_cursor) {
if(self->params.record_cursor && self->texture_map.cursor_texture_id > 0 && cursor_region.width > 0) {
const vec2d scale = {
self->capture_size.x == 0 ? 0 : (double)output_size.x / (double)self->capture_size.x,
self->capture_size.y == 0 ? 0 : (double)output_size.y / (double)self->capture_size.y
};
const vec2i cursor_pos = {
target_pos.x + cursor_region.x,
target_pos.y + cursor_region.y
target_pos.x + (cursor_region.x * scale.x),
target_pos.y + (cursor_region.y * scale.y)
};
self->params.egl->glEnable(GL_SCISSOR_TEST);
self->params.egl->glScissor(target_pos.x, target_pos.y, self->capture_size.x, self->capture_size.y);
self->params.egl->glScissor(target_pos.x, target_pos.y, output_size.x, output_size.y);
gsr_color_conversion_draw(color_conversion, self->texture_map.cursor_texture_id,
(vec2i){cursor_pos.x, cursor_pos.y}, (vec2i){cursor_region.width, cursor_region.height},
(vec2i){cursor_pos.x, cursor_pos.y}, (vec2i){cursor_region.width * scale.x, cursor_region.height * scale.y},
(vec2i){0, 0}, (vec2i){cursor_region.width, cursor_region.height},
0.0f, false);
self->params.egl->glDisable(GL_SCISSOR_TEST);

View File

@@ -113,13 +113,14 @@ static int gsr_capture_xcomposite_start(gsr_capture *cap, AVCodecContext *video_
self->params.egl->glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_HEIGHT, &self->texture_size.y);
self->params.egl->glBindTexture(GL_TEXTURE_2D, 0);
vec2i video_size = self->texture_size;
if(self->params.region_size.x > 0 && self->params.region_size.y > 0)
video_size = self->params.region_size;
video_codec_context->width = FFALIGN(video_size.x, 2);
video_codec_context->height = FFALIGN(video_size.y, 2);
if(self->params.output_resolution.x == 0 && self->params.output_resolution.y == 0) {
self->params.output_resolution = self->texture_size;
video_codec_context->width = FFALIGN(self->texture_size.x, 2);
video_codec_context->height = FFALIGN(self->texture_size.y, 2);
} else {
video_codec_context->width = FFALIGN(self->params.output_resolution.x, 2);
video_codec_context->height = FFALIGN(self->params.output_resolution.y, 2);
}
frame->width = video_codec_context->width;
frame->height = video_codec_context->height;
@@ -257,14 +258,18 @@ static int gsr_capture_xcomposite_capture(gsr_capture *cap, AVFrame *frame, gsr_
gsr_color_conversion_clear(color_conversion);
}
const vec2i target_pos = { max_int(0, frame->width / 2 - self->texture_size.x / 2), max_int(0, frame->height / 2 - self->texture_size.y / 2) };
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, frame->width / 2 - output_size.x / 2), max_int(0, frame->height / 2 - output_size.y / 2) };
self->params.egl->glFlush();
self->params.egl->glFinish();
/* Fast opengl free path */
if(!self->fast_path_failed && video_codec_context_is_vaapi(self->video_codec_context) && self->params.egl->gpu_info.vendor == GSR_GPU_VENDOR_AMD) {
if(!vaapi_copy_egl_image_to_video_surface(self->params.egl, self->window_texture.image, (vec2i){0, 0}, self->texture_size, target_pos, self->texture_size, self->video_codec_context, frame)) {
if(!vaapi_copy_egl_image_to_video_surface(self->params.egl, self->window_texture.image, (vec2i){0, 0}, self->texture_size, target_pos, output_size, self->video_codec_context, frame)) {
fprintf(stderr, "gsr error: gsr_capture_xcomposite_capture: vaapi_copy_egl_image_to_video_surface failed, falling back to opengl copy. Please report this as an issue at https://github.com/dec05eba/gpu-screen-recorder-issues\n");
self->fast_path_failed = true;
}
@@ -274,24 +279,29 @@ static int gsr_capture_xcomposite_capture(gsr_capture *cap, AVFrame *frame, gsr_
if(self->fast_path_failed) {
gsr_color_conversion_draw(color_conversion, window_texture_get_opengl_texture_id(&self->window_texture),
target_pos, self->texture_size,
target_pos, output_size,
(vec2i){0, 0}, self->texture_size,
0.0f, false);
}
if(self->params.record_cursor && self->cursor.visible) {
const vec2d scale = {
self->texture_size.x == 0 ? 0 : (double)output_size.x / (double)self->texture_size.x,
self->texture_size.y == 0 ? 0 : (double)output_size.y / (double)self->texture_size.y
};
gsr_cursor_tick(&self->cursor, self->window);
const vec2i cursor_pos = {
target_pos.x + self->cursor.position.x - self->cursor.hotspot.x,
target_pos.y + self->cursor.position.y - self->cursor.hotspot.y
target_pos.x + (self->cursor.position.x - self->cursor.hotspot.x) * scale.x,
target_pos.y + (self->cursor.position.y - self->cursor.hotspot.y) * scale.y
};
self->params.egl->glEnable(GL_SCISSOR_TEST);
self->params.egl->glScissor(target_pos.x, target_pos.y, self->texture_size.x, self->texture_size.y);
self->params.egl->glScissor(target_pos.x, target_pos.y, output_size.x, output_size.y);
gsr_color_conversion_draw(color_conversion, self->cursor.texture_id,
cursor_pos, self->cursor.size,
cursor_pos, (vec2i){self->cursor.size.x * scale.x, self->cursor.size.y * scale.y},
(vec2i){0, 0}, self->cursor.size,
0.0f, false);

View File

@@ -223,7 +223,7 @@ static bool gsr_egl_create_window(gsr_egl *self, bool wayland) {
if(wayland) {
self->wayland.dpy = wl_display_connect(NULL);
if(!self->wayland.dpy) {
fprintf(stderr, "gsr error: gsr_egl_create_window failed: wl_display_connect failed\n");
fprintf(stderr, "gsr error: gsr_egl_create_window failed: failed to connect to the Wayland server\n");
goto fail;
}

View File

@@ -537,7 +537,7 @@ static AVCodecContext *create_video_codec_context(AVPixelFormat pix_fmt,
codec_context->bit_rate = bitrate;
codec_context->rc_max_rate = codec_context->bit_rate;
//codec_context->rc_min_rate = codec_context->bit_rate;
//codec_context->rc_buffer_size = codec_context->bit_rate;//codec_context->bit_rate / 10;
codec_context->rc_buffer_size = codec_context->bit_rate;//codec_context->bit_rate / 10;
codec_context->rc_initial_buffer_occupancy = 0;//codec_context->bit_rate;//codec_context->bit_rate * 1000;
} else if(bitrate_mode == BitrateMode::VBR) {
const int quality = vbr_get_quality_parameter(codec_context, video_quality, hdr);
@@ -566,8 +566,10 @@ static AVCodecContext *create_video_codec_context(AVPixelFormat pix_fmt,
codec_context->rc_max_rate = codec_context->bit_rate;
//codec_context->rc_min_rate = codec_context->bit_rate;
//codec_context->rc_buffer_size = codec_context->bit_rate;//codec_context->bit_rate / 10;
codec_context->rc_buffer_size = codec_context->bit_rate;//codec_context->bit_rate / 10;
codec_context->rc_initial_buffer_occupancy = codec_context->bit_rate;//codec_context->bit_rate * 1000;
} else {
//codec_context->rc_buffer_size = 50000 * 1000;
}
//codec_context->profile = FF_PROFILE_H264_MAIN;
if (codec_context->codec_id == AV_CODEC_ID_MPEG1VIDEO)
@@ -585,7 +587,7 @@ static AVCodecContext *create_video_codec_context(AVPixelFormat pix_fmt,
codec_context->global_quality = 120 * quality_multiply;
break;
case VideoQuality::VERY_HIGH:
codec_context->global_quality = 100 * quality_multiply;
codec_context->global_quality = 115 * quality_multiply;
break;
case VideoQuality::ULTRA:
codec_context->global_quality = 90 * quality_multiply;
@@ -600,7 +602,7 @@ static AVCodecContext *create_video_codec_context(AVPixelFormat pix_fmt,
codec_context->global_quality = 30 * quality_multiply;
break;
case VideoQuality::VERY_HIGH:
codec_context->global_quality = 20 * quality_multiply;
codec_context->global_quality = 25 * quality_multiply;
break;
case VideoQuality::ULTRA:
codec_context->global_quality = 10 * quality_multiply;
@@ -615,7 +617,7 @@ static AVCodecContext *create_video_codec_context(AVPixelFormat pix_fmt,
codec_context->global_quality = 30 * quality_multiply;
break;
case VideoQuality::VERY_HIGH:
codec_context->global_quality = 20 * quality_multiply;
codec_context->global_quality = 25 * quality_multiply;
break;
case VideoQuality::ULTRA:
codec_context->global_quality = 10 * quality_multiply;
@@ -779,10 +781,10 @@ static void video_software_set_qp(AVCodecContext *codec_context, VideoQuality vi
av_dict_set_int(options, "qp", 30 * qp_multiply, 0);
break;
case VideoQuality::VERY_HIGH:
av_dict_set_int(options, "qp", 23 * qp_multiply, 0);
av_dict_set_int(options, "qp", 25 * qp_multiply, 0);
break;
case VideoQuality::ULTRA:
av_dict_set_int(options, "qp", 20 * qp_multiply, 0);
av_dict_set_int(options, "qp", 22 * qp_multiply, 0);
break;
}
} else {
@@ -873,7 +875,7 @@ static void video_hardware_set_qp(AVCodecContext *codec_context, VideoQuality vi
av_dict_set_int(options, "qp", 30 * qp_multiply, 0);
break;
case VideoQuality::VERY_HIGH:
av_dict_set_int(options, "qp", 25 * qp_multiply, 0);
av_dict_set_int(options, "qp", 27 * qp_multiply, 0);
break;
case VideoQuality::ULTRA:
av_dict_set_int(options, "qp", 22 * qp_multiply, 0);
@@ -882,16 +884,16 @@ static void video_hardware_set_qp(AVCodecContext *codec_context, VideoQuality vi
} else if(codec_context->codec_id == AV_CODEC_ID_H264) {
switch(video_quality) {
case VideoQuality::MEDIUM:
av_dict_set_int(options, "qp", 34 * qp_multiply, 0);
av_dict_set_int(options, "qp", 35 * qp_multiply, 0);
break;
case VideoQuality::HIGH:
av_dict_set_int(options, "qp", 30 * qp_multiply, 0);
break;
case VideoQuality::VERY_HIGH:
av_dict_set_int(options, "qp", 23 * qp_multiply, 0);
av_dict_set_int(options, "qp", 27 * qp_multiply, 0);
break;
case VideoQuality::ULTRA:
av_dict_set_int(options, "qp", 20 * qp_multiply, 0);
av_dict_set_int(options, "qp", 22 * qp_multiply, 0);
break;
}
} else if(codec_context->codec_id == AV_CODEC_ID_HEVC) {
@@ -903,7 +905,7 @@ static void video_hardware_set_qp(AVCodecContext *codec_context, VideoQuality vi
av_dict_set_int(options, "qp", 30 * qp_multiply, 0);
break;
case VideoQuality::VERY_HIGH:
av_dict_set_int(options, "qp", 25 * qp_multiply, 0);
av_dict_set_int(options, "qp", 27 * qp_multiply, 0);
break;
case VideoQuality::ULTRA:
av_dict_set_int(options, "qp", 22 * qp_multiply, 0);
@@ -918,7 +920,7 @@ static void video_hardware_set_qp(AVCodecContext *codec_context, VideoQuality vi
av_dict_set_int(options, "qp", 30 * qp_multiply, 0);
break;
case VideoQuality::VERY_HIGH:
av_dict_set_int(options, "qp", 25 * qp_multiply, 0);
av_dict_set_int(options, "qp", 27 * qp_multiply, 0);
break;
case VideoQuality::ULTRA:
av_dict_set_int(options, "qp", 22 * qp_multiply, 0);
@@ -931,16 +933,16 @@ static void video_hardware_set_qp(AVCodecContext *codec_context, VideoQuality vi
} else if(codec_context->codec_id == AV_CODEC_ID_H264) {
switch(video_quality) {
case VideoQuality::MEDIUM:
av_dict_set_int(options, "qp", 34 * qp_multiply, 0);
av_dict_set_int(options, "qp", 35 * qp_multiply, 0);
break;
case VideoQuality::HIGH:
av_dict_set_int(options, "qp", 30 * qp_multiply, 0);
break;
case VideoQuality::VERY_HIGH:
av_dict_set_int(options, "qp", 23 * qp_multiply, 0);
av_dict_set_int(options, "qp", 27 * qp_multiply, 0);
break;
case VideoQuality::ULTRA:
av_dict_set_int(options, "qp", 20 * qp_multiply, 0);
av_dict_set_int(options, "qp", 22 * qp_multiply, 0);
break;
}
} else if(codec_context->codec_id == AV_CODEC_ID_HEVC) {
@@ -952,7 +954,7 @@ static void video_hardware_set_qp(AVCodecContext *codec_context, VideoQuality vi
av_dict_set_int(options, "qp", 30 * qp_multiply, 0);
break;
case VideoQuality::VERY_HIGH:
av_dict_set_int(options, "qp", 25 * qp_multiply, 0);
av_dict_set_int(options, "qp", 27 * qp_multiply, 0);
break;
case VideoQuality::ULTRA:
av_dict_set_int(options, "qp", 22 * qp_multiply, 0);
@@ -967,7 +969,7 @@ static void video_hardware_set_qp(AVCodecContext *codec_context, VideoQuality vi
av_dict_set_int(options, "qp", 30 * qp_multiply, 0);
break;
case VideoQuality::VERY_HIGH:
av_dict_set_int(options, "qp", 25 * qp_multiply, 0);
av_dict_set_int(options, "qp", 27 * qp_multiply, 0);
break;
case VideoQuality::ULTRA:
av_dict_set_int(options, "qp", 22 * qp_multiply, 0);
@@ -989,7 +991,7 @@ static void open_video_hardware(AVCodecContext *codec_context, VideoQuality vide
// TODO: Enable multipass
if(vendor == GSR_GPU_VENDOR_NVIDIA) {
// TODO: Test these, if they are needed, if they should be used
// TODO: These dont seem to be necessary
// av_dict_set_int(&options, "zerolatency", 1, 0);
// if(codec_context->codec_id == AV_CODEC_ID_AV1) {
// av_dict_set(&options, "tune", "ll", 0);
@@ -1082,7 +1084,9 @@ static void usage_full() {
fprintf(stderr, " If an output file is specified and -c is not used then the container format is determined from the output filename extension.\n");
fprintf(stderr, " Only containers that support h264, hevc, av1, vp8 or vp9 are supported, which means that only mp4, mkv, flv, webm (and some others) are supported.\n");
fprintf(stderr, "\n");
fprintf(stderr, " -s The size (area) to record at in the format WxH, for example 1920x1080. This option is only supported (and required) when -w is \"focused\".\n");
fprintf(stderr, " -s The output resolution limit of the video in the format WxH, for example 1920x1080. If this is 0x0 then the original resolution is used. Optional, except when -w is \"focused\".\n");
fprintf(stderr, " Note: the captured content is scaled to this size. The output resolution might not be exactly as specified by this option. The original aspect ratio is respected so the resolution will match that.\n");
fprintf(stderr, " The video encoder might also need to add padding, which will result in black bars on the sides of the video. This is especially an issue on AMD.\n");
fprintf(stderr, "\n");
fprintf(stderr, " -f Frame rate to record at. Recording will only capture frames at this target frame rate.\n");
fprintf(stderr, " For constant frame rate mode this option is the frame rate every frame will be captured at and if the capture frame rate is below this target frame rate then the frames will be duplicated.\n");
@@ -1162,7 +1166,7 @@ static void usage_full() {
fprintf(stderr, " Note: the directory to the portal session token file is created automatically if it doesn't exist.\n");
fprintf(stderr, "\n");
fprintf(stderr, " -encoder\n");
fprintf(stderr, " Which device should be used for video encoding. Should either be 'gpu' or 'cpu'. Does currently only work with h264 codec option (-k).\n");
fprintf(stderr, " Which device should be used for video encoding. Should either be 'gpu' or 'cpu'. 'cpu' option currently only work with h264 codec option (-k).\n");
fprintf(stderr, " Optional, set to 'gpu' by default.\n");
fprintf(stderr, "\n");
fprintf(stderr, " --info\n");
@@ -1177,6 +1181,7 @@ static void usage_full() {
fprintf(stderr, " For example:\n");
fprintf(stderr, " bluez_input.88:C9:E8:66:A2:27|WH-1000XM4\n");
fprintf(stderr, " The <audio_device_name> is the name to pass to GPU Screen Recorder in a -a option.\n");
fprintf(stderr, "\n");
fprintf(stderr, " --version\n");
fprintf(stderr, " Print version (%s) and exit\n", GSR_VERSION);
fprintf(stderr, "\n");
@@ -1185,7 +1190,7 @@ static void usage_full() {
fprintf(stderr, " In replay mode this has to be a directory instead of a file.\n");
fprintf(stderr, " Note: the directory to the file is created automatically if it doesn't already exist.\n");
fprintf(stderr, "\n");
fprintf(stderr, " -v Prints per second, fps updates. Optional, set to 'yes' by default.\n");
fprintf(stderr, " -v Prints fps and damage info once per second. Optional, set to 'yes' by default.\n");
fprintf(stderr, "\n");
fprintf(stderr, " -h, --help\n");
fprintf(stderr, " Show this help.\n");
@@ -1197,11 +1202,12 @@ static void usage_full() {
fprintf(stderr, "\n");
fprintf(stderr, "EXAMPLES:\n");
fprintf(stderr, " %s -w screen -f 60 -a default_output -o \"$HOME/Videos/video.mp4\"\n", program_name);
fprintf(stderr, " %s -w screen -f 60 -a default_output -a default_input -o \"$HOME/Videos/video.mp4\"\n", program_name);
fprintf(stderr, " %s -w screen -f 60 -a \"default_output|default_input\" -o \"$HOME/Videos/video.mp4\"\n", program_name);
fprintf(stderr, " %s -w screen -f 60 -a default_output -c mkv -r 60 -o \"$HOME/Videos\"\n", program_name);
fprintf(stderr, " %s -w screen -f 60 -a default_output -c mkv -sc script.sh -r 60 -o \"$HOME/Videos\"\n", program_name);
fprintf(stderr, " %s -w portal -f 60 -a default_output -restore-portal-session yes -o \"$HOME/Videos/video.mp4\"\n", program_name);
fprintf(stderr, " %s -w screen -f 60 -a default_output -bm cbr -q 5000 -o \"$HOME/Videos/video.mp4\"\n", program_name);
fprintf(stderr, " %s -w screen -f 60 -a default_output -bm cbr -q 15000 -o \"$HOME/Videos/video.mp4\"\n", program_name);
//fprintf(stderr, " gpu-screen-recorder -w screen -f 60 -q ultra -pixfmt yuv444 -o video.mp4\n");
_exit(1);
}
@@ -1580,16 +1586,27 @@ static void split_string(const std::string &str, char delimiter, std::function<b
}
}
static std::vector<AudioInput> parse_audio_input_arg(const char *str) {
static const AudioInput* get_audio_device_by_name(const std::vector<AudioInput> &audio_inputs, const std::string &name) {
for(const auto &audio_input : audio_inputs) {
if(audio_input.name == name)
return &audio_input;
}
return nullptr;
}
static std::vector<AudioInput> parse_audio_input_arg(const char *str, const AudioDevices &audio_devices) {
std::vector<AudioInput> audio_inputs;
split_string(str, '|', [&audio_inputs](const char *sub, size_t size) {
split_string(str, '|', [&](const char *sub, size_t size) {
AudioInput audio_input;
audio_input.name.assign(sub, size);
const bool name_is_existing_audio_device = get_audio_device_by_name(audio_devices.audio_inputs, audio_input.name);
const size_t index = audio_input.name.find('/');
if(index != std::string::npos) {
if(!name_is_existing_audio_device && index != std::string::npos) {
audio_input.description = audio_input.name.substr(0, index);
audio_input.name.erase(audio_input.name.begin(), audio_input.name.begin() + index + 1);
}
audio_inputs.push_back(std::move(audio_input));
return true;
});
@@ -2063,11 +2080,10 @@ static void list_audio_devices_command() {
_exit(0);
}
static gsr_capture* create_capture_impl(std::string &window_str, const char *screen_region, bool wayland, gsr_egl *egl, int fps, VideoCodec video_codec, gsr_color_range color_range,
static gsr_capture* create_capture_impl(std::string &window_str, vec2i output_resolution, bool wayland, gsr_egl *egl, int fps, VideoCodec video_codec, gsr_color_range color_range,
bool record_cursor, bool use_software_video_encoder, bool restore_portal_session, const char *portal_session_token_filepath,
gsr_color_depth color_depth)
{
vec2i region_size = { 0, 0 };
Window src_window_id = None;
bool follow_focused = false;
@@ -2078,18 +2094,8 @@ static gsr_capture* create_capture_impl(std::string &window_str, const char *scr
_exit(2);
}
if(!screen_region) {
fprintf(stderr, "Error: option -s is required when using -w focused\n");
usage();
}
if(sscanf(screen_region, "%dx%d", &region_size.x, &region_size.y) != 2) {
fprintf(stderr, "Error: invalid value for option -s '%s', expected a value in format WxH\n", screen_region);
usage();
}
if(region_size.x <= 0 || region_size.y <= 0) {
fprintf(stderr, "Error: invalud value for option -s '%s', expected width and height to be greater than 0\n", screen_region);
if(output_resolution.x <= 0 || output_resolution.y <= 0) {
fprintf(stderr, "Error: invalid value for option -s '%dx%d' when using -w focused option. expected width and height to be greater than 0\n", output_resolution.x, output_resolution.y);
usage();
}
@@ -2109,6 +2115,7 @@ static gsr_capture* create_capture_impl(std::string &window_str, const char *scr
portal_params.record_cursor = record_cursor;
portal_params.restore_portal_session = restore_portal_session;
portal_params.portal_session_token_filepath = portal_session_token_filepath;
portal_params.output_resolution = output_resolution;
capture = gsr_capture_portal_create(&portal_params);
if(!capture)
_exit(1);
@@ -2183,6 +2190,7 @@ static gsr_capture* create_capture_impl(std::string &window_str, const char *scr
nvfbc_params.color_range = color_range;
nvfbc_params.record_cursor = record_cursor;
nvfbc_params.use_software_video_encoder = use_software_video_encoder;
nvfbc_params.output_resolution = output_resolution;
capture = gsr_capture_nvfbc_create(&nvfbc_params);
if(!capture)
_exit(1);
@@ -2195,6 +2203,7 @@ static gsr_capture* create_capture_impl(std::string &window_str, const char *scr
kms_params.record_cursor = record_cursor;
kms_params.hdr = video_codec_is_hdr(video_codec);
kms_params.fps = fps;
kms_params.output_resolution = output_resolution;
capture = gsr_capture_kms_create(&kms_params);
if(!capture)
_exit(1);
@@ -2218,10 +2227,10 @@ static gsr_capture* create_capture_impl(std::string &window_str, const char *scr
xcomposite_params.egl = egl;
xcomposite_params.window = src_window_id;
xcomposite_params.follow_focused = follow_focused;
xcomposite_params.region_size = region_size;
xcomposite_params.color_range = color_range;
xcomposite_params.record_cursor = record_cursor;
xcomposite_params.color_depth = color_depth;
xcomposite_params.output_resolution = output_resolution;
capture = gsr_capture_xcomposite_create(&xcomposite_params);
if(!capture)
_exit(1);
@@ -2264,7 +2273,7 @@ static std::vector<MergedAudioInputs> parse_audio_inputs(const AudioDevices &aud
if(!audio_input || audio_input[0] == '\0')
continue;
requested_audio_inputs.push_back({parse_audio_input_arg(audio_input)});
requested_audio_inputs.push_back({parse_audio_input_arg(audio_input, audio_devices)});
if(requested_audio_inputs.back().audio_inputs.size() > 1)
uses_amix = true;
@@ -2285,14 +2294,11 @@ static std::vector<MergedAudioInputs> parse_audio_inputs(const AudioDevices &aud
match = true;
}
for(const auto &existing_audio_input : audio_devices.audio_inputs) {
if(request_audio_input.name == existing_audio_input.name) {
if(request_audio_input.description.empty())
request_audio_input.description = "gsr-" + existing_audio_input.description;
match = true;
break;
}
const AudioInput* existing_audio_input = get_audio_device_by_name(audio_devices.audio_inputs, request_audio_input.name);
if(existing_audio_input) {
if(request_audio_input.description.empty())
request_audio_input.description = "gsr-" + existing_audio_input->description;
match = true;
}
if(!match) {
@@ -2301,8 +2307,8 @@ static std::vector<MergedAudioInputs> parse_audio_inputs(const AudioDevices &aud
fprintf(stderr, " default_output (Default output)\n");
if(!audio_devices.default_input.empty())
fprintf(stderr, " default_input (Default input)\n");
for(const auto &existing_audio_input : audio_devices.audio_inputs) {
fprintf(stderr, " %s (%s)\n", existing_audio_input.name.c_str(), existing_audio_input.description.c_str());
for(const auto &audio_input : audio_devices.audio_inputs) {
fprintf(stderr, " %s (%s)\n", audio_input.name.c_str(), audio_input.description.c_str());
}
_exit(2);
}
@@ -2598,6 +2604,8 @@ static const AVCodec* select_video_codec_with_fallback(VideoCodec *video_codec,
}
int main(int argc, char **argv) {
setlocale(LC_ALL, "C"); // Sigh... stupid C
signal(SIGINT, stop_handler);
signal(SIGUSR1, save_replay_handler);
signal(SIGUSR2, toggle_pause_handler);
@@ -2609,7 +2617,7 @@ int main(int argc, char **argv) {
setenv("__GL_THREADED_OPTIMIZATIONS", "0", true);
// Forces low latency encoding mode. Use this environment variable until vaapi supports setting this as a parameter.
// The downside of this is that it always uses maximum power, which is not ideal for replay mode that runs on system startup.
// This option was added in mesa 24.1.4, released on july 17, 2024.
// This option was added in mesa 24.1.4, released in july 17, 2024.
// TODO: Add an option to enable/disable this?
// Seems like the performance issue is not in encoding, but rendering the frame.
// Some frames end up taking 10 times longer. Seems to be an issue with amd gpu power management when letting the application sleep on the cpu side?
@@ -3135,13 +3143,25 @@ int main(int argc, char **argv) {
usage();
}
const char *screen_region = args["-s"].value();
if(screen_region && strcmp(window_str.c_str(), "focused") != 0) {
fprintf(stderr, "Error: option -s is only available when using -w focused\n");
const char *output_resolution_str = args["-s"].value();
if(!output_resolution_str && strcmp(window_str.c_str(), "focused") == 0) {
fprintf(stderr, "Error: option -s is required when using -w focused option\n");
usage();
}
vec2i output_resolution = {0, 0};
if(output_resolution_str) {
if(sscanf(output_resolution_str, "%dx%d", &output_resolution.x, &output_resolution.y) != 2) {
fprintf(stderr, "Error: invalid value for option -s '%s', expected a value in format WxH\n", output_resolution_str);
usage();
}
if(output_resolution.x < 0 || output_resolution.y < 0) {
fprintf(stderr, "Error: invalud value for option -s '%s', expected width and height to be greater or equal to 0\n", output_resolution_str);
usage();
}
}
bool is_livestream = false;
const char *filename = args["-o"].value();
if(filename) {
@@ -3226,7 +3246,7 @@ int main(int argc, char **argv) {
const AVCodec *video_codec_f = select_video_codec_with_fallback(&video_codec, video_codec_to_use, file_extension.c_str(), use_software_video_encoder, &egl, &low_power);
const gsr_color_depth color_depth = video_codec_to_bit_depth(video_codec);
gsr_capture *capture = create_capture_impl(window_str, screen_region, wayland, &egl, fps, video_codec, color_range, record_cursor, use_software_video_encoder, restore_portal_session, portal_session_token_filepath, color_depth);
gsr_capture *capture = create_capture_impl(window_str, output_resolution, wayland, &egl, fps, video_codec, color_range, record_cursor, use_software_video_encoder, restore_portal_session, portal_session_token_filepath, color_depth);
// (Some?) livestreaming services require at least one audio track to work.
// If not audio is provided then create one silent audio track.
@@ -3805,7 +3825,7 @@ int main(int argc, char **argv) {
const double frame_time = frame_end - frame_start;
const bool frame_deadline_missed = frame_time > target_fps;
if(time_to_next_frame > 0.0 && !frame_deadline_missed && frame_captured)
if(time_to_next_frame >= 0.0 && !frame_deadline_missed && frame_captured)
av_usleep(time_to_next_frame * 1000.0 * 1000.0);
else {
if(paused)

View File

@@ -340,16 +340,18 @@ static void pa_server_info_cb(pa_context*, const pa_server_info *server_info, vo
}
static void get_pulseaudio_default_inputs(AudioDevices &audio_devices) {
pa_mainloop *main_loop = pa_mainloop_new();
pa_context *ctx = pa_context_new(pa_mainloop_get_api(main_loop), "gpu-screen-recorder-gtk");
pa_context_connect(ctx, NULL, PA_CONTEXT_NOFLAGS, NULL);
int state = 0;
int pa_ready = 0;
pa_context_set_state_callback(ctx, pa_state_cb, &pa_ready);
pa_operation *pa_op = NULL;
pa_mainloop *main_loop = pa_mainloop_new();
pa_context *ctx = pa_context_new(pa_mainloop_get_api(main_loop), "gpu-screen-recorder");
if(pa_context_connect(ctx, NULL, PA_CONTEXT_NOFLAGS, NULL) < 0)
goto done;
pa_context_set_state_callback(ctx, pa_state_cb, &pa_ready);
for(;;) {
// Not ready
if(pa_ready == 0) {
@@ -366,23 +368,25 @@ static void get_pulseaudio_default_inputs(AudioDevices &audio_devices) {
}
// Couldn't get connection to the server
if(pa_ready == 2 || (state == 1 && pa_op && pa_operation_get_state(pa_op) == PA_OPERATION_DONE)) {
if(pa_op)
pa_operation_unref(pa_op);
pa_context_disconnect(ctx);
pa_context_unref(ctx);
pa_mainloop_free(main_loop);
return;
}
if(pa_ready == 2 || (state == 1 && pa_op && pa_operation_get_state(pa_op) == PA_OPERATION_DONE))
break;
pa_mainloop_iterate(main_loop, 1, NULL);
}
done:
if(pa_op)
pa_operation_unref(pa_op);
pa_context_disconnect(ctx);
pa_context_unref(ctx);
pa_mainloop_free(main_loop);
}
AudioDevices get_pulseaudio_inputs() {
AudioDevices audio_devices;
int state = 0;
int pa_ready = 0;
pa_operation *pa_op = NULL;
// TODO: Do this in the same connection below instead of two separate connections
get_pulseaudio_default_inputs(audio_devices);
@@ -390,12 +394,10 @@ AudioDevices get_pulseaudio_inputs() {
pa_mainloop *main_loop = pa_mainloop_new();
pa_context *ctx = pa_context_new(pa_mainloop_get_api(main_loop), "gpu-screen-recorder");
pa_context_connect(ctx, NULL, PA_CONTEXT_NOFLAGS, NULL);
int state = 0;
int pa_ready = 0;
pa_context_set_state_callback(ctx, pa_state_cb, &pa_ready);
if(pa_context_connect(ctx, NULL, PA_CONTEXT_NOFLAGS, NULL) < 0)
goto done;
pa_operation *pa_op = NULL;
pa_context_set_state_callback(ctx, pa_state_cb, &pa_ready);
for(;;) {
// Not ready
@@ -413,17 +415,17 @@ AudioDevices get_pulseaudio_inputs() {
}
// Couldn't get connection to the server
if(pa_ready == 2 || (state == 1 && pa_op && pa_operation_get_state(pa_op) == PA_OPERATION_DONE)) {
if(pa_op)
pa_operation_unref(pa_op);
pa_context_disconnect(ctx);
pa_context_unref(ctx);
if(pa_ready == 2 || (state == 1 && pa_op && pa_operation_get_state(pa_op) == PA_OPERATION_DONE))
break;
}
pa_mainloop_iterate(main_loop, 1, NULL);
}
done:
if(pa_op)
pa_operation_unref(pa_op);
pa_context_disconnect(ctx);
pa_context_unref(ctx);
pa_mainloop_free(main_loop);
return audio_devices;
}

View File

@@ -400,6 +400,8 @@ bool gl_get_gpu_info(gsr_egl *egl, gsr_gpu_info *info) {
if(strstr((const char*)gl_vendor, "AMD"))
info->vendor = GSR_GPU_VENDOR_AMD;
else if(strstr((const char*)gl_vendor, "Mesa") && gl_renderer && strstr((const char*)gl_renderer, "AMD"))
info->vendor = GSR_GPU_VENDOR_AMD;
else if(strstr((const char*)gl_vendor, "Intel"))
info->vendor = GSR_GPU_VENDOR_INTEL;
else if(strstr((const char*)gl_vendor, "NVIDIA"))
@@ -736,6 +738,8 @@ bool vaapi_copy_drm_planes_to_video_surface(AVCodecContext *video_codec_context,
.height = dest_size.y
};
const bool scaled = dest_size.x != source_size.x || dest_size.y != source_size.y;
// Copying a surface to another surface will automatically perform the color conversion. Thanks vaapi!
VAProcPipelineParameterBuffer params = {0};
params.surface = input_surface_id;
@@ -743,7 +747,7 @@ bool vaapi_copy_drm_planes_to_video_surface(AVCodecContext *video_codec_context,
params.surface_region = &source_region;
params.output_region = &output_region;
params.output_background_color = 0;
params.filter_flags = VA_FRAME_PICTURE;
params.filter_flags = scaled ? (VA_FILTER_SCALING_HQ | VA_FILTER_INTERPOLATION_BILINEAR) : 0;
params.pipeline_flags = VA_PROC_PIPELINE_FAST;
params.input_color_properties.colour_primaries = 1;
@@ -875,3 +879,20 @@ bool vaapi_copy_egl_image_to_video_surface(gsr_egl *egl, EGLImage image, vec2i s
return success;
}
vec2i scale_keep_aspect_ratio(vec2i from, vec2i to) {
if(from.x == 0 || from.y == 0)
return (vec2i){0, 0};
const double height_to_width_ratio = (double)from.y / (double)from.x;
from.x = to.x;
from.y = from.x * height_to_width_ratio;
if(from.y > to.y) {
const double width_height_ratio = (double)from.x / (double)from.y;
from.y = to.y;
from.x = from.y * width_height_ratio;
}
return from;
}