aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitignore4
-rw-r--r--README.md14
-rw-r--r--TODO17
-rwxr-xr-xbuild.sh53
-rwxr-xr-xdebug-install.sh20
-rw-r--r--extra/gpu-screen-recorder.env6
-rw-r--r--extra/gpu-screen-recorder.service4
-rwxr-xr-xextra/meson_post_install.sh5
-rw-r--r--include/capture/capture.h2
-rw-r--r--include/capture/kms_cuda.h1
-rw-r--r--include/capture/kms_vaapi.h1
-rw-r--r--include/capture/xcomposite.h9
-rw-r--r--include/cursor.h6
-rw-r--r--include/egl.h3
-rw-r--r--include/utils.h4
-rwxr-xr-xinstall.sh16
-rw-r--r--kms/client/kms_client.c29
-rw-r--r--meson.build62
-rw-r--r--meson_options.txt2
-rw-r--r--project.conf8
-rwxr-xr-xscripts/record-application-name.sh2
-rwxr-xr-xscripts/record-save-application-name.sh2
-rwxr-xr-xscripts/replay-application-name.sh2
-rwxr-xr-xscripts/twitch-stream-local-copy.sh2
-rwxr-xr-xscripts/twitch-stream.sh2
-rwxr-xr-xscripts/youtube-hls-stream.sh8
-rw-r--r--src/capture/capture.c17
-rw-r--r--src/capture/kms.c42
-rw-r--r--src/capture/kms_vaapi.c2
-rw-r--r--src/capture/xcomposite.c156
-rw-r--r--src/capture/xcomposite_cuda.c12
-rw-r--r--src/capture/xcomposite_vaapi.c12
-rw-r--r--src/cursor.c81
-rw-r--r--src/egl.c9
-rw-r--r--src/main.cpp219
-rw-r--r--src/utils.c45
-rwxr-xr-xuninstall.sh11
37 files changed, 587 insertions, 303 deletions
diff --git a/.gitignore b/.gitignore
index aa01f09..bc99e58 100644
--- a/.gitignore
+++ b/.gitignore
@@ -11,6 +11,8 @@ external/wlr-export-dmabuf-unstable-v1-protocol.c
.cache/
.vscode/
+build/
+
*.o
gpu-screen-recorder
gsr-kms-server
@@ -19,3 +21,5 @@ gsr-kms-server
*.flv
*.mkv
*.mov
+*.webm
+*.ts
diff --git a/README.md b/README.md
index 325ca78..52cb1ee 100644
--- a/README.md
+++ b/README.md
@@ -8,8 +8,8 @@ This screen recorder can be used for recording your desktop offline, for live st
where only the last few minutes are saved.
Supported video codecs:
-* H264 (default on Intel)
-* HEVC (default on AMD and NVIDIA)
+* H264 (default)
+* HEVC
* AV1 (not currently supported on NVIDIA if you use GPU Screen Recorder flatpak)
Supported audio codecs:
@@ -56,7 +56,7 @@ If you install GPU Screen Recorder flatpak, which is the gtk gui version then yo
libglvnd (which provides libgl and libegl)\
mesa\
ffmpeg (libavcodec, libavformat, libavutil, libswresample, libavfilter)\
-x11 (libx11, libxcomposite, libxrandr, xfixes)\
+x11 (libx11, libxcomposite, libxrandr, libxfixes, libxdamage, libxi)\
libpulse\
vaapi (libva, libva-mesa-driver)\
libdrm\
@@ -66,7 +66,7 @@ wayland-client
libglvnd (which provides libgl and libegl)\
mesa\
ffmpeg (libavcodec, libavformat, libavutil, libswresample, libavfilter)\
-x11 (libx11, libxcomposite, libxrandr, xfixes)\
+x11 (libx11, libxcomposite, libxrandr, libxfixes, libxdamage, libxi)\
libpulse\
vaapi (libva, intel-media-driver/libva-intel-driver)\
libdrm\
@@ -75,7 +75,7 @@ wayland-client
## NVIDIA
libglvnd (which provides libgl and libegl)\
ffmpeg (libavcodec, libavformat, libavutil, libswresample, libavfilter)\
-x11 (libx11, libxcomposite, libxrandr, xfixes)\
+x11 (libx11, libxcomposite, libxrandr, libxfixes, libxdamage, libxi)\
libpulse\
cuda runtime (libcuda.so.1) (libnvidia-compute)\
nvenc (libnvidia-encode)\
@@ -152,9 +152,9 @@ It seems like ffmpeg earlier than version 6.1 has some type of bug. Install ffmp
Browsers and discord don't support hevc video codec at the moment. Choose h264 video codec instead with the -k h264 option.
Note that websites such as youtube support hevc so there is no need to choose h264 video codec if you intend to upload the video to youtube or if you want to play the video locally or if you intend to
edit the video with a video editor. Hevc allows for better video quality (especially at lower file sizes) so hevc (or av1) is recommended for source videos.
-## I get a black bar on the right/bottom in the video
+## I get a black bar/distorted colors on the right/bottom in the video
This is mostly an issue on AMD. For av1 it's a hardware issue, see: https://gitlab.freedesktop.org/mesa/mesa/-/issues/9185. For hevc it's a software issue that has been fixed but not released yet, see: https://gitlab.freedesktop.org/mesa/mesa/-/issues/10985.
-If you get black bars then the workaround is to record with h264 video codec instead (using the -k h264 option).
+If you get this issue then a workaround is to record with h264 video codec instead (using the -k h264 option).
## The video is glitched, looks like checkerboard pattern
This is an issue on some intel integrated gpus on wayland caused by power saving option. Right now the only way to fix this is to record on X11 instead.
## The video doesn't display or has a green/yellow overlay
diff --git a/TODO b/TODO
index 955d961..39c8c54 100644
--- a/TODO
+++ b/TODO
@@ -84,7 +84,9 @@ Use SRC_W and SRC_H for screen plane instead of crtc_w and crtc_h.
Make it possible to select which /dev/dri/card* to use, but that requires opengl to also use the same card. Not sure if that is possible for amd, intel and nvidia without using vulkan instead.
-Support I915_FORMAT_MOD_Y_TILED_CCS (and other power saving modifiers, see https://trac.ffmpeg.org/ticket/8542). The only fix may be to use desktop portal for recording. This issue doesn't appear on x11 since these modifiers are not used by xorg server.
+Support intel display framebuffer compression (I915_FORMAT_MOD_Y_TILED_CCS modifier) (and other power saving modifiers, see https://trac.ffmpeg.org/ticket/8542). The only fix may be to use desktop portal for recording. This issue doesn't appear on x11 since these modifiers are not used by xorg server.
+This issue only appears on some intel iGPUs, such as Intel Iris Xe, see: https://github.com/dec05eba/gpu-screen-recorder-issues/issues/1.
+Intel dedicated GPU (intel arc a750) can have a similar issue, but it's not related to compression. In that case the modifier is I915_FORMAT_MOD_4_TILED.
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.-
@@ -124,4 +126,15 @@ Implement scaling and use lanczos resampling for better quality. Lanczos resampl
Try fixing HDR by passing HDR+10 data as well, and in the packet. Run "ffprobe -loglevel quiet -read_intervals "%+#2" -select_streams v:0 -show_entries side_data video.mp4" to test if the file has correct metadata.
-Flac is disabled because the frame sizes are too large which causes big audio/video desync. \ No newline at end of file
+Flac is disabled because the frame sizes are too large which causes big audio/video desync.
+
+Add 10-bit capture option. This is good because it reduces banding and quality in very dark areas while reducing the file size compared to doing the same thing with 8-bits.
+
+Enable b-frames.
+
+Support vfr matching games exact fps all the time. On x11 use damage tracking, on wayland? maybe there is drm plane damage tracking. But that may not be accurate as the compositor may update it every monitor hz anyways. On wayland maybe only support it for desktop portal + pipewire capture.
+
+Support selecting which gpu to use. This can be done in egl with eglQueryDevicesEXT and then eglGetPlatformDisplayEXT. This will automatically work on AMD and Intel as vaapi uses the same device. On nvidia we need to use eglQueryDeviceAttribEXT with EGL_CUDA_DEVICE_NV.
+ Maybe on glx (nvidia x11 nvfbc) we need to use __NV_PRIME_RENDER_OFFLOAD_PROVIDER and __GLX_VENDOR_LIBRARY_NAME instead.
+
+Remove is_damaged and clear_damage and return a value from capture function instead that states if the image has been updated or not.
diff --git a/build.sh b/build.sh
deleted file mode 100755
index b353406..0000000
--- a/build.sh
+++ /dev/null
@@ -1,53 +0,0 @@
-#!/bin/sh -e
-
-script_dir=$(dirname "$0")
-cd "$script_dir"
-
-CC=${CC:-gcc}
-CXX=${CXX:-g++}
-
-opts="-O2 -g0 -DNDEBUG -Wall -Wextra -Wshadow"
-[ -n "$DEBUG" ] && opts="-O0 -g3 -Wall -Wextra -Wshadow";
-
-build_gsr_kms_server() {
- # TODO: -fcf-protection=full, not supported on arm
- extra_opts="-fstack-protector-all"
- dependencies="libdrm"
- includes="$(pkg-config --cflags $dependencies)"
- libs="$(pkg-config --libs $dependencies) -ldl"
- $CC -c kms/server/kms_server.c $opts $extra_opts $includes
- $CC -o gsr-kms-server kms_server.o $libs $opts $extra_opts
-}
-
-build_gsr() {
- dependencies="libavcodec libavformat libavutil x11 xcomposite xrandr xfixes libpulse libswresample libavfilter libva libcap libdrm wayland-egl wayland-client"
- includes="$(pkg-config --cflags $dependencies)"
- libs="$(pkg-config --libs $dependencies) -ldl -pthread -lm"
- $CC -c src/capture/capture.c $opts $includes
- $CC -c src/capture/nvfbc.c $opts $includes
- $CC -c src/capture/xcomposite.c $opts $includes
- $CC -c src/capture/xcomposite_cuda.c $opts $includes
- $CC -c src/capture/xcomposite_vaapi.c $opts $includes
- $CC -c src/capture/kms_vaapi.c $opts $includes
- $CC -c src/capture/kms_cuda.c $opts $includes
- $CC -c src/capture/kms.c $opts $includes
- $CC -c kms/client/kms_client.c $opts $includes
- $CC -c src/egl.c $opts $includes
- $CC -c src/cuda.c $opts $includes
- $CC -c src/xnvctrl.c $opts $includes
- $CC -c src/overclock.c $opts $includes
- $CC -c src/window_texture.c $opts $includes
- $CC -c src/shader.c $opts $includes
- $CC -c src/color_conversion.c $opts $includes
- $CC -c src/utils.c $opts $includes
- $CC -c src/library_loader.c $opts $includes
- $CC -c src/cursor.c $opts $includes
- $CXX -c src/sound.cpp $opts $includes
- $CXX -c src/main.cpp $opts $includes
- $CXX -o gpu-screen-recorder capture.o nvfbc.o kms_client.o egl.o cuda.o xnvctrl.o overclock.o window_texture.o shader.o \
- color_conversion.o utils.o library_loader.o cursor.o xcomposite.o xcomposite_cuda.o xcomposite_vaapi.o kms_vaapi.o kms_cuda.o kms.o sound.o main.o $libs $opts
-}
-
-build_gsr_kms_server
-build_gsr
-echo "Successfully built gpu-screen-recorder"
diff --git a/debug-install.sh b/debug-install.sh
deleted file mode 100755
index 096eefa..0000000
--- a/debug-install.sh
+++ /dev/null
@@ -1,20 +0,0 @@
-#!/bin/sh -e
-
-script_dir=$(dirname "$0")
-cd "$script_dir"
-
-[ $(id -u) -ne 0 ] && echo "You need root privileges to run the install script" && exit 1
-
-DEBUG=1 ./build.sh
-
-install -Dm755 "gsr-kms-server" "/usr/bin/gsr-kms-server"
-install -Dm755 "gpu-screen-recorder" "/usr/bin/gpu-screen-recorder"
-if [ -d "/usr/lib/systemd/user" ]; then
- install -Dm644 "extra/gpu-screen-recorder.service" "/usr/lib/systemd/user/gpu-screen-recorder.service"
-fi
-# Not necessary, but removes the password prompt when trying to record a monitor on amd/intel or nvidia wayland
-setcap cap_sys_admin+ep /usr/bin/gsr-kms-server
-# Not necessary, but allows use of EGL_CONTEXT_PRIORITY_LEVEL_IMG which allows gpu screen recorder to run without being limited to game fps under heavy load on AMD/Intel
-setcap cap_sys_nice+ep /usr/bin/gpu-screen-recorder
-
-echo "Successfully installed gpu-screen-recorder (debug)"
diff --git a/extra/gpu-screen-recorder.env b/extra/gpu-screen-recorder.env
index c4728cc..ce9f223 100644
--- a/extra/gpu-screen-recorder.env
+++ b/extra/gpu-screen-recorder.env
@@ -1,9 +1,11 @@
WINDOW=screen
CONTAINER=mp4
QUALITY=very_high
-CODEC=hevc
+CODEC=h264
AUDIO_CODEC=opus
AUDIO_DEVICE=alsa_output.pci-0000_0a_00.4.iec958-stereo.monitor
+SECONDARY_AUDIO_DEVICE=alsa_input.some-mic.mono-fallback
FRAMERATE=60
REPLAYDURATION=60
-OUTPUTDIR=/run/media/dec05eba/SSD1TB/Videos/aaaa \ No newline at end of file
+OUTPUTDIR=/run/media/dec05eba/SSD1TB/Videos/aaaa
+KEYINT=2
diff --git a/extra/gpu-screen-recorder.service b/extra/gpu-screen-recorder.service
index 0ba621b..6933f66 100644
--- a/extra/gpu-screen-recorder.service
+++ b/extra/gpu-screen-recorder.service
@@ -9,12 +9,14 @@ Environment=QUALITY=very_high
Environment=CODEC=auto
Environment=AUDIO_CODEC=opus
Environment=AUDIO_DEVICE=
+Environment=SECONDARY_AUDIO_DEVICE=
Environment=FRAMERATE=60
Environment=REPLAYDURATION=30
Environment=OUTPUTDIR=%h/Videos
Environment=MAKEFOLDERS=no
Environment=COLOR_RANGE=limited
-ExecStart=/bin/sh -c 'AUDIO="${AUDIO_DEVICE:-$(pactl get-default-sink).monitor}"; gpu-screen-recorder -v no -w $WINDOW -c $CONTAINER -q $QUALITY -k $CODEC -ac $AUDIO_CODEC -a "$AUDIO" -f $FRAMERATE -r $REPLAYDURATION -o "$OUTPUTDIR" -mf $MAKEFOLDERS $ADDITIONAL_ARGS -cr $COLOR_RANGE'
+Environment=KEYINT=2
+ExecStart=/bin/sh -c 'AUDIO="${AUDIO_DEVICE:-$(pactl get-default-sink).monitor}"; gpu-screen-recorder -v no -w $WINDOW -c $CONTAINER -q $QUALITY -k $CODEC -ac $AUDIO_CODEC -a "$AUDIO" -a "$SECONDARY_AUDIO_DEVICE" -f $FRAMERATE -r $REPLAYDURATION -o "$OUTPUTDIR" -mf $MAKEFOLDERS $ADDITIONAL_ARGS -cr $COLOR_RANGE -keyint $KEYINT'
KillSignal=SIGINT
Restart=on-failure
RestartSec=5s
diff --git a/extra/meson_post_install.sh b/extra/meson_post_install.sh
new file mode 100755
index 0000000..f1f6a5a
--- /dev/null
+++ b/extra/meson_post_install.sh
@@ -0,0 +1,5 @@
+#!/bin/sh
+
+setcap cap_sys_admin+ep ${MESON_INSTALL_DESTDIR_PREFIX}/bin/gsr-kms-server \
+ || echo "\n!!! Please re-run install as root\n"
+setcap cap_sys_nice+ep ${MESON_INSTALL_DESTDIR_PREFIX}/bin/gpu-screen-recorder
diff --git a/include/capture/capture.h b/include/capture/capture.h
index 2eb8e42..fbbe767 100644
--- a/include/capture/capture.h
+++ b/include/capture/capture.h
@@ -21,6 +21,8 @@ struct gsr_capture {
/* These methods should not be called manually. Call gsr_capture_* instead */
int (*start)(gsr_capture *cap, AVCodecContext *video_codec_context, AVFrame *frame);
void (*tick)(gsr_capture *cap, AVCodecContext *video_codec_context); /* can be NULL */
+ bool (*is_damaged)(gsr_capture *cap); /* can be NULL */
+ void (*clear_damage)(gsr_capture *cap); /* can be NULL */
bool (*should_stop)(gsr_capture *cap, bool *err); /* can be NULL */
int (*capture)(gsr_capture *cap, AVFrame *frame);
void (*capture_end)(gsr_capture *cap, AVFrame *frame); /* can be NULL */
diff --git a/include/capture/kms_cuda.h b/include/capture/kms_cuda.h
index fd0d396..433e053 100644
--- a/include/capture/kms_cuda.h
+++ b/include/capture/kms_cuda.h
@@ -9,7 +9,6 @@
typedef struct {
gsr_egl *egl;
const char *display_to_capture; /* if this is "screen", then the first monitor is captured. A copy is made of this */
- gsr_gpu_info gpu_inf;
bool hdr;
gsr_color_range color_range;
bool record_cursor;
diff --git a/include/capture/kms_vaapi.h b/include/capture/kms_vaapi.h
index 196b597..bf078b5 100644
--- a/include/capture/kms_vaapi.h
+++ b/include/capture/kms_vaapi.h
@@ -9,7 +9,6 @@
typedef struct {
gsr_egl *egl;
const char *display_to_capture; /* if this is "screen", then the first monitor is captured. A copy is made of this */
- gsr_gpu_info gpu_inf;
bool hdr;
gsr_color_range color_range;
bool record_cursor;
diff --git a/include/capture/xcomposite.h b/include/capture/xcomposite.h
index ce0dbad..27b289a 100644
--- a/include/capture/xcomposite.h
+++ b/include/capture/xcomposite.h
@@ -15,6 +15,7 @@ typedef struct {
vec2i region_size; /* This is currently only used with |follow_focused| */
gsr_color_range color_range;
bool record_cursor;
+ bool track_damage;
} gsr_capture_xcomposite_params;
typedef struct {
@@ -37,7 +38,11 @@ typedef struct {
Atom net_active_window_atom;
gsr_cursor cursor;
- bool clear_next_frame;
+
+ int damage_event;
+ int damage_error;
+ XID damage;
+ bool damaged;
} gsr_capture_xcomposite;
void gsr_capture_xcomposite_init(gsr_capture_xcomposite *self, const gsr_capture_xcomposite_params *params);
@@ -45,6 +50,8 @@ void gsr_capture_xcomposite_init(gsr_capture_xcomposite *self, const gsr_capture
int gsr_capture_xcomposite_start(gsr_capture_xcomposite *self, AVCodecContext *video_codec_context, AVFrame *frame);
void gsr_capture_xcomposite_stop(gsr_capture_xcomposite *self);
void gsr_capture_xcomposite_tick(gsr_capture_xcomposite *self, AVCodecContext *video_codec_context);
+bool gsr_capture_xcomposite_is_damaged(gsr_capture_xcomposite *self);
+void gsr_capture_xcomposite_clear_damage(gsr_capture_xcomposite *self);
bool gsr_capture_xcomposite_should_stop(gsr_capture_xcomposite *self, bool *err);
int gsr_capture_xcomposite_capture(gsr_capture_xcomposite *self, AVFrame *frame);
diff --git a/include/cursor.h b/include/cursor.h
index b1ec6bd..2f26dfd 100644
--- a/include/cursor.h
+++ b/include/cursor.h
@@ -8,6 +8,7 @@ typedef struct {
gsr_egl *egl;
Display *display;
int x_fixes_event_base;
+ int xi_opcode;
unsigned int texture_id;
vec2i size;
@@ -15,12 +16,15 @@ typedef struct {
vec2i position;
bool cursor_image_set;
+ bool visible;
+ bool cursor_moved;
} gsr_cursor;
int gsr_cursor_init(gsr_cursor *self, gsr_egl *egl, Display *display);
void gsr_cursor_deinit(gsr_cursor *self);
-void gsr_cursor_update(gsr_cursor *self, XEvent *xev);
+/* Returns true if the cursor image has updated or if the cursor has moved */
+bool gsr_cursor_update(gsr_cursor *self, XEvent *xev);
void gsr_cursor_tick(gsr_cursor *self, Window relative_to);
#endif /* GSR_CURSOR_H */
diff --git a/include/egl.h b/include/egl.h
index afdb5a9..64dd2c6 100644
--- a/include/egl.h
+++ b/include/egl.h
@@ -110,6 +110,7 @@ typedef void(*__GLXextFuncPtr)(void);
#define GL_SRC_ALPHA 0x0302
#define GL_ONE_MINUS_SRC_ALPHA 0x0303
#define GL_DEBUG_OUTPUT 0x92E0
+#define GL_SCISSOR_TEST 0x0C11
#define GL_VENDOR 0x1F00
#define GL_RENDERER 0x1F01
@@ -270,11 +271,13 @@ struct gsr_egl {
void (*glEnableVertexAttribArray)(unsigned int index);
void (*glDrawArrays)(unsigned int mode, int first, int count);
void (*glEnable)(unsigned int cap);
+ void (*glDisable)(unsigned int cap);
void (*glBlendFunc)(unsigned int sfactor, unsigned int dfactor);
int (*glGetUniformLocation)(unsigned int program, const char *name);
void (*glUniform1f)(int location, float v0);
void (*glUniform2f)(int location, float v0, float v1);
void (*glDebugMessageCallback)(GLDEBUGPROC callback, const void *userParam);
+ void (*glScissor)(int x, int y, int width, int height);
};
bool gsr_egl_load(gsr_egl *self, Display *dpy, bool wayland, bool is_monitor_capture);
diff --git a/include/utils.h b/include/utils.h
index 74fdd59..c5d659a 100644
--- a/include/utils.h
+++ b/include/utils.h
@@ -37,10 +37,8 @@ gsr_monitor_rotation drm_monitor_get_display_server_rotation(const gsr_egl *egl,
bool gl_get_gpu_info(gsr_egl *egl, gsr_gpu_info *info);
/* |output| should be at least 128 bytes in size */
-bool gsr_get_valid_card_path(gsr_egl *egl, char *output);
+bool gsr_get_valid_card_path(gsr_egl *egl, char *output, bool is_monitor_capture);
/* |render_path| should be at least 128 bytes in size */
bool gsr_card_path_get_render_path(const char *card_path, char *render_path);
-int even_number_ceil(int value);
-
#endif /* GSR_UTILS_H */
diff --git a/install.sh b/install.sh
index 0833307..ab921fa 100755
--- a/install.sh
+++ b/install.sh
@@ -5,18 +5,10 @@ cd "$script_dir"
[ $(id -u) -ne 0 ] && echo "You need root privileges to run the install script" && exit 1
-./build.sh
-strip gsr-kms-server
-strip gpu-screen-recorder
+echo "Warning: this install.sh script is deprecated. Use meson directly instead if possible"
-install -Dm755 "gsr-kms-server" "/usr/bin/gsr-kms-server"
-install -Dm755 "gpu-screen-recorder" "/usr/bin/gpu-screen-recorder"
-if [ -d "/usr/lib/systemd/user" ]; then
- install -Dm644 "extra/gpu-screen-recorder.service" "/usr/lib/systemd/user/gpu-screen-recorder.service"
-fi
-# Not necessary, but removes the password prompt when trying to record a monitor on amd/intel or nvidia wayland
-setcap cap_sys_admin+ep /usr/bin/gsr-kms-server
-# Not necessary, but allows use of EGL_CONTEXT_PRIORITY_LEVEL_IMG which allows gpu screen recorder to run without being limited to game fps under heavy load on AMD/Intel
-setcap cap_sys_nice+ep /usr/bin/gpu-screen-recorder
+test -d build || meson setup build
+meson configure --prefix=/usr --buildtype=release -Dsystemd=true -Dstrip=true build
+ninja -C build install
echo "Successfully installed gpu-screen-recorder"
diff --git a/kms/client/kms_client.c b/kms/client/kms_client.c
index 1c8f634..869bf81 100644
--- a/kms/client/kms_client.c
+++ b/kms/client/kms_client.c
@@ -79,7 +79,7 @@ static int send_msg_to_server(int server_fd, gsr_kms_request *request) {
return sendmsg(server_fd, &response_message, 0);
}
-static int recv_msg_from_server(int server_fd, gsr_kms_response *response) {
+static int recv_msg_from_server(int server_pid, int server_fd, gsr_kms_response *response) {
struct iovec iov;
iov.iov_base = response;
iov.iov_len = sizeof(*response);
@@ -93,11 +93,26 @@ static int recv_msg_from_server(int server_fd, gsr_kms_response *response) {
response_message.msg_control = cmsgbuf;
response_message.msg_controllen = sizeof(cmsgbuf);
- int res = recvmsg(server_fd, &response_message, MSG_WAITALL);
- if(res <= 0)
- return res;
+ int res = 0;
+ for(;;) {
+ res = recvmsg(server_fd, &response_message, MSG_DONTWAIT);
+ if(res <= 0 && (errno == EAGAIN || errno == EWOULDBLOCK)) {
+ // If we are replacing the connection and closing the application at the same time
+ // then recvmsg can get stuck (because the server died), so we prevent that by doing
+ // non-blocking recvmsg and checking if the server died
+ int status = 0;
+ int wait_result = waitpid(server_pid, &status, WNOHANG);
+ if(wait_result != 0) {
+ res = -1;
+ break;
+ }
+ usleep(1000);
+ } else {
+ break;
+ }
+ }
- if(response->num_fds > 0) {
+ if(res > 0 && response->num_fds > 0) {
struct cmsghdr *cmsg = CMSG_FIRSTHDR(&response_message);
if(cmsg) {
int *fds = (int*)CMSG_DATA(cmsg);
@@ -371,7 +386,7 @@ int gsr_kms_client_replace_connection(gsr_kms_client *self) {
return -1;
}
- const int recv_res = recv_msg_from_server(self->socket_pair[GSR_SOCKET_PAIR_LOCAL], &response);
+ const int recv_res = recv_msg_from_server(self->kms_server_pid, self->socket_pair[GSR_SOCKET_PAIR_LOCAL], &response);
if(recv_res == 0) {
fprintf(stderr, "gsr warning: gsr_kms_client_replace_connection: kms server shut down\n");
return -1;
@@ -404,7 +419,7 @@ int gsr_kms_client_get_kms(gsr_kms_client *self, gsr_kms_response *response) {
return -1;
}
- const int recv_res = recv_msg_from_server(self->socket_pair[GSR_SOCKET_PAIR_LOCAL], response);
+ const int recv_res = recv_msg_from_server(self->kms_server_pid, self->socket_pair[GSR_SOCKET_PAIR_LOCAL], response);
if(recv_res == 0) {
fprintf(stderr, "gsr warning: gsr_kms_client_get_kms: kms server shut down\n");
strcpy(response->err_msg, "failed to receive");
diff --git a/meson.build b/meson.build
new file mode 100644
index 0000000..a188f16
--- /dev/null
+++ b/meson.build
@@ -0,0 +1,62 @@
+project('gpu-screen-recorder', ['c', 'cpp'], version : '3.8.0', default_options : ['warning_level=2'])
+
+add_project_arguments('-Wshadow', language : ['c', 'cpp'])
+if get_option('buildtype') == 'debug'
+ add_project_arguments('-g3', language : ['c', 'cpp'])
+elif get_option('buildtype') == 'release'
+ add_project_arguments('-DNDEBUG', language : ['c', 'cpp'])
+endif
+
+src = [
+ 'kms/client/kms_client.c',
+ 'src/capture/capture.c',
+ 'src/capture/nvfbc.c',
+ 'src/capture/xcomposite.c',
+ 'src/capture/xcomposite_cuda.c',
+ 'src/capture/xcomposite_vaapi.c',
+ 'src/capture/kms_vaapi.c',
+ 'src/capture/kms_cuda.c',
+ 'src/capture/kms.c',
+ 'src/egl.c',
+ 'src/cuda.c',
+ 'src/xnvctrl.c',
+ 'src/overclock.c',
+ 'src/window_texture.c',
+ 'src/shader.c',
+ 'src/color_conversion.c',
+ 'src/utils.c',
+ 'src/library_loader.c',
+ 'src/cursor.c',
+ 'src/sound.cpp',
+ 'src/main.cpp',
+]
+dep = [
+ dependency('libavcodec'),
+ dependency('libavformat'),
+ dependency('libavutil'),
+ dependency('x11'),
+ dependency('xcomposite'),
+ dependency('xrandr'),
+ dependency('xfixes'),
+ dependency('xdamage'),
+ dependency('xi'),
+ dependency('libpulse'),
+ dependency('libswresample'),
+ dependency('libavfilter'),
+ dependency('libva'),
+ dependency('libcap'),
+ dependency('libdrm'),
+ dependency('wayland-egl'),
+ dependency('wayland-client'),
+]
+
+executable('gsr-kms-server', 'kms/server/kms_server.c', dependencies : dependency('libdrm'), c_args : '-fstack-protector-all', install : true)
+executable('gpu-screen-recorder', src, dependencies : dep, install : true)
+
+if get_option('systemd') == true
+ install_data(files('extra/gpu-screen-recorder.service'), install_dir : '/usr/lib/systemd/user')
+endif
+
+if get_option('capabilities') == true
+ meson.add_install_script('extra/meson_post_install.sh')
+endif
diff --git a/meson_options.txt b/meson_options.txt
new file mode 100644
index 0000000..7286d14
--- /dev/null
+++ b/meson_options.txt
@@ -0,0 +1,2 @@
+option('systemd', type : 'boolean', value : false, description : 'Install systemd service file')
+option('capabilities', type : 'boolean', value : true, description : 'Set binary admin capabilities to remove password prompt and increase performance')
diff --git a/project.conf b/project.conf
index a046427..a7e2757 100644
--- a/project.conf
+++ b/project.conf
@@ -1,11 +1,11 @@
[package]
name = "gpu-screen-recorder"
type = "executable"
-version = "3.0.0"
+version = "3.8.0"
platforms = ["posix"]
[config]
-ignore_dirs = ["kms/server"]
+ignore_dirs = ["kms/server", "build"]
error_on_warning = "true"
[dependencies]
@@ -16,6 +16,8 @@ x11 = ">=1"
xcomposite = ">=0.2"
xrandr = ">=1"
xfixes = ">=2"
+xdamage = ">=1"
+xi = ">=1"
libpulse = ">=13"
libswresample = ">=3"
libavfilter = ">=5"
@@ -23,4 +25,4 @@ libva = ">=1"
libcap = ">=2"
libdrm = ">=2"
wayland-egl = ">=15"
-wayland-client = ">=1"
+wayland-client = ">=1" \ No newline at end of file
diff --git a/scripts/record-application-name.sh b/scripts/record-application-name.sh
index 0411781..cc29255 100755
--- a/scripts/record-application-name.sh
+++ b/scripts/record-application-name.sh
@@ -1,6 +1,6 @@
#!/bin/sh
window=$(xdotool selectwindow)
-window_name=$(xdotool getwindowclassname "$window" || xdotool getwindowname "$window" || echo "game")
+window_name=$(xdotool getwindowclassname "$window" || xdotool getwindowname "$window" || echo "Game")
window_name="$(echo "$window_name" | tr '/\\' '_')"
gpu-screen-recorder -w "$window" -f 60 -a "$(pactl get-default-sink).monitor" -o "$HOME/Videos/recording/$window_name/$(date +"Video_%Y-%m-%d_%H-%M-%S.mp4")"
diff --git a/scripts/record-save-application-name.sh b/scripts/record-save-application-name.sh
index c002376..46c51f0 100755
--- a/scripts/record-save-application-name.sh
+++ b/scripts/record-save-application-name.sh
@@ -4,7 +4,7 @@
# gpu-screen-recorder -w screen -f 60 -a "$(pactl get-default-sink).monitor" -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 getwindowclassname "$window" || xdotool getwindowname "$window" || echo "Game")
window_name="$(echo "$window_name" | tr '/\\' '_')"
video_dir="$HOME/Videos/Replays/$window_name"
diff --git a/scripts/replay-application-name.sh b/scripts/replay-application-name.sh
index 7683abb..18df61a 100755
--- a/scripts/replay-application-name.sh
+++ b/scripts/replay-application-name.sh
@@ -1,6 +1,6 @@
#!/bin/sh
window=$(xdotool selectwindow)
-window_name=$(xdotool getwindowclassname "$window" || xdotool getwindowname "$window" || echo "game")
+window_name=$(xdotool getwindowclassname "$window" || xdotool getwindowname "$window" || echo "Game")
window_name="$(echo "$window_name" | tr '/\\' '_')"
gpu-screen-recorder -w "$window" -f 60 -c mkv -a "$(pactl get-default-sink).monitor" -r 60 -o "$HOME/Videos/Replays/$window_name"
diff --git a/scripts/twitch-stream-local-copy.sh b/scripts/twitch-stream-local-copy.sh
index dba9d15..4a678e8 100755
--- a/scripts/twitch-stream-local-copy.sh
+++ b/scripts/twitch-stream-local-copy.sh
@@ -4,4 +4,4 @@
[ "$#" -ne 4 ] && echo "usage: twitch-stream-local-copy.sh <window_id> <fps> <livestream_key> <local_file>" && exit 1
active_sink="$(pactl get-default-sink).monitor"
-gpu-screen-recorder -w "$1" -c flv -f "$2" -a "$active_sink" | tee -- "$4" | ffmpeg -i pipe:0 -c copy -f flv -- "rtmp://live.twitch.tv/app/$3"
+gpu-screen-recorder -w "$1" -c flv -f "$2" -q high -a "$active_sink" | tee -- "$4" | ffmpeg -i pipe:0 -c copy -f flv -- "rtmp://live.twitch.tv/app/$3"
diff --git a/scripts/twitch-stream.sh b/scripts/twitch-stream.sh
index cd4737a..aaa5828 100755
--- a/scripts/twitch-stream.sh
+++ b/scripts/twitch-stream.sh
@@ -2,4 +2,4 @@
[ "$#" -ne 3 ] && echo "usage: twitch-stream.sh <window_id> <fps> <livestream_key>" && exit 1
active_sink="$(pactl get-default-sink).monitor"
-gpu-screen-recorder -w "$1" -c flv -f "$2" -a "$active_sink" -o "rtmp://live.twitch.tv/app/$3"
+gpu-screen-recorder -w "$1" -c flv -f "$2" -q high -a "$active_sink" -o "rtmp://live.twitch.tv/app/$3"
diff --git a/scripts/youtube-hls-stream.sh b/scripts/youtube-hls-stream.sh
index 21619af..2f1659e 100755
--- a/scripts/youtube-hls-stream.sh
+++ b/scripts/youtube-hls-stream.sh
@@ -1,11 +1,5 @@
#!/bin/sh
[ "$#" -ne 3 ] && echo "usage: youtube-hls-stream.sh <window_id> <fps> <livestream_key>" && exit 1
-mkdir "youtube_stream"
-cd "youtube_stream"
active_sink="$(pactl get-default-sink).monitor"
-gpu-screen-recorder -w "$1" -c mpegts -f "$2" -a "$active_sink" | ffmpeg -i pipe:0 -c copy -f hls \
- -hls_time 2 -hls_flags independent_segments -hls_flags delete_segments -hls_segment_type mpegts -hls_segment_filename stream%02d.ts -master_pl_name stream.m3u8 out1 &
-echo "Waiting until stream segments are created..."
-sleep 10
-ffmpeg -i stream.m3u8 -c copy -- "https://a.upload.youtube.com/http_upload_hls?cid=$3&copy=0&file=stream.m3u8"
+gpu-screen-recorder -w "$1" -c hls -f "$2" -q high -a "$active_sink" -ac aac -o "https://a.upload.youtube.com/http_upload_hls?cid=$3&copy=0&file=stream.m3u8" \ No newline at end of file
diff --git a/src/capture/capture.c b/src/capture/capture.c
index 670114e..5e1f546 100644
--- a/src/capture/capture.c
+++ b/src/capture/capture.c
@@ -11,9 +11,6 @@
#include <libavutil/hwcontext_cuda.h>
#include <libavcodec/avcodec.h>
-#define FOURCC_NV12 842094158
-#define FOURCC_P010 808530000
-
int gsr_capture_start(gsr_capture *cap, AVCodecContext *video_codec_context, AVFrame *frame) {
if(cap->started)
return -1;
@@ -110,8 +107,8 @@ bool gsr_capture_base_setup_vaapi_textures(gsr_capture_base *self, AVFrame *fram
const uint32_t formats_nv12[2] = { fourcc('R', '8', ' ', ' '), fourcc('G', 'R', '8', '8') };
const uint32_t formats_p010[2] = { fourcc('R', '1', '6', ' '), fourcc('G', 'R', '3', '2') };
- if(prime->fourcc == FOURCC_NV12 || prime->fourcc == FOURCC_P010) {
- const uint32_t *formats = prime->fourcc == FOURCC_NV12 ? formats_nv12 : formats_p010;
+ if(prime->fourcc == VA_FOURCC_NV12 || prime->fourcc == VA_FOURCC_P010) {
+ const uint32_t *formats = prime->fourcc == VA_FOURCC_NV12 ? formats_nv12 : formats_p010;
const int div[2] = {1, 2}; // divide UV texture size by 2 because chroma is half size
self->egl->glGenTextures(2, self->target_textures);
@@ -119,8 +116,7 @@ bool gsr_capture_base_setup_vaapi_textures(gsr_capture_base *self, AVFrame *fram
const int layer = i;
const int plane = 0;
- //const uint64_t modifier = prime->objects[prime->layers[layer].object_index[plane]].drm_format_modifier;
-
+ const uint64_t modifier = prime->objects[prime->layers[layer].object_index[plane]].drm_format_modifier;
const intptr_t img_attr[] = {
EGL_LINUX_DRM_FOURCC_EXT, formats[i],
EGL_WIDTH, prime->width / div[i],
@@ -128,9 +124,8 @@ bool gsr_capture_base_setup_vaapi_textures(gsr_capture_base *self, AVFrame *fram
EGL_DMA_BUF_PLANE0_FD_EXT, prime->objects[prime->layers[layer].object_index[plane]].fd,
EGL_DMA_BUF_PLANE0_OFFSET_EXT, prime->layers[layer].offset[plane],
EGL_DMA_BUF_PLANE0_PITCH_EXT, prime->layers[layer].pitch[plane],
- // TODO:
- //EGL_DMA_BUF_PLANE0_MODIFIER_LO_EXT, modifier & 0xFFFFFFFFULL,
- //EGL_DMA_BUF_PLANE0_MODIFIER_HI_EXT, modifier >> 32ULL,
+ EGL_DMA_BUF_PLANE0_MODIFIER_LO_EXT, modifier & 0xFFFFFFFFULL,
+ EGL_DMA_BUF_PLANE0_MODIFIER_HI_EXT, modifier >> 32ULL,
EGL_NONE
};
@@ -166,7 +161,7 @@ bool gsr_capture_base_setup_vaapi_textures(gsr_capture_base *self, AVFrame *fram
color_conversion_params.color_range = color_range;
color_conversion_params.egl = self->egl;
color_conversion_params.source_color = GSR_SOURCE_COLOR_RGB;
- if(prime->fourcc == FOURCC_NV12)
+ if(prime->fourcc == VA_FOURCC_NV12)
color_conversion_params.destination_color = GSR_DESTINATION_COLOR_NV12;
else
color_conversion_params.destination_color = GSR_DESTINATION_COLOR_P010;
diff --git a/src/capture/kms.c b/src/capture/kms.c
index 16b20b7..ec83cab 100644
--- a/src/capture/kms.c
+++ b/src/capture/kms.c
@@ -10,6 +10,10 @@
#define HDMI_STATIC_METADATA_TYPE1 0
#define HDMI_EOTF_SMPTE_ST2084 2
+static int max_int(int a, int b) {
+ return a > b ? a : b;
+}
+
/* TODO: On monitor reconfiguration, find monitor x, y, width and height again. Do the same for nvfbc. */
typedef struct {
@@ -35,10 +39,6 @@ static void monitor_callback(const gsr_monitor *monitor, void *userdata) {
fprintf(stderr, "gsr warning: reached max connector ids\n");
}
-static int max_int(int a, int b) {
- return a > b ? a : b;
-}
-
int gsr_capture_kms_start(gsr_capture_kms *self, const char *display_to_capture, gsr_egl *egl, AVCodecContext *video_codec_context, AVFrame *frame) {
memset(self, 0, sizeof(*self));
self->base.video_codec_context = video_codec_context;
@@ -77,8 +77,25 @@ int gsr_capture_kms_start(gsr_capture_kms *self, const char *display_to_capture,
/* Disable vsync */
egl->eglSwapInterval(egl->egl_display, 0);
- self->base.video_codec_context->width = max_int(2, even_number_ceil(self->capture_size.x));
- self->base.video_codec_context->height = max_int(2, even_number_ceil(self->capture_size.y));
+ // TODO: Move this and xcomposite equivalent to a common section unrelated to capture method
+ if(egl->gpu_info.vendor == GSR_GPU_VENDOR_AMD && video_codec_context->codec_id == AV_CODEC_ID_HEVC) {
+ // TODO: dont do this if using ffmpeg reports that this is not needed (AMD driver bug that was fixed recently)
+ self->base.video_codec_context->width = FFALIGN(self->capture_size.x, 64);
+ self->base.video_codec_context->height = FFALIGN(self->capture_size.y, 16);
+ } else if(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
+ self->base.video_codec_context->width = FFALIGN(self->capture_size.x, 64);
+ // AMD driver has special case handling for 1080 height to set it to 1082 instead of 1088 (1080 aligned to 16).
+ // TODO: Set height to 1082 in this case, but it wont work because it will be aligned to 1088.
+ if(self->capture_size.y == 1080) {
+ self->base.video_codec_context->height = 1080;
+ } else {
+ self->base.video_codec_context->height = FFALIGN(self->capture_size.y, 16);
+ }
+ } else {
+ self->base.video_codec_context->width = FFALIGN(self->capture_size.x, 2);
+ self->base.video_codec_context->height = FFALIGN(self->capture_size.y, 2);
+ }
frame->width = self->base.video_codec_context->width;
frame->height = self->base.video_codec_context->height;
@@ -295,8 +312,11 @@ bool gsr_capture_kms_capture(gsr_capture_kms *self, AVFrame *frame, bool hdr, bo
const float texture_rotation = monitor_rotation_to_radians(self->monitor_rotation);
+ const int target_x = max_int(0, frame->width / 2 - self->capture_size.x / 2);
+ const int target_y = max_int(0, frame->height / 2 - self->capture_size.y / 2);
+
gsr_color_conversion_draw(&self->base.color_conversion, self->base.input_texture,
- (vec2i){0, 0}, self->capture_size,
+ (vec2i){target_x, target_y}, self->capture_size,
capture_pos, self->capture_size,
texture_rotation, false);
@@ -327,6 +347,9 @@ bool gsr_capture_kms_capture(gsr_capture_kms *self, AVFrame *frame, bool hdr, bo
break;
}
+ cursor_pos.x += target_x;
+ cursor_pos.y += target_y;
+
const intptr_t img_attr_cursor[] = {
EGL_LINUX_DRM_FOURCC_EXT, cursor_drm_fd->pixel_format,
EGL_WIDTH, cursor_drm_fd->width,
@@ -346,10 +369,15 @@ bool gsr_capture_kms_capture(gsr_capture_kms *self, AVFrame *frame, bool hdr, bo
self->base.egl->eglDestroyImage(self->base.egl->egl_display, cursor_image);
self->base.egl->glBindTexture(target, 0);
+ self->base.egl->glEnable(GL_SCISSOR_TEST);
+ self->base.egl->glScissor(target_x, target_y, self->capture_size.x, self->capture_size.y);
+
gsr_color_conversion_draw(&self->base.color_conversion, self->base.cursor_texture,
cursor_pos, cursor_size,
(vec2i){0, 0}, cursor_size,
texture_rotation, cursor_texture_is_external);
+
+ self->base.egl->glDisable(GL_SCISSOR_TEST);
}
self->base.egl->eglSwapBuffers(self->base.egl->egl_display, self->base.egl->egl_surface);
diff --git a/src/capture/kms_vaapi.c b/src/capture/kms_vaapi.c
index a7e8182..b9c9ee5 100644
--- a/src/capture/kms_vaapi.c
+++ b/src/capture/kms_vaapi.c
@@ -57,7 +57,7 @@ static bool gsr_capture_kms_vaapi_should_stop(gsr_capture *cap, bool *err) {
static int gsr_capture_kms_vaapi_capture(gsr_capture *cap, AVFrame *frame) {
gsr_capture_kms_vaapi *cap_kms = cap->priv;
- gsr_capture_kms_capture(&cap_kms->kms, frame, cap_kms->params.hdr, false, false, cap_kms->params.record_cursor);
+ gsr_capture_kms_capture(&cap_kms->kms, frame, cap_kms->params.hdr, cap_kms->params.egl->gpu_info.vendor == GSR_GPU_VENDOR_INTEL, false, cap_kms->params.record_cursor);
return 0;
}
diff --git a/src/capture/xcomposite.c b/src/capture/xcomposite.c
index 29b42d5..3240ed8 100644
--- a/src/capture/xcomposite.c
+++ b/src/capture/xcomposite.c
@@ -6,6 +6,7 @@
#include <unistd.h>
#include <assert.h>
#include <X11/Xlib.h>
+#include <X11/extensions/Xdamage.h>
#include <libavutil/hwcontext.h>
#include <libavutil/hwcontext.h>
#include <libavutil/frame.h>
@@ -17,10 +18,6 @@ static int max_int(int a, int b) {
return a > b ? a : b;
}
-static int min_int(int a, int b) {
- return a < b ? a : b;
-}
-
void gsr_capture_xcomposite_init(gsr_capture_xcomposite *self, const gsr_capture_xcomposite_params *params) {
memset(self, 0, sizeof(*self));
self->params = *params;
@@ -40,6 +37,23 @@ static Window get_focused_window(Display *display, Atom net_active_window_atom)
return None;
}
+static void gsr_capture_xcomposite_setup_damage(gsr_capture_xcomposite *self, Window window) {
+ if(self->damage_event == 0)
+ return;
+
+ if(self->damage) {
+ XDamageDestroy(self->params.egl->x11.dpy, self->damage);
+ self->damage = None;
+ }
+
+ self->damage = XDamageCreate(self->params.egl->x11.dpy, window, XDamageReportNonEmpty);
+ if(self->damage) {
+ XDamageSubtract(self->params.egl->x11.dpy, self->damage, None, None);
+ } else {
+ fprintf(stderr, "gsr warning: gsr_capture_xcomposite_setup_damage: XDamageCreate failed\n");
+ }
+}
+
int gsr_capture_xcomposite_start(gsr_capture_xcomposite *self, AVCodecContext *video_codec_context, AVFrame *frame) {
self->base.video_codec_context = video_codec_context;
self->base.egl = self->params.egl;
@@ -55,6 +69,20 @@ int gsr_capture_xcomposite_start(gsr_capture_xcomposite *self, AVCodecContext *v
self->window = self->params.window;
}
+ if(self->params.track_damage) {
+ if(!XDamageQueryExtension(self->params.egl->x11.dpy, &self->damage_event, &self->damage_error)) {
+ fprintf(stderr, "gsr warning: gsr_capture_xcomposite_start: XDamage is not supported by your X11 server\n");
+ self->damage_event = 0;
+ self->damage_error = 0;
+ }
+ } else {
+ self->damage_event = 0;
+ self->damage_error = 0;
+ }
+
+ self->damaged = true;
+ gsr_capture_xcomposite_setup_damage(self, self->window);
+
/* TODO: Do these in tick, and allow error if follow_focused */
XWindowAttributes attr;
@@ -102,32 +130,50 @@ int gsr_capture_xcomposite_start(gsr_capture_xcomposite *self, AVCodecContext *v
self->params.egl->glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_HEIGHT, &self->texture_size.y);
self->params.egl->glBindTexture(GL_TEXTURE_2D, 0);
- self->texture_size.x = max_int(2, even_number_ceil(self->texture_size.x));
- self->texture_size.y = max_int(2, even_number_ceil(self->texture_size.y));
-
- video_codec_context->width = self->texture_size.x;
- video_codec_context->height = self->texture_size.y;
-
- if(self->params.region_size.x > 0 && self->params.region_size.y > 0) {
- video_codec_context->width = max_int(2, even_number_ceil(self->params.region_size.x));
- video_codec_context->height = max_int(2, even_number_ceil(self->params.region_size.y));
+ 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;
+
+ if(self->params.egl->gpu_info.vendor == GSR_GPU_VENDOR_AMD && video_codec_context->codec_id == AV_CODEC_ID_HEVC) {
+ // TODO: dont do this if using ffmpeg reports that this is not needed (AMD driver bug that was fixed recently)
+ video_codec_context->width = FFALIGN(video_size.x, 64);
+ video_codec_context->height = FFALIGN(video_size.y, 16);
+ } else if(self->params.egl->gpu_info.vendor == GSR_GPU_VENDOR_AMD && video_codec_context->codec_id == AV_CODEC_ID_AV1) {
+ // TODO: Dont do this for VCN 5 and forward which should fix this hardware bug
+ video_codec_context->width = FFALIGN(video_size.x, 64);
+ // AMD driver has special case handling for 1080 height to set it to 1082 instead of 1088 (1080 aligned to 16).
+ // TODO: Set height to 1082 in this case, but it wont work because it will be aligned to 1088.
+ if(video_size.y == 1080) {
+ video_codec_context->height = 1080;
+ } else {
+ video_codec_context->height = FFALIGN(video_size.y, 16);
+ }
+ } else {
+ video_codec_context->width = FFALIGN(video_size.x, 2);
+ video_codec_context->height = FFALIGN(video_size.y, 2);
}
frame->width = video_codec_context->width;
frame->height = video_codec_context->height;
self->window_resize_timer = clock_get_monotonic_seconds();
- self->clear_next_frame = true;
return 0;
}
void gsr_capture_xcomposite_stop(gsr_capture_xcomposite *self) {
+ if(self->damage) {
+ XDamageDestroy(self->params.egl->x11.dpy, self->damage);
+ self->damage = None;
+ }
+
window_texture_deinit(&self->window_texture);
gsr_cursor_deinit(&self->cursor);
gsr_capture_base_stop(&self->base);
}
void gsr_capture_xcomposite_tick(gsr_capture_xcomposite *self, AVCodecContext *video_codec_context) {
+ (void)video_codec_context;
//self->params.egl->glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
self->params.egl->glClear(0);
@@ -171,7 +217,20 @@ void gsr_capture_xcomposite_tick(gsr_capture_xcomposite *self, AVCodecContext *v
}
}
- gsr_cursor_update(&self->cursor, &self->xev);
+ if(self->damage_event && self->xev.type == self->damage_event + XDamageNotify) {
+ XDamageNotifyEvent *de = (XDamageNotifyEvent*)&self->xev;
+ XserverRegion region = XFixesCreateRegion(self->params.egl->x11.dpy, NULL, 0);
+ // Subtract all the damage, repairing the window
+ XDamageSubtract(self->params.egl->x11.dpy, de->damage, None, region);
+ XFixesDestroyRegion(self->params.egl->x11.dpy, region);
+ self->damaged = true;
+ }
+
+ if(gsr_cursor_update(&self->cursor, &self->xev)) {
+ if(self->params.record_cursor && self->cursor.visible) {
+ self->damaged = true;
+ }
+ }
}
if(self->params.follow_focused && !self->follow_focused_initialized) {
@@ -198,6 +257,7 @@ void gsr_capture_xcomposite_tick(gsr_capture_xcomposite *self, AVCodecContext *v
window_texture_deinit(&self->window_texture);
window_texture_init(&self->window_texture, self->params.egl->x11.dpy, self->window, self->params.egl); // TODO: Do not do the below window_texture_on_resize after this
+ gsr_capture_xcomposite_setup_damage(self, self->window);
}
}
@@ -220,13 +280,19 @@ void gsr_capture_xcomposite_tick(gsr_capture_xcomposite *self, AVCodecContext *v
self->params.egl->glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_HEIGHT, &self->texture_size.y);
self->params.egl->glBindTexture(GL_TEXTURE_2D, 0);
- self->texture_size.x = min_int(video_codec_context->width, max_int(2, even_number_ceil(self->texture_size.x)));
- self->texture_size.y = min_int(video_codec_context->height, max_int(2, even_number_ceil(self->texture_size.y)));
-
gsr_color_conversion_clear(&self->base.color_conversion);
+ gsr_capture_xcomposite_setup_damage(self, self->window);
}
}
+bool gsr_capture_xcomposite_is_damaged(gsr_capture_xcomposite *self) {
+ return self->damage_event ? self->damaged : true;
+}
+
+void gsr_capture_xcomposite_clear_damage(gsr_capture_xcomposite *self) {
+ self->damaged = false;
+}
+
bool gsr_capture_xcomposite_should_stop(gsr_capture_xcomposite *self, bool *err) {
if(self->should_stop) {
if(err)
@@ -245,50 +311,36 @@ int gsr_capture_xcomposite_capture(gsr_capture_xcomposite *self, AVFrame *frame)
const int target_x = max_int(0, frame->width / 2 - self->texture_size.x / 2);
const int target_y = max_int(0, frame->height / 2 - self->texture_size.y / 2);
- // TODO: Can we do this a better way than to call it every capture?
- if(self->params.record_cursor)
- gsr_cursor_tick(&self->cursor, self->window);
-
const vec2i cursor_pos = {
target_x + self->cursor.position.x - self->cursor.hotspot.x,
target_y + self->cursor.position.y - self->cursor.hotspot.y
};
- const bool cursor_completely_inside_window =
- cursor_pos.x >= target_x &&
- cursor_pos.x + self->cursor.size.x <= target_x + self->texture_size.x &&
- cursor_pos.y >= target_y &&
- cursor_pos.y + self->cursor.size.y <= target_y + self->texture_size.y;
-
- const bool cursor_inside_window =
- cursor_pos.x + self->cursor.size.x >= target_x &&
- cursor_pos.x <= target_x + self->texture_size.x &&
- cursor_pos.y + self->cursor.size.y >= target_y &&
- cursor_pos.y <= target_y + self->texture_size.y;
-
- if(self->clear_next_frame) {
- self->clear_next_frame = false;
- gsr_color_conversion_clear(&self->base.color_conversion);
- }
-
- /*
- We dont draw the cursor if it's outside the window but if it's partially inside the window then the cursor area that is outside the window
- will not get overdrawn the next frame causing a cursor trail to be visible since we dont clear the background.
- To fix this we detect if the cursor is partially inside the window and clear the background only in that case.
- */
- if(!cursor_completely_inside_window && cursor_inside_window && self->params.record_cursor)
- self->clear_next_frame = true;
-
gsr_color_conversion_draw(&self->base.color_conversion, window_texture_get_opengl_texture_id(&self->window_texture),
(vec2i){target_x, target_y}, self->texture_size,
(vec2i){0, 0}, self->texture_size,
0.0f, false);
- if(cursor_inside_window && self->params.record_cursor) {
- gsr_color_conversion_draw(&self->base.color_conversion, self->cursor.texture_id,
- cursor_pos, self->cursor.size,
- (vec2i){0, 0}, self->cursor.size,
- 0.0f, false);
+ if(self->params.record_cursor && self->cursor.visible) {
+ gsr_cursor_tick(&self->cursor, self->window);
+
+ const bool cursor_inside_window =
+ cursor_pos.x + self->cursor.size.x >= target_x &&
+ cursor_pos.x <= target_x + self->texture_size.x &&
+ cursor_pos.y + self->cursor.size.y >= target_y &&
+ cursor_pos.y <= target_y + self->texture_size.y;
+
+ if(cursor_inside_window) {
+ self->base.egl->glEnable(GL_SCISSOR_TEST);
+ self->base.egl->glScissor(target_x, target_y, self->texture_size.x, self->texture_size.y);
+
+ gsr_color_conversion_draw(&self->base.color_conversion, self->cursor.texture_id,
+ cursor_pos, self->cursor.size,
+ (vec2i){0, 0}, self->cursor.size,
+ 0.0f, false);
+
+ self->base.egl->glDisable(GL_SCISSOR_TEST);
+ }
}
self->params.egl->eglSwapBuffers(self->params.egl->egl_display, self->params.egl->egl_surface);
diff --git a/src/capture/xcomposite_cuda.c b/src/capture/xcomposite_cuda.c
index 6e13d2a..c436221 100644
--- a/src/capture/xcomposite_cuda.c
+++ b/src/capture/xcomposite_cuda.c
@@ -76,6 +76,16 @@ static void gsr_capture_xcomposite_cuda_tick(gsr_capture *cap, AVCodecContext *v
gsr_capture_xcomposite_tick(&cap_xcomp->xcomposite, video_codec_context);
}
+static bool gsr_capture_xcomposite_cuda_is_damaged(gsr_capture *cap) {
+ gsr_capture_xcomposite_cuda *cap_xcomp = cap->priv;
+ return gsr_capture_xcomposite_is_damaged(&cap_xcomp->xcomposite);
+}
+
+static void gsr_capture_xcomposite_cuda_clear_damage(gsr_capture *cap) {
+ gsr_capture_xcomposite_cuda *cap_xcomp = cap->priv;
+ gsr_capture_xcomposite_clear_damage(&cap_xcomp->xcomposite);
+}
+
static bool gsr_capture_xcomposite_cuda_should_stop(gsr_capture *cap, bool *err) {
gsr_capture_xcomposite_cuda *cap_xcomp = cap->priv;
return gsr_capture_xcomposite_should_stop(&cap_xcomp->xcomposite, err);
@@ -144,6 +154,8 @@ gsr_capture* gsr_capture_xcomposite_cuda_create(const gsr_capture_xcomposite_cud
*cap = (gsr_capture) {
.start = gsr_capture_xcomposite_cuda_start,
.tick = gsr_capture_xcomposite_cuda_tick,
+ .is_damaged = gsr_capture_xcomposite_cuda_is_damaged,
+ .clear_damage = gsr_capture_xcomposite_cuda_clear_damage,
.should_stop = gsr_capture_xcomposite_cuda_should_stop,
.capture = gsr_capture_xcomposite_cuda_capture,
.capture_end = NULL,
diff --git a/src/capture/xcomposite_vaapi.c b/src/capture/xcomposite_vaapi.c
index 8c9c56c..3f27014 100644
--- a/src/capture/xcomposite_vaapi.c
+++ b/src/capture/xcomposite_vaapi.c
@@ -43,6 +43,16 @@ static void gsr_capture_xcomposite_vaapi_tick(gsr_capture *cap, AVCodecContext *
gsr_capture_xcomposite_tick(&cap_xcomp->xcomposite, video_codec_context);
}
+static bool gsr_capture_xcomposite_vaapi_is_damaged(gsr_capture *cap) {
+ gsr_capture_xcomposite_vaapi *cap_xcomp = cap->priv;
+ return gsr_capture_xcomposite_is_damaged(&cap_xcomp->xcomposite);
+}
+
+static void gsr_capture_xcomposite_vaapi_clear_damage(gsr_capture *cap) {
+ gsr_capture_xcomposite_vaapi *cap_xcomp = cap->priv;
+ gsr_capture_xcomposite_clear_damage(&cap_xcomp->xcomposite);
+}
+
static bool gsr_capture_xcomposite_vaapi_should_stop(gsr_capture *cap, bool *err) {
gsr_capture_xcomposite_vaapi *cap_xcomp = cap->priv;
return gsr_capture_xcomposite_should_stop(&cap_xcomp->xcomposite, err);
@@ -98,6 +108,8 @@ gsr_capture* gsr_capture_xcomposite_vaapi_create(const gsr_capture_xcomposite_va
*cap = (gsr_capture) {
.start = gsr_capture_xcomposite_vaapi_start,
.tick = gsr_capture_xcomposite_vaapi_tick,
+ .is_damaged = gsr_capture_xcomposite_vaapi_is_damaged,
+ .clear_damage = gsr_capture_xcomposite_vaapi_clear_damage,
.should_stop = gsr_capture_xcomposite_vaapi_should_stop,
.capture = gsr_capture_xcomposite_vaapi_capture,
.capture_end = NULL,
diff --git a/src/cursor.c b/src/cursor.c
index 737c33b..9825ad2 100644
--- a/src/cursor.c
+++ b/src/cursor.c
@@ -6,10 +6,15 @@
#include <assert.h>
#include <X11/extensions/Xfixes.h>
+#include <X11/extensions/XI2.h>
+#include <X11/extensions/XInput2.h>
-static bool gsr_cursor_set_from_x11_cursor_image(gsr_cursor *self, XFixesCursorImage *x11_cursor_image) {
+// TODO: Test cursor visibility with XFixesHideCursor
+
+static bool gsr_cursor_set_from_x11_cursor_image(gsr_cursor *self, XFixesCursorImage *x11_cursor_image, bool *visible) {
uint8_t *cursor_data = NULL;
uint8_t *out = NULL;
+ *visible = false;
if(!x11_cursor_image)
goto err;
@@ -34,8 +39,11 @@ static bool gsr_cursor_set_from_x11_cursor_image(gsr_cursor *self, XFixesCursorI
uint32_t pixel = *pixels++;
uint8_t *in = (uint8_t*)&pixel;
uint8_t alpha = in[3];
- if(alpha == 0)
+ if(alpha == 0) {
alpha = 1;
+ } else {
+ *visible = true;
+ }
*out++ = (unsigned)*in++ * 255/alpha;
*out++ = (unsigned)*in++ * 255/alpha;
@@ -63,6 +71,26 @@ static bool gsr_cursor_set_from_x11_cursor_image(gsr_cursor *self, XFixesCursorI
return false;
}
+static bool xinput_is_supported(Display *dpy, int *xi_opcode) {
+ *xi_opcode = 0;
+ int query_event = 0;
+ int query_error = 0;
+ if(!XQueryExtension(dpy, "XInputExtension", xi_opcode, &query_event, &query_error)) {
+ fprintf(stderr, "gsr error: gsr_cursor_init: X Input extension not available\n");
+ return false;
+ }
+
+ int major = 2;
+ int minor = 1;
+ int retval = XIQueryVersion(dpy, &major, &minor);
+ if (retval != Success) {
+ fprintf(stderr, "gsr error: gsr_cursor_init: XInput 2.1 is not supported\n");
+ return false;
+ }
+
+ return true;
+}
+
int gsr_cursor_init(gsr_cursor *self, gsr_egl *egl, Display *display) {
int x_fixes_error_base = 0;
@@ -79,11 +107,31 @@ int gsr_cursor_init(gsr_cursor *self, gsr_egl *egl, Display *display) {
return -1;
}
+ if(!xinput_is_supported(self->display, &self->xi_opcode)) {
+ gsr_cursor_deinit(self);
+ return -1;
+ }
+
+ unsigned char mask[XIMaskLen(XI_LASTEVENT)];
+ memset(mask, 0, sizeof(mask));
+ XISetMask(mask, XI_RawMotion);
+
+ XIEventMask xi_masks;
+ xi_masks.deviceid = XIAllMasterDevices;
+ xi_masks.mask_len = sizeof(mask);
+ xi_masks.mask = mask;
+ if(XISelectEvents(self->display, DefaultRootWindow(self->display), &xi_masks, 1) != Success) {
+ fprintf(stderr, "gsr error: gsr_cursor_init: XISelectEvents failed\n");
+ gsr_cursor_deinit(self);
+ return -1;
+ }
+
self->egl->glGenTextures(1, &self->texture_id);
XFixesSelectCursorInput(self->display, DefaultRootWindow(self->display), XFixesDisplayCursorNotifyMask);
- gsr_cursor_set_from_x11_cursor_image(self, XFixesGetCursorImage(self->display));
+ gsr_cursor_set_from_x11_cursor_image(self, XFixesGetCursorImage(self->display), &self->visible);
self->cursor_image_set = true;
+ self->cursor_moved = true;
return 0;
}
@@ -97,29 +145,46 @@ void gsr_cursor_deinit(gsr_cursor *self) {
self->texture_id = 0;
}
+ XISelectEvents(self->display, DefaultRootWindow(self->display), NULL, 0);
XFixesSelectCursorInput(self->display, DefaultRootWindow(self->display), 0);
self->display = NULL;
self->egl = NULL;
}
-void gsr_cursor_update(gsr_cursor *self, XEvent *xev) {
+bool gsr_cursor_update(gsr_cursor *self, XEvent *xev) {
+ bool updated = false;
+ XGenericEventCookie *cookie = (XGenericEventCookie*)&xev->xcookie;
+ const Bool got_event_data = XGetEventData(self->display, cookie);
+ if(got_event_data && cookie->type == GenericEvent && cookie->extension == self->xi_opcode && cookie->evtype == XI_RawMotion) {
+ updated = true;
+ self->cursor_moved = true;
+ }
+ if(got_event_data)
+ XFreeEventData(self->display, cookie);
+
if(xev->type == self->x_fixes_event_base + XFixesCursorNotify) {
XFixesCursorNotifyEvent *cursor_notify_event = (XFixesCursorNotifyEvent*)xev;
if(cursor_notify_event->subtype == XFixesDisplayCursorNotify && cursor_notify_event->window == DefaultRootWindow(self->display)) {
- self->cursor_image_set = true;
- gsr_cursor_set_from_x11_cursor_image(self, XFixesGetCursorImage(self->display));
+ self->cursor_image_set = false;
}
}
if(!self->cursor_image_set) {
self->cursor_image_set = true;
- gsr_cursor_set_from_x11_cursor_image(self, XFixesGetCursorImage(self->display));
+ gsr_cursor_set_from_x11_cursor_image(self, XFixesGetCursorImage(self->display), &self->visible);
+ updated = true;
}
+
+ return updated;
}
void gsr_cursor_tick(gsr_cursor *self, Window relative_to) {
- /* TODO: Use XInput2 instead. However that doesn't work when the pointer is grabbed. Maybe check for focused window change and XSelectInput PointerMask */
+ if(!self->cursor_moved)
+ return;
+
+ self->cursor_moved = false;
+
Window dummy_window;
int dummy_i;
unsigned int dummy_u;
diff --git a/src/egl.c b/src/egl.c
index 2ad4f85..552d5f4 100644
--- a/src/egl.c
+++ b/src/egl.c
@@ -328,7 +328,7 @@ static bool gsr_egl_switch_to_glx_context(gsr_egl *self) {
}
static bool gsr_egl_load_egl(gsr_egl *self, void *library) {
- dlsym_assign required_dlsym[] = {
+ const dlsym_assign required_dlsym[] = {
{ (void**)&self->eglGetError, "eglGetError" },
{ (void**)&self->eglGetDisplay, "eglGetDisplay" },
{ (void**)&self->eglInitialize, "eglInitialize" },
@@ -373,7 +373,7 @@ static bool gsr_egl_proc_load_egl(gsr_egl *self) {
}
static bool gsr_egl_load_glx(gsr_egl *self, void *library) {
- dlsym_assign required_dlsym[] = {
+ const dlsym_assign required_dlsym[] = {
{ (void**)&self->glXGetProcAddress, "glXGetProcAddress" },
{ (void**)&self->glXChooseFBConfig, "glXChooseFBConfig" },
{ (void**)&self->glXMakeContextCurrent, "glXMakeContextCurrent" },
@@ -403,7 +403,7 @@ static bool gsr_egl_load_glx(gsr_egl *self, void *library) {
}
static bool gsr_egl_load_gl(gsr_egl *self, void *library) {
- dlsym_assign required_dlsym[] = {
+ const dlsym_assign required_dlsym[] = {
{ (void**)&self->glGetError, "glGetError" },
{ (void**)&self->glGetString, "glGetString" },
{ (void**)&self->glFlush, "glFlush" },
@@ -452,11 +452,13 @@ static bool gsr_egl_load_gl(gsr_egl *self, void *library) {
{ (void**)&self->glEnableVertexAttribArray, "glEnableVertexAttribArray" },
{ (void**)&self->glDrawArrays, "glDrawArrays" },
{ (void**)&self->glEnable, "glEnable" },
+ { (void**)&self->glDisable, "glDisable" },
{ (void**)&self->glBlendFunc, "glBlendFunc" },
{ (void**)&self->glGetUniformLocation, "glGetUniformLocation" },
{ (void**)&self->glUniform1f, "glUniform1f" },
{ (void**)&self->glUniform2f, "glUniform2f" },
{ (void**)&self->glDebugMessageCallback, "glDebugMessageCallback" },
+ { (void**)&self->glScissor, "glScissor" },
{ NULL, NULL }
};
@@ -488,7 +490,6 @@ static bool gsr_egl_load_gl(gsr_egl *self, void *library) {
// }
bool gsr_egl_load(gsr_egl *self, Display *dpy, bool wayland, bool is_monitor_capture) {
- (void)is_monitor_capture;
memset(self, 0, sizeof(gsr_egl));
self->x11.dpy = dpy;
self->context_type = GSR_GL_CONTEXT_TYPE_EGL;
diff --git a/src/main.cpp b/src/main.cpp
index 1c5024b..bd4be62 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -101,7 +101,8 @@ enum class PixelFormat {
enum class FramerateMode {
CONSTANT,
- VARIABLE
+ VARIABLE,
+ CONTENT
};
static int x11_error_handler(Display*, XErrorEvent*) {
@@ -328,7 +329,7 @@ static AVCodecContext* create_audio_codec_context(int fps, AudioCodec audio_code
static AVCodecContext *create_video_codec_context(AVPixelFormat pix_fmt,
VideoQuality video_quality,
int fps, const AVCodec *codec, bool is_livestream, gsr_gpu_vendor vendor, FramerateMode framerate_mode,
- bool hdr, gsr_color_range color_range) {
+ bool hdr, gsr_color_range color_range, float keyint) {
AVCodecContext *codec_context = avcodec_alloc_context3(codec);
@@ -352,9 +353,9 @@ static AVCodecContext *create_video_codec_context(AVPixelFormat pix_fmt,
codec_context->flags2 |= AV_CODEC_FLAG2_FAST;
//codec_context->gop_size = std::numeric_limits<int>::max();
//codec_context->keyint_min = std::numeric_limits<int>::max();
- codec_context->gop_size = fps * 2;
+ codec_context->gop_size = fps * keyint;
} else {
- codec_context->gop_size = fps * 2;
+ codec_context->gop_size = fps * keyint;
}
codec_context->max_b_frames = 0;
codec_context->pix_fmt = pix_fmt;
@@ -505,7 +506,7 @@ static bool vaapi_create_codec_context(AVCodecContext *video_codec_context, cons
static bool check_if_codec_valid_for_hardware(const AVCodec *codec, gsr_gpu_vendor vendor, const char *card_path) {
// Do not use AV_PIX_FMT_CUDA because we dont want to do full check with hardware context
- AVCodecContext *codec_context = create_video_codec_context(vendor == GSR_GPU_VENDOR_NVIDIA ? AV_PIX_FMT_YUV420P : AV_PIX_FMT_VAAPI, VideoQuality::VERY_HIGH, 60, codec, false, vendor, FramerateMode::CONSTANT, false, GSR_COLOR_RANGE_LIMITED);
+ AVCodecContext *codec_context = create_video_codec_context(vendor == GSR_GPU_VENDOR_NVIDIA ? AV_PIX_FMT_YUV420P : AV_PIX_FMT_VAAPI, VideoQuality::VERY_HIGH, 60, codec, false, vendor, FramerateMode::CONSTANT, false, GSR_COLOR_RANGE_LIMITED, 2);
if(!codec_context)
return false;
@@ -792,6 +793,7 @@ static void open_video(AVCodecContext *codec_context, VideoQuality video_quality
if(codec_context->codec_id == AV_CODEC_ID_H264) {
av_dict_set(&options, "profile", "high", 0);
+ // Removed because it causes stutter in games for some people
//av_dict_set_int(&options, "quality", 5, 0); // quality preset
} else if(codec_context->codec_id == AV_CODEC_ID_AV1) {
av_dict_set(&options, "profile", "main", 0); // TODO: use professional instead?
@@ -822,7 +824,7 @@ static void open_video(AVCodecContext *codec_context, VideoQuality video_quality
static void usage_header() {
const bool inside_flatpak = getenv("FLATPAK_ID") != NULL;
const char *program_name = inside_flatpak ? "flatpak run --command=gpu-screen-recorder com.dec05eba.gpu_screen_recorder" : "gpu-screen-recorder";
- fprintf(stderr, "usage: %s -w <window_id|monitor|focused> [-c <container_format>] [-s WxH] -f <fps> [-a <audio_input>] [-q <quality>] [-r <replay_buffer_size_sec>] [-k h264|hevc|hevc_hdr|av1|av1_hdr] [-ac aac|opus|flac] [-ab <bitrate>] [-oc yes|no] [-fm cfr|vfr] [-cr limited|full] [-v yes|no] [-h|--help] [-o <output_file>] [-mf yes|no] [-sc <script_path>] [-cursor yes|no]\n", program_name);
+ fprintf(stderr, "usage: %s -w <window_id|monitor|focused> [-c <container_format>] [-s WxH] -f <fps> [-a <audio_input>] [-q <quality>] [-r <replay_buffer_size_sec>] [-k h264|hevc|hevc_hdr|av1|av1_hdr] [-ac aac|opus|flac] [-ab <bitrate>] [-oc yes|no] [-fm cfr|vfr|content] [-cr limited|full] [-v yes|no] [-h|--help] [-o <output_file>] [-mf yes|no] [-sc <script_path>] [-cursor yes|no] [-keyint <value>]\n", program_name);
}
static void usage_full() {
@@ -843,11 +845,15 @@ static void usage_full() {
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, "\n");
- fprintf(stderr, " -f Framerate to record at.\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");
+ fprintf(stderr, " For variable frame rate mode this option is the max frame rate and if the capture frame rate is below this target frame rate then frames will not be duplicated.\n");
+ fprintf(stderr, " Content frame rate is similar to variable frame rate mode, except the frame rate will match the frame rate of the captured content when possible, but not capturing above the frame rate set in this -f option.\n");
fprintf(stderr, "\n");
fprintf(stderr, " -a Audio device to record from (pulse audio device). Can be specified multiple times. Each time this is specified a new audio track is added for the specified audio device.\n");
fprintf(stderr, " A name can be given to the audio input device by prefixing the audio input with <name>/, for example \"dummy/alsa_output.pci-0000_00_1b.0.analog-stereo.monitor\".\n");
fprintf(stderr, " Multiple audio devices can be merged into one audio track by using \"|\" as a separator into one -a argument, for example: -a \"alsa_output1|alsa_output2\".\n");
+ fprintf(stderr, " If the audio device is an empty string then the audio device is ignored.\n");
fprintf(stderr, " Optional, no audio track is added by default.\n");
fprintf(stderr, "\n");
fprintf(stderr, " -q Video quality. Should be either 'medium', 'high', 'very_high' or 'ultra'. 'high' is the recommended option when live streaming or when you have a slower harddrive.\n");
@@ -857,9 +863,8 @@ static void usage_full() {
fprintf(stderr, " and the video will only be saved when the gpu-screen-recorder is closed. This feature is similar to Nvidia's instant replay feature.\n");
fprintf(stderr, " This option has be between 5 and 1200. Note that the replay buffer size will not always be precise, because of keyframes. Optional, disabled by default.\n");
fprintf(stderr, "\n");
- fprintf(stderr, " -k Video codec to use. Should be either 'auto', 'h264', 'hevc', 'av1', 'hevc_hdr' or 'av1_hdr'. Defaults to 'auto' which defaults to 'hevc' on AMD/Nvidia and 'h264' on intel.\n");
+ fprintf(stderr, " -k Video codec to use. Should be either 'auto', 'h264', 'hevc', 'av1', 'hevc_hdr' or 'av1_hdr'. Defaults to 'auto' which defaults to 'h264'.\n");
fprintf(stderr, " Forcefully set to 'h264' if the file container type is 'flv'.\n");
- fprintf(stderr, " Forcefully set to 'hevc' on AMD/intel if video codec is 'h264' and if the file container type is 'mkv'.\n");
fprintf(stderr, " 'hevc_hdr' and 'av1_hdr' option is not available on X11.\n");
fprintf(stderr, " Note: hdr metadata is not included in the video when recording with 'hevc_hdr'/'av1_hdr' because of bugs in AMD, Intel and NVIDIA drivers (amazin', they are all bugged).\n");
fprintf(stderr, "\n");
@@ -874,7 +879,9 @@ static void usage_full() {
fprintf(stderr, " is dropped when you record a game. Only needed if you are recording a game that is bottlenecked by GPU. The same issue exists on Wayland but overclocking is not possible on Wayland.\n");
fprintf(stderr, " Works only if your have \"Coolbits\" set to \"12\" in NVIDIA X settings, see README for more information. Note! use at your own risk! Optional, disabled by default.\n");
fprintf(stderr, "\n");
- fprintf(stderr, " -fm Framerate mode. Should be either 'cfr' or 'vfr'. Defaults to 'vfr'.\n");
+ fprintf(stderr, " -fm Framerate mode. Should be either 'cfr' (constant frame rate), 'vfr' (variable frame rate) or 'content'. Defaults to 'vfr'.\n");
+ fprintf(stderr, " 'vfr' is recommended for recording for less issue with very high system load but some applications such as video editors may not support it properly.\n");
+ fprintf(stderr, " 'content' is currently only supported when recording a single window, on X11. The 'content' option matches the recording frame rate to the captured content.\n");
fprintf(stderr, "\n");
fprintf(stderr, " -cr Color range. Should be either 'limited' (aka mpeg) or 'full' (aka jpeg). Defaults to 'limited'.\n");
fprintf(stderr, " Limited color range means that colors are in range 16-235 (4112-60395 for hdr) while full color range means that colors are in range 0-255 (0-65535 for hdr).\n");
@@ -892,6 +899,11 @@ static void usage_full() {
fprintf(stderr, "\n");
fprintf(stderr, " -cursor\n");
fprintf(stderr, " Record cursor. Defaults to 'yes'.\n");
+ fprintf(stderr, " -keyint\n");
+ fprintf(stderr, " Specifies the keyframe interval in seconds, the max amount of time to wait to generate a keyframe. Keyframes can be generated more often than this.\n");
+ fprintf(stderr, " This also affects seeking in the video and may affect how the replay video is cut. If this is set to 10 for example then you can only seek in 10-second chunks in the video.\n");
+ fprintf(stderr, " Setting this to a higher value reduces the video file size if you are ok with the previously described downside. This option is expected to be a floating point number.\n");
+ fprintf(stderr, " By default this value is set to 2.0.\n");
fprintf(stderr, "\n");
fprintf(stderr, " --list-supported-video-codecs\n");
fprintf(stderr, " List supported video codecs and exits. Prints h264, hevc, hevc_hdr, av1 and av1_hdr (if supported).\n");
@@ -1209,6 +1221,7 @@ static void save_replay_async(AVCodecContext *video_codec_context, int video_str
av_packet.pts = save_replay_packets[i]->data.pts;
av_packet.dts = save_replay_packets[i]->data.pts;
av_packet.flags = save_replay_packets[i]->data.flags;
+ //av_packet.duration = save_replay_packets[i]->data.duration;
AVStream *stream = video_stream;
AVCodecContext *codec_context = video_codec_context;
@@ -1435,8 +1448,8 @@ static void list_supported_video_codecs() {
card_path[0] = '\0';
if(wayland || egl.gpu_info.vendor != GSR_GPU_VENDOR_NVIDIA) {
// TODO: Allow specifying another card, and in other places
- if(!gsr_get_valid_card_path(&egl, card_path)) {
- fprintf(stderr, "Error: no /dev/dri/cardX device found. If you are running GPU Screen Recorder with prime-run then try running without it\n");
+ if(!gsr_get_valid_card_path(&egl, card_path, false)) {
+ fprintf(stderr, "Error: no /dev/dri/cardX device found. If you are running GPU Screen Recorder with prime-run then try running without it. Also make sure that you have at least one connected monitor or record a single window instead on X11\n");
_exit(2);
}
}
@@ -1458,7 +1471,7 @@ static void list_supported_video_codecs() {
XCloseDisplay(dpy);
}
-static gsr_capture* create_capture_impl(const char *window_str, const char *screen_region, bool wayland, gsr_gpu_info gpu_inf, gsr_egl &egl, int fps, bool overclock, VideoCodec video_codec, gsr_color_range color_range, bool record_cursor) {
+static gsr_capture* create_capture_impl(const char *window_str, const char *screen_region, bool wayland, gsr_egl &egl, int fps, bool overclock, VideoCodec video_codec, gsr_color_range color_range, bool record_cursor, bool track_damage) {
vec2i region_size = { 0, 0 };
Window src_window_id = None;
bool follow_focused = false;
@@ -1497,6 +1510,7 @@ static gsr_capture* create_capture_impl(const char *window_str, const char *scre
window_str = first_output.output_name;
} else {
fprintf(stderr, "Error: no available output found\n");
+ _exit(1);
}
}
@@ -1528,7 +1542,6 @@ static gsr_capture* create_capture_impl(const char *window_str, const char *scre
gsr_capture_kms_cuda_params kms_params;
kms_params.egl = &egl;
kms_params.display_to_capture = window_str;
- kms_params.gpu_inf = gpu_inf;
kms_params.hdr = video_codec_is_hdr(video_codec);
kms_params.color_range = color_range;
kms_params.record_cursor = record_cursor;
@@ -1569,7 +1582,6 @@ static gsr_capture* create_capture_impl(const char *window_str, const char *scre
gsr_capture_kms_vaapi_params kms_params;
kms_params.egl = &egl;
kms_params.display_to_capture = window_str;
- kms_params.gpu_inf = gpu_inf;
kms_params.hdr = video_codec_is_hdr(video_codec);
kms_params.color_range = color_range;
kms_params.record_cursor = record_cursor;
@@ -1602,6 +1614,7 @@ static gsr_capture* create_capture_impl(const char *window_str, const char *scre
xcomposite_params.base.region_size = region_size;
xcomposite_params.base.color_range = color_range;
xcomposite_params.base.record_cursor = record_cursor;
+ xcomposite_params.base.track_damage = track_damage;
capture = gsr_capture_xcomposite_vaapi_create(&xcomposite_params);
if(!capture)
_exit(1);
@@ -1615,6 +1628,7 @@ static gsr_capture* create_capture_impl(const char *window_str, const char *scre
xcomposite_params.base.region_size = region_size;
xcomposite_params.base.color_range = color_range;
xcomposite_params.base.record_cursor = record_cursor;
+ xcomposite_params.base.track_damage = track_damage;
xcomposite_params.overclock = overclock;
capture = gsr_capture_xcomposite_cuda_create(&xcomposite_params);
if(!capture)
@@ -1653,6 +1667,10 @@ int main(int argc, char **argv) {
// nvidia doesn't support vaapi and nvidia-vaapi-driver doesn't support encoding yet.
// Let vaapi find the match vaapi driver instead of forcing a specific one.
unsetenv("LIBVA_DRIVER_NAME");
+ // Some people set this to force all applications to vsync on nvidia, but this makes eglSwapBuffers never return.
+ unsetenv("__GL_SYNC_TO_VBLANK");
+ // Same as above, but for amd/intel
+ unsetenv("vblank_mode");
if(argc <= 1)
usage_full();
@@ -1687,6 +1705,8 @@ int main(int argc, char **argv) {
{ "-sc", Arg { {}, true, false } },
{ "-cr", Arg { {}, true, false } },
{ "-cursor", Arg { {}, true, false } },
+ { "-gopm", Arg { {}, true, false } }, // deprecated, used keyint instead
+ { "-keyint", Arg { {}, true, false } },
};
for(int i = 1; i < argc; i += 2) {
@@ -1767,6 +1787,33 @@ int main(int argc, char **argv) {
}
}
+ float keyint = 2.0;
+ const char *gopm_str = args["-gopm"].value();
+ const char *keyint_str = args["-keyint"].value();
+ if(keyint_str) {
+ if(sscanf(keyint_str, "%f", &keyint) != 1) {
+ fprintf(stderr, "Error: -keyint argument \"%s\" is not a floating point number\n", keyint_str);
+ usage();
+ }
+
+ if(keyint < 0) {
+ fprintf(stderr, "Error: -keyint is expected to be 0 or larger\n");
+ usage();
+ }
+ } else if(gopm_str) {
+ if(sscanf(gopm_str, "%f", &keyint) != 1) {
+ fprintf(stderr, "Error: -gopm argument \"%s\" is not a floating point number\n", gopm_str);
+ usage();
+ }
+
+ if(keyint < 0) {
+ fprintf(stderr, "Error: -gopm is expected to be 0 or larger\n");
+ usage();
+ }
+
+ fprintf(stderr, "Warning: -gopm argument is deprecated, use -keyint instead\n");
+ }
+
bool overclock = false;
const char *overclock_str = args["-oc"].value();
if(!overclock_str)
@@ -1903,11 +1950,11 @@ int main(int argc, char **argv) {
if(fps < 1)
fps = 1;
+ VideoQuality quality = VideoQuality::VERY_HIGH;
const char *quality_str = args["-q"].value();
if(!quality_str)
quality_str = "very_high";
- VideoQuality quality;
if(strcmp(quality_str, "medium") == 0) {
quality = VideoQuality::MEDIUM;
} else if(strcmp(quality_str, "high") == 0) {
@@ -1929,7 +1976,7 @@ int main(int argc, char **argv) {
fprintf(stderr, "Error: option -r has to be between 5 and 1200, was: %s\n", replay_buffer_size_secs_str);
_exit(1);
}
- replay_buffer_size_secs += 3; // Add a few seconds to account of lost packets because of non-keyframe packets skipped
+ replay_buffer_size_secs += std::ceil(keyint); // Add a few seconds to account of lost packets because of non-keyframe packets skipped
}
const char *window_str = strdup(args["-w"].value());
@@ -1977,8 +2024,8 @@ int main(int argc, char **argv) {
egl.card_path[0] = '\0';
if(wayland || egl.gpu_info.vendor != GSR_GPU_VENDOR_NVIDIA) {
// TODO: Allow specifying another card, and in other places
- if(!gsr_get_valid_card_path(&egl, egl.card_path)) {
- fprintf(stderr, "Error: no /dev/dri/cardX device found. If you are running GPU Screen Recorder with prime-run then try running without it\n");
+ if(!gsr_get_valid_card_path(&egl, egl.card_path, is_monitor_capture)) {
+ fprintf(stderr, "Error: no /dev/dri/cardX device found. If you are running GPU Screen Recorder with prime-run then try running without it. Also make sure that you have at least one connected monitor or record a single window instead on X11\n");
_exit(2);
}
}
@@ -1986,7 +2033,7 @@ int main(int argc, char **argv) {
// TODO: Fix constant framerate not working properly on amd/intel because capture framerate gets locked to the same framerate as
// game framerate, which doesn't work well when you need to encode multiple duplicate frames (AMD/Intel is slow at encoding!).
// It also appears to skip audio frames on nvidia wayland? why? that should be fine, but it causes video stuttering because of audio/video sync.
- FramerateMode framerate_mode;
+ FramerateMode framerate_mode = FramerateMode::VARIABLE;
const char *framerate_mode_str = args["-fm"].value();
if(!framerate_mode_str)
framerate_mode_str = "vfr";
@@ -1995,12 +2042,19 @@ int main(int argc, char **argv) {
framerate_mode = FramerateMode::CONSTANT;
} else if(strcmp(framerate_mode_str, "vfr") == 0) {
framerate_mode = FramerateMode::VARIABLE;
+ } else if(strcmp(framerate_mode_str, "content") == 0) {
+ framerate_mode = FramerateMode::CONTENT;
} else {
- fprintf(stderr, "Error: -fm should either be either 'cfr' or 'vfr', got: '%s'\n", framerate_mode_str);
+ fprintf(stderr, "Error: -fm should either be either 'cfr', 'vfr' or 'content', got: '%s'\n", framerate_mode_str);
usage();
}
- gsr_color_range color_range;
+ if(framerate_mode == FramerateMode::CONTENT && (wayland || is_monitor_capture)) {
+ fprintf(stderr, "Error: -fm 'content' is currently only supported on X11 and when capturing a single window.\n");
+ usage();
+ }
+
+ gsr_color_range color_range = GSR_COLOR_RANGE_LIMITED;
const char *color_range_str = args["-cr"].value();
if(!color_range_str)
color_range_str = "limited";
@@ -2088,30 +2142,33 @@ int main(int argc, char **argv) {
file_extension = file_extension.substr(0, comma_index);
}
- const bool force_no_audio_offset = file_extension == "ts" || file_extension == "flv";
-
- if(egl.gpu_info.vendor != GSR_GPU_VENDOR_NVIDIA && file_extension == "mkv" && strcmp(video_codec_to_use, "h264") == 0) {
- video_codec_to_use = "hevc";
- video_codec = VideoCodec::HEVC;
- fprintf(stderr, "Warning: video codec was forcefully set to hevc because mkv container is used and mesa (AMD and Intel driver) does not support h264 in mkv files\n");
- }
+ const bool force_no_audio_offset = is_livestream || (file_extension != "mp4" && file_extension != "mkv" && file_extension != "webm");
switch(audio_codec) {
case AudioCodec::AAC: {
+ if(file_extension == "webm") {
+ audio_codec_to_use = "opus";
+ audio_codec = AudioCodec::OPUS;
+ fprintf(stderr, "Warning: .webm files only support opus audio codec, changing audio codec from aac to opus\n");
+ }
break;
}
case AudioCodec::OPUS: {
// TODO: Also check mpegts?
- if(file_extension != "mp4" && file_extension != "mkv") {
+ if(file_extension != "mp4" && file_extension != "mkv" && file_extension != "webm") {
audio_codec_to_use = "aac";
audio_codec = AudioCodec::AAC;
- fprintf(stderr, "Warning: opus audio codec is only supported by .mp4 and .mkv files, falling back to aac instead\n");
+ fprintf(stderr, "Warning: opus audio codec is only supported by .mp4, .mkv and .webm files, falling back to aac instead\n");
}
break;
}
case AudioCodec::FLAC: {
// TODO: Also check mpegts?
- if(file_extension != "mp4" && file_extension != "mkv") {
+ if(file_extension == "webm") {
+ audio_codec_to_use = "opus";
+ audio_codec = AudioCodec::OPUS;
+ fprintf(stderr, "Warning: .webm files only support opus audio codec, changing audio codec from flac to opus\n");
+ } else if(file_extension != "mp4" && file_extension != "mkv") {
audio_codec_to_use = "aac";
audio_codec = AudioCodec::AAC;
fprintf(stderr, "Warning: flac audio codec is only supported by .mp4 and .mkv files, falling back to aac instead\n");
@@ -2129,47 +2186,47 @@ int main(int argc, char **argv) {
const bool video_codec_auto = strcmp(video_codec_to_use, "auto") == 0;
if(video_codec_auto) {
- if(egl.gpu_info.vendor == GSR_GPU_VENDOR_INTEL) {
- const AVCodec *h264_codec = find_h264_encoder(egl.gpu_info.vendor, egl.card_path);
- if(!h264_codec) {
- fprintf(stderr, "Info: using hevc encoder because a codec was not specified and your gpu does not support h264\n");
- video_codec_to_use = "hevc";
- video_codec = VideoCodec::HEVC;
- } else {
- fprintf(stderr, "Info: using h264 encoder because a codec was not specified\n");
- video_codec_to_use = "h264";
- video_codec = VideoCodec::H264;
- }
+ const AVCodec *h264_codec = find_h264_encoder(egl.gpu_info.vendor, egl.card_path);
+ if(!h264_codec) {
+ fprintf(stderr, "Info: using hevc encoder because a codec was not specified and your gpu does not support h264\n");
+ video_codec_to_use = "hevc";
+ video_codec = VideoCodec::HEVC;
} else {
- const AVCodec *hevc_codec = find_hevc_encoder(egl.gpu_info.vendor, egl.card_path);
-
- if(hevc_codec && fps > 60) {
- fprintf(stderr, "Warning: recording at higher fps than 60 with hevc might result in recording at a very low fps. If this happens, switch to h264 or av1\n");
- }
-
- // TODO: Default to h264 if resolution is around 1366x768 on AMD
-
- // hevc generally allows recording at a higher resolution than h264 on nvidia cards. On a gtx 1080 4k is the max resolution for h264 but for hevc it's 8k.
- // Another important info is that when recording at a higher fps than.. 60? hevc has very bad performance. For example when recording at 144 fps the fps drops to 1
- // while with h264 the fps doesn't drop.
- if(!hevc_codec) {
- fprintf(stderr, "Info: using h264 encoder because a codec was not specified and your gpu does not support hevc\n");
- video_codec_to_use = "h264";
- video_codec = VideoCodec::H264;
- } else {
- fprintf(stderr, "Info: using hevc encoder because a codec was not specified\n");
- video_codec_to_use = "hevc";
- video_codec = VideoCodec::HEVC;
- }
+ fprintf(stderr, "Info: using h264 encoder because a codec was not specified\n");
+ video_codec_to_use = "h264";
+ video_codec = VideoCodec::H264;
}
}
// TODO: Allow hevc, vp9 and av1 in (enhanced) flv (supported since ffmpeg 6.1)
const bool is_flv = strcmp(file_extension.c_str(), "flv") == 0;
- if(video_codec != VideoCodec::H264 && is_flv) {
- video_codec_to_use = "h264";
- video_codec = VideoCodec::H264;
- fprintf(stderr, "Warning: hevc/av1 is not compatible with flv, falling back to h264 instead.\n");
+ if(is_flv) {
+ if(video_codec != VideoCodec::H264) {
+ video_codec_to_use = "h264";
+ video_codec = VideoCodec::H264;
+ fprintf(stderr, "Warning: hevc/av1 is not compatible with flv, falling back to h264 instead.\n");
+ }
+
+ if(audio_codec != AudioCodec::AAC) {
+ audio_codec_to_use = "aac";
+ audio_codec = AudioCodec::AAC;
+ fprintf(stderr, "Warning: flv only supports aac, falling back to aac instead.\n");
+ }
+ }
+
+ const bool is_hls = strcmp(file_extension.c_str(), "m3u8") == 0;
+ if(is_hls) {
+ if(video_codec == VideoCodec::AV1 || video_codec == VideoCodec::AV1_HDR) {
+ video_codec_to_use = "hevc";
+ video_codec = VideoCodec::HEVC;
+ fprintf(stderr, "Warning: av1 is not compatible with hls (m3u8), falling back to hevc instead.\n");
+ }
+
+ if(audio_codec != AudioCodec::AAC) {
+ audio_codec_to_use = "aac";
+ audio_codec = AudioCodec::AAC;
+ fprintf(stderr, "Warning: hls (m3u8) only supports aac, falling back to aac instead.\n");
+ }
}
const AVCodec *video_codec_f = nullptr;
@@ -2246,7 +2303,7 @@ int main(int argc, char **argv) {
_exit(2);
}
- gsr_capture *capture = create_capture_impl(window_str, screen_region, wayland, egl.gpu_info, egl, fps, overclock, video_codec, color_range, record_cursor);
+ gsr_capture *capture = create_capture_impl(window_str, screen_region, wayland, egl, fps, overclock, video_codec, color_range, record_cursor, framerate_mode == FramerateMode::CONTENT);
// (Some?) livestreaming services require at least one audio track to work.
// If not audio is provided then create one silent audio track.
@@ -2257,12 +2314,6 @@ int main(int argc, char **argv) {
requested_audio_inputs.push_back(std::move(mai));
}
- if(is_livestream && framerate_mode != FramerateMode::CONSTANT) {
- fprintf(stderr, "Info: framerate mode was forcefully set to \"cfr\" because live streaming was detected\n");
- framerate_mode = FramerateMode::CONSTANT;
- framerate_mode_str = "cfr";
- }
-
if(is_livestream && recording_saved_script) {
fprintf(stderr, "Warning: live stream detected, -sc script is ignored\n");
recording_saved_script = nullptr;
@@ -2272,7 +2323,7 @@ int main(int argc, char **argv) {
std::vector<AudioTrack> audio_tracks;
const bool hdr = video_codec_is_hdr(video_codec);
- AVCodecContext *video_codec_context = create_video_codec_context(egl.gpu_info.vendor == GSR_GPU_VENDOR_NVIDIA ? AV_PIX_FMT_CUDA : AV_PIX_FMT_VAAPI, quality, fps, video_codec_f, is_livestream, egl.gpu_info.vendor, framerate_mode, hdr, color_range);
+ AVCodecContext *video_codec_context = create_video_codec_context(egl.gpu_info.vendor == GSR_GPU_VENDOR_NVIDIA ? AV_PIX_FMT_CUDA : AV_PIX_FMT_VAAPI, quality, fps, video_codec_f, is_livestream, egl.gpu_info.vendor, framerate_mode, hdr, color_range, keyint);
if(replay_buffer_size_secs == -1)
video_stream = create_stream(av_format_context, video_codec_context);
@@ -2409,6 +2460,7 @@ int main(int argc, char **argv) {
double fps_start_time = clock_get_monotonic_seconds();
double frame_timer_start = fps_start_time - target_fps; // We want to capture the first frame immediately
int fps_counter = 0;
+ int damage_fps_counter = 0;
bool paused = false;
double paused_time_offset = 0.0;
@@ -2445,6 +2497,9 @@ int main(int argc, char **argv) {
#if LIBAVUTIL_VERSION_MAJOR <= 56
av_opt_set_channel_layout(swr, "in_channel_layout", AV_CH_LAYOUT_STEREO, 0);
av_opt_set_channel_layout(swr, "out_channel_layout", AV_CH_LAYOUT_STEREO, 0);
+ #elif LIBAVUTIL_VERSION_MAJOR >= 59
+ av_opt_set_chlayout(swr, "in_chlayout", &audio_track.codec_context->ch_layout, 0);
+ av_opt_set_chlayout(swr, "out_chlayout", &audio_track.codec_context->ch_layout, 0);
#else
av_opt_set_chlayout(swr, "in_channel_layout", &audio_track.codec_context->ch_layout, 0);
av_opt_set_chlayout(swr, "out_channel_layout", &audio_track.codec_context->ch_layout, 0);
@@ -2600,7 +2655,6 @@ int main(int argc, char **argv) {
running = 0;
break;
}
- ++fps_counter;
// TODO: Move to another thread, since this shouldn't be locked to video encoding fps
{
@@ -2625,19 +2679,28 @@ int main(int argc, char **argv) {
}
}
+ const bool damaged = !capture->is_damaged || capture->is_damaged(capture);
+ if(damaged) {
+ ++damage_fps_counter;
+ }
+
+ ++fps_counter;
double time_now = clock_get_monotonic_seconds();
double frame_timer_elapsed = time_now - frame_timer_start;
double elapsed = time_now - fps_start_time;
if (elapsed >= 1.0) {
if(verbose) {
- fprintf(stderr, "update fps: %d\n", fps_counter);
+ fprintf(stderr, "update fps: %d, damage fps: %d\n", fps_counter, damage_fps_counter);
}
fps_start_time = time_now;
fps_counter = 0;
+ damage_fps_counter = 0;
}
double frame_time_overflow = frame_timer_elapsed - target_fps;
- if (frame_time_overflow >= 0.0) {
+ if (frame_time_overflow >= 0.0 && damaged) {
+ if(capture->clear_damage)
+ capture->clear_damage(capture);
frame_time_overflow = std::min(frame_time_overflow, target_fps);
frame_timer_start = time_now - frame_time_overflow;
diff --git a/src/utils.c b/src/utils.c
index 40d652e..e00f3c5 100644
--- a/src/utils.c
+++ b/src/utils.c
@@ -296,28 +296,55 @@ bool get_monitor_by_name(const gsr_egl *egl, gsr_connection_type connection_type
typedef struct {
const gsr_monitor *monitor;
gsr_monitor_rotation rotation;
+ bool match_found;
} get_monitor_by_connector_id_userdata;
+static bool vec2i_eql(vec2i a, vec2i b) {
+ return a.x == b.x && a.y == b.y;
+}
+
+static void get_monitor_by_name_and_size_callback(const gsr_monitor *monitor, void *userdata) {
+ get_monitor_by_connector_id_userdata *data = (get_monitor_by_connector_id_userdata*)userdata;
+ if(monitor->name && data->monitor->name && strcmp(monitor->name, data->monitor->name) == 0 && vec2i_eql(monitor->size, data->monitor->size)) {
+ data->rotation = monitor->rotation;
+ data->match_found = true;
+ }
+}
+
static void get_monitor_by_connector_id_callback(const gsr_monitor *monitor, void *userdata) {
get_monitor_by_connector_id_userdata *data = (get_monitor_by_connector_id_userdata*)userdata;
if(monitor->connector_id == data->monitor->connector_id ||
(!monitor->connector_id && monitor->monitor_identifier == data->monitor->monitor_identifier))
{
data->rotation = monitor->rotation;
+ data->match_found = true;
}
}
gsr_monitor_rotation drm_monitor_get_display_server_rotation(const gsr_egl *egl, const gsr_monitor *monitor) {
if(egl->wayland.dpy) {
- get_monitor_by_connector_id_userdata userdata;
- userdata.monitor = monitor;
- userdata.rotation = GSR_MONITOR_ROT_0;
- for_each_active_monitor_output_wayland(egl, get_monitor_by_connector_id_callback, &userdata);
- return userdata.rotation;
+ {
+ get_monitor_by_connector_id_userdata userdata;
+ userdata.monitor = monitor;
+ userdata.rotation = GSR_MONITOR_ROT_0;
+ userdata.match_found = false;
+ for_each_active_monitor_output_wayland(egl, get_monitor_by_name_and_size_callback, &userdata);
+ if(userdata.match_found)
+ return userdata.rotation;
+ }
+ {
+ get_monitor_by_connector_id_userdata userdata;
+ userdata.monitor = monitor;
+ userdata.rotation = GSR_MONITOR_ROT_0;
+ userdata.match_found = false;
+ for_each_active_monitor_output_wayland(egl, get_monitor_by_connector_id_callback, &userdata);
+ return userdata.rotation;
+ }
} else {
get_monitor_by_connector_id_userdata userdata;
userdata.monitor = monitor;
userdata.rotation = GSR_MONITOR_ROT_0;
+ userdata.match_found = false;
for_each_active_monitor_output_x11(egl->x11.dpy, get_monitor_by_connector_id_callback, &userdata);
return userdata.rotation;
}
@@ -423,10 +450,10 @@ static void string_copy(char *dst, const char *src, int len) {
dst[min_len] = '\0';
}
-bool gsr_get_valid_card_path(gsr_egl *egl, char *output) {
+bool gsr_get_valid_card_path(gsr_egl *egl, char *output, bool is_monitor_capture) {
if(egl->dri_card_path) {
string_copy(output, egl->dri_card_path, 127);
- return try_card_has_valid_plane(output);
+ return is_monitor_capture ? try_card_has_valid_plane(output) : true;
}
for(int i = 0; i < 10; ++i) {
@@ -453,7 +480,3 @@ bool gsr_card_path_get_render_path(const char *card_path, char *render_path) {
close(fd);
return false;
}
-
-int even_number_ceil(int value) {
- return value + (value & 1);
-}
diff --git a/uninstall.sh b/uninstall.sh
index 9457d1f..b8aac26 100755
--- a/uninstall.sh
+++ b/uninstall.sh
@@ -1,9 +1,10 @@
-#!/bin/sh
+#!/bin/sh -e
+
+script_dir=$(dirname "$0")
+cd "$script_dir"
[ $(id -u) -ne 0 ] && echo "You need root privileges to run the uninstall script" && exit 1
-rm -f "/usr/bin/gsr-kms-server"
-rm -f "/usr/bin/gpu-screen-recorder"
-rm -f "/usr/lib/systemd/user/gpu-screen-recorder.service"
+ninja -C build uninstall
-echo "Successfully uninstalled gpu-screen-recorder" \ No newline at end of file
+echo "Successfully uninstalled gpu-screen-recorder"