Compare commits

...

37 Commits
5.7.3 ... 5.9.2

Author SHA1 Message Date
dec05eba
72c548c19e Fix -fallback-cpu-encoding not working properly 2025-11-23 01:36:00 +01:00
dec05eba
7123081e53 README DnD 2025-11-19 23:26:45 +01:00
dec05eba
eba5f3f43d Allow hevc/av1 flv for recent ffmpeg 2025-11-19 20:26:37 +01:00
dec05eba
76dd8004e9 5.9.1 2025-11-19 02:30:41 +01:00
dec05eba
02c7a0bcce Fix region capture not always working on wayland if monitor is rotated (incorrect region detected) 2025-11-19 02:29:56 +01:00
dec05eba
82177cd742 m 2025-11-18 20:42:27 +01:00
dec05eba
84e3b911d9 Update README with info about amd performance and move sections 2025-11-18 20:39:14 +01:00
dec05eba
f9ab3ebd65 5.9.0 2025-11-18 11:41:12 +01:00
dec05eba
ab5988a2bb Dont scale image frame to padding in video 2025-11-18 02:52:11 +01:00
dec05eba
95c6fc84ea x11: fix monitor region incorrect when capturing a rotated monitor connected to an external gpu 2025-11-18 01:06:37 +01:00
dec05eba
a10b882e82 Fix shit 2025-11-18 00:28:13 +01:00
dec05eba
92f4bd5f95 kms: fix incorrect capture region on intel when playing a fullscreen game with a lower resolution (on some wayland compositors) 2025-11-17 23:54:09 +01:00
dec05eba
cc43ca0336 Scale video/image to output size instead of adding black bars or no scaling 2025-11-17 23:19:26 +01:00
dec05eba
a3e1b2a896 comment 2025-11-16 20:47:07 +01:00
dec05eba
80f0e483a4 screenshot: improve jpeg very high quality to 91 (enables yuv444 instead of yuv420) 2025-11-16 18:58:57 +01:00
dec05eba
739fd9cd72 Replay: attempt to fix audio desync when using multiple audio tracks 2025-11-16 17:42:30 +01:00
dec05eba
ec90166c6e 5.8.2 2025-11-14 03:57:37 +01:00
dec05eba
d34a1036ef color_conversion: remove unused alpha_blending parameter 2025-11-14 03:43:25 +01:00
dec05eba
bf6011ffcf Kms: fix region capture incorrect when monitor is rotated 2025-11-14 03:43:04 +01:00
dec05eba
20e101bfe9 Identify DVI monitor 2025-11-14 02:12:52 +01:00
dec05eba
e99605fec2 README move 2025-11-10 15:57:39 +01:00
dec05eba
9e59f5f5cd Disable overclocking if set and gpu supports CUDA_DISABLE_PERF_BOOST 2025-11-08 13:35:30 +01:00
dec05eba
96c62f2db2 Set CUDA_DISABLE_PERF_BOOST to workaround p2 power level issue. Required nvidia driver >= 580.105.08 2025-11-05 00:57:58 +01:00
dec05eba
dc15c69848 5.8.1 2025-11-02 22:57:38 +01:00
dec05eba
a545fe45c3 Allow negative position for region 2025-11-02 18:20:18 +01:00
dec05eba
e623145de8 Force QP mode when capturing with software encoder and attempting to use vbr 2025-10-27 19:02:00 +01:00
dec05eba
7d4a4068ff Add -fallback-cpu-encoding option to use cpu encoding (x264) if gpu encoding is not available on the system 2025-10-24 11:51:10 +02:00
dec05eba
57ac400eab Show better error on vaapi init failure 2025-10-22 10:21:11 +02:00
dec05eba
cf65b24d3d Update README.md 2025-10-15 17:48:38 +02:00
dec05eba
8c42c47627 Disable vaapi debug output 2025-10-05 13:26:09 +02:00
dec05eba
dee37433af 5.8.0 2025-10-05 13:00:16 +02:00
dec05eba
f9f0074f29 Update for ffmpeg 8: remove hevc vaapi padding (black bars), fix garbage output (workaround ffmpeg 8 issue) 2025-10-05 12:57:58 +02:00
dec05eba
50fbee2b2f 5.7.4 2025-10-01 18:17:53 +02:00
Theodoros Orfanidis
c60783fdcb Fix texture size when recording via portal 2025-10-01 17:47:54 +02:00
dec05eba
313d3227d8 TODO 2025-10-01 17:47:45 +02:00
dec05eba
3c5514480c Remove use of libavcodec/defs.h (fix compile on old ffmpeg) 2025-09-29 10:07:08 +02:00
dec05eba
afd140f33b m 2025-09-28 22:26:47 +02:00
24 changed files with 505 additions and 346 deletions

View File

@@ -14,7 +14,7 @@ This is a cli-only tool, if you want an UI for this check out [GPU Screen Record
Supported video codecs:
* H264 (default)
* HEVC (Optionally with HDR)
* AV1 (Optionally with HDR. Not currently supported on NVIDIA if you use GPU Screen Recorder flatpak)
* AV1 (Optionally with HDR. Not currently supported on NVIDIA in the flatpak version of GPU Screen Recorder)
* VP8
* VP9
@@ -31,9 +31,9 @@ This software works on X11 and Wayland on AMD, Intel and NVIDIA.
# Installation
If you are running an Arch Linux based distro then you can find gpu screen recorder on aur under the name gpu-screen-recorder (`yay -S gpu-screen-recorder`).\
If you are running 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
You can also install gpu screen recorder ([the ui version](https://git.dec05eba.com/gpu-screen-recorder-gtk/)) from [flathub](https://flathub.org/apps/details/com.dec05eba.gpu_screen_recorder), which is the easiest method
to install GPU Screen Recorder on non-arch based distros.\
If you install GPU Screen Recorder flatpak, which is the gtk gui version then you can still run GPU Screen Recorder command line by using the flatpak command option, for example `flatpak run --command=gpu-screen-recorder com.dec05eba.gpu_screen_recorder -w screen -f 60 -o video.mp4`. Note that if you want to record your monitor on AMD/Intel then you need to install the flatpak system-wide (like so: `flatpak install flathub --system com.dec05eba.gpu_screen_recorder`).
If you install GPU Screen Recorder flatpak, which is the gtk gui version then you can still run GPU Screen Recorder command line by using the flatpak command option, for example `flatpak run --command=gpu-screen-recorder com.dec05eba.gpu_screen_recorder -w screen -f 60 -o video.mp4`. Note that if you want to record your monitor on AMD/Intel then you need to install the flatpak system-wide (like so: `flatpak install --system com.dec05eba.gpu_screen_recorder`).
## Unofficial install methods
The only official ways to install GPU Screen Recorder is either from source, AUR or flathub. Other sources may be out of date and missing features or may not work correctly.\
@@ -55,14 +55,14 @@ GPU Screen Recorder uses meson build system so you need to install `meson` to bu
## Build dependencies
These are the dependencies needed to build GPU Screen Recorder:
* vulkan-headers
* ffmpeg (libavcodec, libavformat, libavutil, libswresample, libavfilter)
* x11 (libx11, libxcomposite, libxrandr, libxfixes, libxdamage)
* libpulse
* wayland (wayland-client, wayland-egl, wayland-scanner)
* ffmpeg (libavcodec, libavformat, libavutil, libswresample, libavfilter)
* libva (and libva-drm)
* libpulse
* libdrm
* libcap
* wayland (wayland-client, wayland-egl, wayland-scanner)
* vulkan-headers
## Optional dependencies
When building GPU Screen Recorder with portal support (`-Dportal=true` meson option, which is enabled by default) these dependencies are also needed:
@@ -80,7 +80,7 @@ There are also additional dependencies needed at runtime depending on your GPU v
### Intel
* mesa
* vaapi (intel-media-driver/libva-intel-driver/linux-firmware-intel, depending on which intel iGPU you have)
* vaapi (intel-media-driver/libva-intel-driver/linux-firmware-intel, depending on which intel GPU you have)
### NVIDIA
* cuda runtime (libcuda.so.1) (libnvidia-compute)
@@ -102,11 +102,8 @@ You can see a list of capture options to record if you run `gpu-screen-recorder
```
in this case you could record a window or a monitor with the name `DP-1`.\
To list available audio devices that you can use you can run `gpu-screen-recorder --list-audio-devices` and the name to use is on the left size of the `|`.\
To list available audio application names that you can use you can run `gpu-screen-recorder --list-application-audio`.
## Streaming
Streaming works the same way as recording, but the `-o` argument should be path to the live streaming service you want to use (including your live streaming key). Take a look at `scripts/twitch-stream.sh` to see an example of how to stream to twitch.\
GPU Screen Recorder uses Ffmpeg so GPU Screen Recorder supports all protocols that Ffmpeg supports.\
If you want to reduce latency one thing you can do is to use the `-keyint` option, for example `-keyint 0.5`. Lower value means lower latency at the cost of increased bitrate/decreased quality.
To list available audio application names that you can use you can run `gpu-screen-recorder --list-application-audio`.\
You can run `gpu-screen-recorder --info` to list more information about the system, such as the device that is used for capture and video encoding and supported codecs. These commands can be parsed by scripts/programs.
## Replay mode
Run `gpu-screen-recorder` with the `-c mp4` and `-r` option, for example: `gpu-screen-recorder -w screen -f 60 -r 30 -c mp4 -o ~/Videos`. Note that in this case, `-o` should point to a directory.\
If `-df yes` is set, replays are save in folders based on the date.
@@ -117,6 +114,10 @@ This can be used for example to show a notification when a replay has been saved
The replay buffer is stored in ram (as encoded video) by default, so don't use a too large replay time and/or video quality unless you have enough ram to store it.\
You can use the `-replay-storage disk` option to store the replay buffer on disk instead of ram (in the same location as the output video).\
By default videos are recorded with constant quality, but with replay mode you might want to record in constant bitrate mode instead for consistent ram/disk usage in high motion scenes. You can do that by using the `-bm cbr` option (along with `-q` option, for example `-bm cbr -q 20000`).
## Streaming
Streaming works the same way as recording, but the `-o` argument should be path to the live streaming service you want to use (including your live streaming key). Take a look at `scripts/twitch-stream.sh` to see an example of how to stream to twitch.\
GPU Screen Recorder uses Ffmpeg so GPU Screen Recorder supports all protocols that Ffmpeg supports.\
If you want to reduce latency one thing you can do is to use the `-keyint` option, for example `-keyint 0.5`. Lower value means lower latency at the cost of increased bitrate/decreased quality.
## Recording while using replay/streaming
You can record a regular video while using replay/streaming by launching GPU Screen Recorder with the `-ro` option to specify a directory where to save the recording.\
To start/stop (and save) recording use the SIGRTMIN signal, for example `pkill -SIGRTMIN -f gpu-screen-recorder`. The name of the video will be displayed in stdout when saving the video.\
@@ -150,22 +151,12 @@ If you record at your monitors refresh rate and enabled vsync in a game then the
This is an issue in some games.
If you experience this issue then you might want to either disable vsync in the game or use the `-fm content` option to sync capture to the content on the screen. For example: `gpu-screen-recorder -w screen -fm content -o video.mp4`.\
Note that this option is currently only available on X11, or with desktop portal capture on Wayland (`-w portal`).
# Issues
## NVIDIA
Nvidia drivers have an issue where CUDA breaks if CUDA is running when suspend/hibernation happens, and it remains broken until you reload the nvidia driver. `extra/gsr-nvidia.conf` will be installed by default when you install GPU Screen Recorder and that should fix this issue. If this doesn't fix the issue for you then your distro may use a different path for modprobe files. In that case you have to install that `extra/gsr-nvidia.conf` yourself into that location.
You have to reboot your computer after installing GPU Screen Recorder for the first time for the fix to have any effect.
# Examples
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.
# AMD/Intel/Wayland root permission
When recording a window or when using the `-w portal` option no special user permission is required,
however when recording a monitor the program needs root permission (to access KMS).\
This is safe in GPU Screen Recorder as the part that needs root access has been moved to its own small program that only does one thing.\
For you as a user this only means that if you installed GPU Screen Recorder as a flatpak then a prompt asking for root password will show up once when you start recording.
# Performance
On a system with a i5 4690k CPU and a GTX 1080 GPU:\
On a system with an i5 4690k CPU and a GTX 1080 GPU:\
When recording Legend of Zelda Breath of the Wild at 4k, fps drops from 30 to 7 when using OBS Studio + nvenc, however when using this screen recorder the fps remains at 30.\
When recording GTA V at 4k on highest settings, fps drops from 60 to 23 when using obs-nvfbc + nvenc, however when using this screen recorder the fps only drops to 58.\
On a system with an AMD Ryzen 9 5900X CPU and an RX 7800XT GPU I dont see any fps drop at all, even when recording at 4k 60fps with AV1 codec with 10-bit colors.\
GPU Screen Recorder also produces much smoother videos than OBS when GPU utilization is close to 100%, see comparison here: [https://www.youtube.com/watch?v=zfj4sNVLLLg](https://www.youtube.com/watch?v=zfj4sNVLLLg) and [https://www.youtube.com/watch?v=aK67RSZw2ZQ](https://www.youtube.com/watch?v=aK67RSZw2ZQ).\
GPU Screen Recorder has much better performance than OBS Studio even with version 30.2 that does "zero-copy" recording and encoding, see: [https://www.youtube.com/watch?v=jdroRjibsDw](https://www.youtube.com/watch?v=jdroRjibsDw).\
It is recommended to save the video to a SSD because of the large file size, which a slow HDD might not be fast enough to handle. Using variable framerate mode (-fm vfr) which is the default is also recommended as this reduces encoding load. Ultra quality is also overkill most of the time, very high (the default) or lower quality is usually enough.\
@@ -175,6 +166,25 @@ NVIDIA driver has a "feature" (read: bug) where it will downclock memory transfe
To enable overclocking for optimal performance use the `-oc` option when running GPU Screen Recorder. You also need to have "Coolbits" NVIDIA X setting set to "12" to enable overclocking. You can automatically add this option if you run `sudo nvidia-xconfig --cool-bits=12` and then reboot your computer.\
Note that this only works when Xorg server is running as root, and using this option will only give you a performance boost if the game you are recording is bottlenecked by your GPU.\
Note! use at your own risk!
# Issues
## NVIDIA
Nvidia drivers have an issue where CUDA breaks if CUDA is running when suspend/hibernation happens, and it remains broken until you reload the nvidia driver. `extra/gsr-nvidia.conf` will be installed by default when you install GPU Screen Recorder and that should fix this issue. If this doesn't fix the issue for you then your distro may use a different path for modprobe files. In that case you have to install that `extra/gsr-nvidia.conf` yourself into that location.
You have to reboot your computer after installing GPU Screen Recorder for the first time for the fix to have any effect.
## TEMPORARY ISSUES
1) Videos are in variable framerate format. Use MPV to play such videos, otherwise you might experience stuttering in the video if you are using a buggy video player. You can try saving the video into a .mkv file instead as some software may have better support for .mkv files (such as kdenlive). You can use the "-fm cfr" option to to use constant framerate mode.
2) FLAC audio codec is disabled at the moment because of temporary issues.
# Examples
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.
# AMD/Intel/Wayland root permission
When recording a window or when using the `-w portal` option no special user permission is required,
however when recording a monitor the program needs root permission (to access KMS).\
This is safe in GPU Screen Recorder as the part that needs root access has been moved to its own small program that only does one thing.\
For you as a user this only means that if you installed GPU Screen Recorder as a flatpak then a prompt asking for root password will show up once when you start recording.
# VRR/G-SYNC
This should work fine on AMD/Intel X11 or Wayland. On Nvidia X11 G-SYNC only works with the -w screen-direct option, but because of bugs in the Nvidia driver this option is not always recommended.
For example it can cause your computer to freeze when recording certain games.
@@ -185,10 +195,6 @@ See [https://git.dec05eba.com/?p=about](https://git.dec05eba.com/?p=about).
# Demo
[![Click here to watch a demo video on youtube](https://img.youtube.com/vi/n5tm0g01n6A/0.jpg)](https://www.youtube.com/watch?v=n5tm0g01n6A)
# TEMPORARY ISSUES
1) Videos are in variable framerate format. Use MPV to play such videos, otherwise you might experience stuttering in the video if you are using a buggy video player. You can try saving the video into a .mkv file instead as some software may have better support for .mkv files (such as kdenlive). You can use the "-fm cfr" option to to use constant framerate mode.
2) FLAC audio codec is disabled at the moment because of temporary issues.
# FAQ
## It tells me that my AMD/Intel GPU is not supported or that my GPU doesn't support h264/hevc, but that's not true!
Some linux distros (such as manjaro and fedora) disable hardware accelerated h264/hevc on AMD/Intel because of "patent license issues". If you are using an arch-based distro then you can install mesa-git instead of mesa and if you are using another distro then you may have to switch to a better distro. On fedora based distros you can follow this: [Hardware Accelerated Codec](https://rpmfusion.org/Howto/Multimedia).\
@@ -202,7 +208,9 @@ Browsers and discord don't support hevc video codec at the moment. Choose h264 v
Note that websites such as youtube support hevc so there is no need to choose h264 video codec if you intend to upload the video to youtube or if you want to play the video locally or if you intend to
edit the video with a video editor. Hevc allows for better video quality (especially at lower file sizes) so hevc (or av1) is recommended for source videos.
## I get a black bar/distorted colors on the sides in the video
This is mostly an issue on AMD. For av1 it's a hardware issue, see: https://gitlab.freedesktop.org/mesa/mesa/-/issues/9185. For hevc it's a software issue in the AMD driver that hasn't been fixed yet. This issue happens at certain video resolutions. If you get this issue then a workaround is to record with h264 video codec instead (using the -k h264 option).
This is mostly an issue on AMD. For av1 it's a hardware issue, see: https://gitlab.freedesktop.org/mesa/mesa/-/issues/9185. For hevc it's a software issue in ffmpeg that was fixed in ffmpeg version 8.\
If your ffmpeg version is older than 8 then you can use the flatpak version of GPU Screen Recorder which comes with ffmpeg version >= 8.\
Alternatively you can record with h264 codec (-k h264, which is also the default codec) to workaround this issue.
## The video doesn't display or has a green/yellow overlay
This can happen if your video player is missing the H264/HEVC video codecs. Either install the codecs or use mpv.
## I get stutter in the video
@@ -224,10 +232,13 @@ You have to use external software for that, such as Easy Effects or NoiseTorch.
It's not really possible except in some cases. You can only record with the GPU that is displaying the graphics on your monitor.\
Some laptops have display adapters that connect external monitors directly to the external GPU (if you have one)
and on Wayland the external GPU will display the graphics for that monitor.
In that case you can record the monitor with the external GPU by launching GPU Screen Recorder with [prime-run or by setting the DRI_PRIME environment variable](https://wiki.archlinux.org/title/PRIME) depending on your GPU brand.\
In that case you can record the monitor with the external GPU by launching GPU Screen Recorder with [prime-run or by setting the DRI_PRIME environment variable](https://wiki.archlinux.org/title/PRIME) depending on your GPU brand.
Alternatively you can capture with the desktop portal option (`-w portal`), which should allow you to capture any monitor.\
However if you really want to change which GPU you want to record and encode with with then you can instead configure your display server (Xorg or Wayland compositor) to run with that GPU,
then GPU Screen Recorder will automatically use that same GPU for recording and encoding.
## The rotation of the video is incorrect when the monitor is rotated when using desktop portal capture
This is a bug in kde plasma wayland. When using desktop portal capture and the monitor is rotated and a window is made fullscreen kde plasma wayland will give incorrect rotation to GPU Screen Recorder.
This also affects other screen recording software such as obs studio.\
Capture a monitor directly instead to workaround this issue until kde plasma devs fix it, or use another wayland compositor that doesn't have this issue.
## System notifications get disabled when recording with desktop portal option
Some desktop environments such as KDE Plasma turn off notifications when you record the screen with the desktop portal option. You can disable this by going into KDE Plasma settings -> search for notifications and then under "Do Not Disturb mode" untick "During screen sharing".

58
TODO
View File

@@ -30,9 +30,6 @@ Window capture doesn't work properly in _control_ game after going from pause me
Monitor capture on steam deck is slightly below the game fps, but only when capturing on the steam deck screen. If capturing on another monitor, there is no issue.
Is this related to the dma buf rotation issue? different modifier being slow? does this always happen?
Fallback to vaapi copy in kms if opengl version fails. This can happen on steam deck for some reason (driver bug?). Also vaapi copy uses less gpu since it uses video codec unit to copy.
Test if vaapi copy version uses less memory than opengl version.
Intel is a bit weird with monitor capture and multiple monitors. If one of the monitors is rotated then all the kms will be rotated as well.
Is that only the case when the primary monitor is rotated? Also the primary monitor becomes position 0, 0 so crtc (x11 randr) position doesn't match the drm pos. Maybe get monitor position and size from drm instead.
How about if multiple monitors are rotated?
@@ -64,8 +61,6 @@ Remove follow focused option.
Exit if X11/Wayland killed (if drm plane dead or something?)
Use SRC_W and SRC_H for screen plane instead of crtc_w and crtc_h.
Test if p2 state can be worked around by using pure nvenc api and overwriting cuInit/cuCtxCreate* to not do anything. Cuda might be loaded when using nvenc but it might not be used, with certain record options? (such as h264 p5).
nvenc uses cuda when using b frames and rgb->yuv conversion, so convert the image ourselves instead.-
@@ -84,8 +79,6 @@ When vulkan encode is added, mention minimum nvidia driver required. (550.54.14?
Investigate if there is a way to do gpu->gpu copy directly without touching system ram to enable video encoding on a different gpu. On nvidia this is possible with cudaMemcpyPeer, but how about from an intel/amd gpu to an nvidia gpu or the other way around or any combination of iGPU and dedicated GPU?
Maybe something with clEnqueueMigrateMemObjects? on AMD something with DirectGMA maybe?
Go back to using pure vaapi without opengl for video encoding? rotation (transpose) can be done if its done after (rgb to yuv) color conversion.
Use lanczos resampling for better scaling quality. Lanczos resampling can also be used for YUV chroma for better color quality on small text.
Flac is disabled because the frame sizes are too large which causes big audio/video desync.
@@ -189,13 +182,6 @@ Use wayland color management protocol when it's available: https://gitlab.freede
Use different exit codes for different errors. Use one for invalid -w option, another one for invalid -a option for audio devices, etc. This is to make UI error reporting better.
Document these exit codes in an exit code .md file, or finally create a manpage where this can be documented.
Ffmpeg fixed black bars in videos on amd when using hevc and when recording at some resolutions, such as 1080p:
https://github.com/FFmpeg/FFmpeg/commit/bcfbf2bac8f9eeeedc407b40596f5c7aaa0d5b47
https://github.com/FFmpeg/FFmpeg/commit/d0facac679faf45d3356dff2e2cb382580d7a521
Disable gpu screen recorder black bar handling when using hevc on amd when the libavcodec version is the one that comes after those commits.
Also consider the mesa version, to see if the gpu supports this.
The version is libavcodec >= 61.28.100
Use opengl compute shader instead of graphics shader. This might allow for better performance when games are using 100% of graphics unit which might fix issue with 100% gpu usage causing gpu screen recorder to run slow when not using vaapi to convert rgb to nv12(?).
Always disable prime run/dri prime and list all monitors to record from from all cards.
@@ -209,8 +195,6 @@ Always disable prime run/dri prime and list all monitors to record from from all
Allow flv av1 if recent ffmpeg version and streaming to youtube (and twitch?) and for custom services.
Use explicit sync in pipewire video code: https://docs.pipewire.org/page_dma_buf.html.
Support vaapi rotation. Support for it is added in mesa here: https://gitlab.freedesktop.org/mesa/mesa/-/merge_requests/32919.
Replay (and recording?) fails to save properly sometimes (especially for long videos). This is noticable with mp4 files since they get corrupt and become unplayable.
The entire video does seem to get saved (it's a large video file) and it seems to have the correct headers but it's not playable.
@@ -273,7 +257,8 @@ Add the option to set audio track name, for example with -a "track-name:blabla|d
Maybe disable qp/vbr for replay. In that case we can preallocate all replay data (for both ram and disk) and write to that directly when receiving packet (dont do that when also recording at the same time).
That could improve performance/disk write optimization and maybe even reduce ram usage because of less blocks/fragmentation.
When rpc is added add the option to add/remove audio devices/app audio and also overlays (from new capture sources).
When rpc is added add the option to add/remove audio devices/app audio and also overlays (from new capture sources). It should also be possible to save a replay of any duration (below duration set with -r).
Have an rpc info/status command that just returns the status. This is to check if gpu screen recorder is running.
Support hdr screenshot.
@@ -314,7 +299,7 @@ It's possible for microphone audio to get desynced when recording together with
We can use dri2connect/dri3open to get the /dev/dri/card device. Note that this doesn't work on nvidia x11.
Add support for QVBR (QP with target bitrate).
Add support for QVBR (QP with target bitrate). Maybe use VBR instead, since nvidia doesn't support QVBR and neither does vulkan.
KDE Plasma Wayland seems to use overlay planes now in non-fullscreen mode(limited to 1 overlay plane per gpu). Check if this is the case in the latest kde on arch linux.
If it is, then support it in kms capture.
@@ -334,3 +319,40 @@ Application audio capture isn't good enough. It creates a sink that for some aut
However that causes pulseaudio to be unable to record from gsr-app-sink, and it ends up being stuck in pa_sound_device_handle_reconnect in the loop with pa_mainloop_iterate.
Add -k best/best_hdr/best_10bit option, to automatically choose the best codec (prefer av1, then hevc and then h264. For webm files choose vp9 and then vp8).
Check if region capture works properly with fractional scaling on wayland.
Add option to specify medium/high/very high/ultra for -bm cbr as well, which should automatically pick bitrate based on resolution and framerate.
This should also be reflected in gsr ui.
Create a manpage and move --help text there and mention the manpage command to view it (and make it work in flatpak, maybe with man <link-to-manpage-file>).
Implement webcam support by using mjpeg with v4l2 and use ffmpeg mjpeg decoder.
After adding rpc, making recording while in replay/streaming work differently. Start recording should take audio as an argument, to optionally specify different audio for recording than replay/stream.
After adding rpc, make it possible to add/remove audio and video. The same number of audio tracks should remain, but the audio devices/app should be possible to configure. You should be able to configure the capture sources however you want.
It takes a while to shutdown gpu screen recorder when using replay with a long replay time (when the replay buffer is for example 6gb). Maybe this has to do with cleaning up so much memory. Try if _exit(0) is faster, ignoring cleanup of anything (assuming the gpu driver is happy with that, nvidia doesn't like such things. GPU drivers are often buggy when it comes to automatic cleanup).
Support desktop portal hdr capture when this gets merged: https://invent.kde.org/plasma/kwin/-/merge_requests/8293 note that it only works with pipewire >= 1.5.81. If codec is set to non-hdr codec when use desktop portal without hdr (convert to sdr, remove the code that forces non-hdr equivalent of the codec). Update gsr-gtk and gsr-ui accordingly.
Add option to save screenshot as .qoi. Use that then in gsr-ui for the background for the window (capture the monitor that the ui is going to show on). Do that on cosmic and hyprland, which are unable to display things behind the fullscreen window.
Support pausing recording when recording while replay/streaming.
Maybe use VK_VALVE_video_encode_rgb_conversion with vulkan encoding for shader-less rgb to yuv conversion. That would allow screen capture with no gpu processing.
Cursor sometimes doesn't have color when capturing region scaled (on kde plasma wayland at least).
Remove drm_monitor_get_display_server_data and do that work in the drm monitor query.
In gpu screen recorder --info output codec max resolutions. This allows for better error messages in ui frontends and easier and better error handling.
Set minimum fps for live stream or piping or always.
Support youtube sso.
Remove -fm content (support it but remove it from documentation and output deprecation notice when its used) and use it when using -fm vbr (which is the default option).
But first -fm content needs to be support on wayland as well, by checking if there is a difference between frames (checksum the frame content).
-fm content also needs to have a minimum fps to prevent live stream from timing out when nothing changes on the screen.

View File

@@ -8,7 +8,7 @@
typedef struct gsr_egl gsr_egl;
#define NUM_ARGS 31
#define NUM_ARGS 32
typedef enum {
ARG_TYPE_STRING,
@@ -76,6 +76,7 @@ typedef struct {
const char *recording_saved_script;
bool verbose;
bool gl_debug;
bool fallback_cpu_encoding;
bool record_cursor;
bool date_folders;
bool restore_portal_session;

View File

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

View File

@@ -60,7 +60,7 @@ typedef struct {
int gsr_color_conversion_init(gsr_color_conversion *self, const gsr_color_conversion_params *params);
void gsr_color_conversion_deinit(gsr_color_conversion *self);
void gsr_color_conversion_draw(gsr_color_conversion *self, unsigned int texture_id, vec2i destination_pos, vec2i destination_size, vec2i source_pos, vec2i source_size, vec2i texture_size, gsr_rotation rotation, gsr_source_color source_color, bool external_texture, bool alpha_blending);
void gsr_color_conversion_draw(gsr_color_conversion *self, unsigned int texture_id, vec2i destination_pos, vec2i destination_size, vec2i source_pos, vec2i source_size, vec2i texture_size, gsr_rotation rotation, gsr_source_color source_color, bool external_texture);
void gsr_color_conversion_clear(gsr_color_conversion *self);
void gsr_color_conversion_read_destination_texture(gsr_color_conversion *self, int destination_texture_index, int x, int y, int width, int height, unsigned int color_format, unsigned int data_format, void *pixels);

View File

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

View File

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

View File

@@ -58,4 +58,6 @@ vec2i scale_keep_aspect_ratio(vec2i from, vec2i to);
unsigned int gl_create_texture(gsr_egl *egl, int width, int height, int internal_format, unsigned int format, int filter);
bool get_nvidia_driver_version(int *major, int *minor);
#endif /* GSR_UTILS_H */

View File

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

View File

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

View File

@@ -1,4 +1,4 @@
project('gpu-screen-recorder', ['c', 'cpp'], version : '5.7.3', default_options : ['warning_level=2'])
project('gpu-screen-recorder', ['c', 'cpp'], version : '5.9.2', default_options : ['warning_level=2'])
add_project_arguments('-Wshadow', language : ['c', 'cpp'])
if get_option('buildtype') == 'debug'

View File

@@ -1,7 +1,7 @@
[package]
name = "gpu-screen-recorder"
type = "executable"
version = "5.7.3"
version = "5.9.2"
platforms = ["posix"]
[config]

View File

@@ -195,7 +195,7 @@ static void usage_header() {
"[-k h264|hevc|av1|vp8|vp9|hevc_hdr|av1_hdr|hevc_10bit|av1_10bit] [-ac aac|opus|flac] [-ab <bitrate>] [-oc yes|no] [-fm cfr|vfr|content] "
"[-bm auto|qp|vbr|cbr] [-cr limited|full] [-tune performance|quality] [-df yes|no] [-sc <script_path>] [-p <plugin_path>] "
"[-cursor yes|no] [-keyint <value>] [-restore-portal-session yes|no] [-portal-session-token-filepath filepath] [-encoder gpu|cpu] "
"[-o <output_file>] [-ro <output_directory>] [--list-capture-options [card_path]] [--list-audio-devices] [--list-application-audio] "
"[-fallback-cpu-encoding yes|no] [-o <output_file>] [-ro <output_directory>] [--list-capture-options [card_path]] [--list-audio-devices] [--list-application-audio] "
"[-v yes|no] [-gl-debug yes|no] [--version] [-h|--help]\n", program_name);
fflush(stdout);
}
@@ -293,8 +293,9 @@ static void usage_full() {
printf(" Works only if your have \"Coolbits\" set to \"12\" in NVIDIA X settings, see README for more information. Note! use at your own risk! Optional, disabled by default.\n");
printf("\n");
printf(" -fm Framerate mode. Should be either 'cfr' (constant frame rate), 'vfr' (variable frame rate) or 'content'. Optional, set to 'vfr' by default.\n");
printf(" 'vfr' is recommended for recording for less issue with very high system load but some applications such as video editors may not support it properly.\n");
printf(" 'vfr' is recommended for general recording for less issue with very high system load but some applications such as video editors may not support it properly.\n");
printf(" 'content' is currently only supported on X11 or when using portal capture option. The 'content' option matches the recording frame rate to the captured content.\n");
printf(" 'content' is the best option to use when possible as it avoids capturing duplicate frames, which results in a smoother video.\n");
printf("\n");
printf(" -bm Bitrate mode. Should be either 'auto', 'qp' (constant quality), 'vbr' (variable bitrate) or 'cbr' (constant bitrate). Optional, set to 'auto' by default which defaults to 'qp' on all devices\n");
printf(" except steam deck that has broken drivers and doesn't support qp.\n");
@@ -341,6 +342,12 @@ static void usage_full() {
printf(" 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");
printf(" Optional, set to 'gpu' by default.\n");
printf("\n");
printf(" -fallback-cpu-encoding\n");
printf(" If this is set to 'yes' and GPU encoding is not available on the system then CPU encoding will be used instead. Optional, set to 'no' by default.\n");
printf(" If the fallback to CPU encoding happens then h264 codec will forcefully be used.\n");
printf(" This should ideally not be used. Instead properly install vaapi on your system to encode the video with your GPU.\n");
printf(" Some very old GPUs dont support video encoding in which case this option may be used.\n");
printf("\n");
printf(" --info\n");
printf(" List info about the system. Lists the following information (prints them to stdout and exits):\n");
printf(" Supported video codecs (h264, h264_software, hevc, hevc_hdr, hevc_10bit, av1, av1_hdr, av1_10bit, vp8, vp9) and image codecs (jpeg, png) (if supported).\n");
@@ -469,6 +476,7 @@ static bool args_parser_set_values(args_parser *self) {
self->restore_portal_session = args_get_boolean_by_key(self->args, NUM_ARGS, "-restore-portal-session", false);
self->restart_replay_on_save = args_get_boolean_by_key(self->args, NUM_ARGS, "-restart-replay-on-save", false);
self->overclock = args_get_boolean_by_key(self->args, NUM_ARGS, "-oc", false);
self->fallback_cpu_encoding = args_get_boolean_by_key(self->args, NUM_ARGS, "-fallback-cpu-encoding", false);
self->audio_bitrate = args_get_i64_by_key(self->args, NUM_ARGS, "-ab", 0);
self->audio_bitrate *= 1000LL;
@@ -586,8 +594,8 @@ static bool args_parser_set_values(args_parser *self) {
return false;
}
if(self->region_size.x < 0 || self->region_size.y < 0 || self->region_position.x < 0 || self->region_position.y < 0) {
fprintf(stderr, "gsr error: invalid value for option -region '%s', expected width, height, x and y to be greater or equal to 0\n", region_str);
if(self->region_size.x < 0 || self->region_size.y < 0) {
fprintf(stderr, "gsr error: invalid value for option -region '%s', expected width and height to be greater or equal to 0\n", region_str);
usage();
return false;
}
@@ -755,6 +763,7 @@ bool args_parser_parse(args_parser *self, int argc, char **argv, const args_hand
self->args[arg_index++] = (Arg){ .key = "-restore-portal-session", .optional = true, .list = false, .type = ARG_TYPE_BOOLEAN };
self->args[arg_index++] = (Arg){ .key = "-portal-session-token-filepath", .optional = true, .list = false, .type = ARG_TYPE_STRING };
self->args[arg_index++] = (Arg){ .key = "-encoder", .optional = true, .list = false, .type = ARG_TYPE_ENUM, .enum_values = video_encoder_enums, .num_enum_values = sizeof(video_encoder_enums)/sizeof(ArgEnum) };
self->args[arg_index++] = (Arg){ .key = "-fallback-cpu-encoding", .optional = true, .list = false, .type = ARG_TYPE_BOOLEAN };
self->args[arg_index++] = (Arg){ .key = "-replay-storage", .optional = true, .list = false, .type = ARG_TYPE_ENUM, .enum_values = replay_storage_enums, .num_enum_values = sizeof(replay_storage_enums)/sizeof(ArgEnum) };
self->args[arg_index++] = (Arg){ .key = "-p", .optional = true, .list = true, .type = ARG_TYPE_STRING };
assert(arg_index == NUM_ARGS);

View File

@@ -209,14 +209,14 @@ static int gsr_capture_kms_start(gsr_capture *cap, gsr_capture_metadata *capture
if(self->params.output_resolution.x > 0 && self->params.output_resolution.y > 0) {
self->params.output_resolution = scale_keep_aspect_ratio(self->capture_size, self->params.output_resolution);
capture_metadata->width = self->params.output_resolution.x;
capture_metadata->height = self->params.output_resolution.y;
capture_metadata->video_width = self->params.output_resolution.x;
capture_metadata->video_height = self->params.output_resolution.y;
} else if(self->params.region_size.x > 0 && self->params.region_size.y > 0) {
capture_metadata->width = self->params.region_size.x;
capture_metadata->height = self->params.region_size.y;
capture_metadata->video_width = self->params.region_size.x;
capture_metadata->video_height = self->params.region_size.y;
} else {
capture_metadata->width = self->capture_size.x;
capture_metadata->height = self->capture_size.y;
capture_metadata->video_width = self->capture_size.x;
capture_metadata->video_height = self->capture_size.y;
}
self->last_time_monitor_check = clock_get_monotonic_seconds();
@@ -508,7 +508,7 @@ static void render_drm_cursor(gsr_capture_kms *self, gsr_color_conversion *color
gsr_color_conversion_draw(color_conversion, self->cursor_texture_id,
cursor_pos, (vec2i){cursor_size.x * scale.x, cursor_size.y * scale.y},
(vec2i){0, 0}, cursor_size, cursor_size,
gsr_monitor_rotation_to_rotation(rotation), GSR_SOURCE_COLOR_RGB, cursor_texture_id_is_external, true);
gsr_monitor_rotation_to_rotation(rotation), GSR_SOURCE_COLOR_RGB, cursor_texture_id_is_external);
self->params.egl->glDisable(GL_SCISSOR_TEST);
}
@@ -536,13 +536,13 @@ static void render_x11_cursor(gsr_capture_kms *self, gsr_color_conversion *color
gsr_color_conversion_draw(color_conversion, self->x11_cursor.texture_id,
cursor_pos, (vec2i){self->x11_cursor.size.x * scale.x, self->x11_cursor.size.y * scale.y},
(vec2i){0, 0}, self->x11_cursor.size, self->x11_cursor.size,
GSR_ROT_0, GSR_SOURCE_COLOR_RGB, false, true);
GSR_ROT_0, GSR_SOURCE_COLOR_RGB, false);
self->params.egl->glDisable(GL_SCISSOR_TEST);
}
static void gsr_capture_kms_update_capture_size_change(gsr_capture_kms *self, gsr_color_conversion *color_conversion, vec2i target_pos, const gsr_kms_response_item *drm_fd) {
if(target_pos.x != self->prev_target_pos.x || target_pos.y != self->prev_target_pos.y || drm_fd->crtc_w != self->prev_plane_size.x || drm_fd->crtc_h != self->prev_plane_size.y) {
if(target_pos.x != self->prev_target_pos.x || target_pos.y != self->prev_target_pos.y || drm_fd->src_w != self->prev_plane_size.x || drm_fd->src_h != self->prev_plane_size.y) {
self->prev_target_pos = target_pos;
self->prev_plane_size = self->capture_size;
gsr_color_conversion_clear(color_conversion);
@@ -621,15 +621,12 @@ static int gsr_capture_kms_capture(gsr_capture *cap, gsr_capture_metadata *captu
if(drm_fd->has_hdr_metadata && self->params.hdr && hdr_metadata_is_supported_format(&drm_fd->hdr_metadata))
gsr_kms_set_hdr_metadata(self, drm_fd);
self->capture_size = rotate_capture_size_if_rotated(self, (vec2i){ drm_fd->crtc_w, drm_fd->crtc_h });
self->capture_size = rotate_capture_size_if_rotated(self, (vec2i){ drm_fd->src_w, drm_fd->src_h });
if(self->params.region_size.x > 0 && self->params.region_size.y > 0)
self->capture_size = self->params.region_size;
const bool is_scaled = self->params.output_resolution.x > 0 && self->params.output_resolution.y > 0;
vec2i output_size = is_scaled ? self->params.output_resolution : self->capture_size;
output_size = scale_keep_aspect_ratio(self->capture_size, output_size);
const vec2i target_pos = { max_int(0, capture_metadata->width / 2 - output_size.x / 2), max_int(0, capture_metadata->height / 2 - output_size.y / 2) };
const vec2i output_size = scale_keep_aspect_ratio(self->capture_size, (vec2i){capture_metadata->recording_width, capture_metadata->recording_height});
const vec2i target_pos = { max_int(0, capture_metadata->video_width / 2 - output_size.x / 2), max_int(0, capture_metadata->video_height / 2 - output_size.y / 2) };
gsr_capture_kms_update_capture_size_change(self, color_conversion, target_pos, drm_fd);
vec2i capture_pos = self->capture_pos;
@@ -649,12 +646,12 @@ static int gsr_capture_kms_capture(gsr_capture *cap, gsr_capture_metadata *captu
}
const gsr_monitor_rotation plane_rotation = kms_rotation_to_gsr_monitor_rotation(drm_fd->rotation);
const gsr_monitor_rotation rotation = sub_rotations(self->monitor_rotation, plane_rotation);
const gsr_monitor_rotation rotation = capture_is_combined_plane ? GSR_MONITOR_ROT_0 : sub_rotations(self->monitor_rotation, plane_rotation);
gsr_color_conversion_draw(color_conversion, self->external_texture_fallback ? self->external_input_texture_id : self->input_texture_id,
target_pos, output_size,
capture_pos, self->capture_size, (vec2i){ drm_fd->width, drm_fd->height },
gsr_monitor_rotation_to_rotation(rotation), GSR_SOURCE_COLOR_RGB, self->external_texture_fallback, false);
gsr_monitor_rotation_to_rotation(rotation), GSR_SOURCE_COLOR_RGB, self->external_texture_fallback);
if(self->params.record_cursor) {
gsr_kms_response_item *cursor_drm_fd = find_cursor_drm_if_on_monitor(self, drm_fd->connector_id, capture_is_combined_plane);
@@ -668,7 +665,7 @@ static int gsr_capture_kms_capture(gsr_capture *cap, gsr_capture_metadata *captu
cursor_monitor_offset.y += self->params.region_position.y;
render_x11_cursor(self, color_conversion, cursor_monitor_offset, target_pos, output_size);
} else if(cursor_drm_fd) {
const vec2i framebuffer_size = rotate_capture_size_if_rotated(self, (vec2i){ drm_fd->crtc_w, drm_fd->crtc_h });
const vec2i framebuffer_size = rotate_capture_size_if_rotated(self, (vec2i){ drm_fd->src_w, drm_fd->src_h });
render_drm_cursor(self, color_conversion, cursor_drm_fd, target_pos, output_size, framebuffer_size);
}
}

View File

@@ -55,40 +55,6 @@ static uint32_t get_output_id_from_display_name(NVFBC_RANDR_OUTPUT_INFO *outputs
return 0;
}
/* TODO: Test with optimus and open kernel modules */
static bool get_driver_version(int *major, int *minor) {
*major = 0;
*minor = 0;
FILE *f = fopen("/proc/driver/nvidia/version", "rb");
if(!f) {
fprintf(stderr, "gsr warning: failed to get nvidia driver version (failed to read /proc/driver/nvidia/version)\n");
return false;
}
char buffer[2048];
size_t bytes_read = fread(buffer, 1, sizeof(buffer) - 1, f);
buffer[bytes_read] = '\0';
bool success = false;
const char *p = strstr(buffer, "Kernel Module");
if(p) {
p += 13;
int driver_major_version = 0, driver_minor_version = 0;
if(sscanf(p, "%d.%d", &driver_major_version, &driver_minor_version) == 2) {
*major = driver_major_version;
*minor = driver_minor_version;
success = true;
}
}
if(!success)
fprintf(stderr, "gsr warning: failed to get nvidia driver version\n");
fclose(f);
return success;
}
static bool version_at_least(int major, int minor, int expected_major, int expected_minor) {
return major > expected_major || (major == expected_major && minor >= expected_minor);
}
@@ -288,7 +254,7 @@ static int gsr_capture_nvfbc_start(gsr_capture *cap, gsr_capture_metadata *captu
self->supports_direct_cursor = false;
int driver_major_version = 0;
int driver_minor_version = 0;
if(self->params.direct_capture && get_driver_version(&driver_major_version, &driver_minor_version)) {
if(self->params.direct_capture && get_nvidia_driver_version(&driver_major_version, &driver_minor_version)) {
fprintf(stderr, "gsr info: detected nvidia version: %d.%d\n", driver_major_version, driver_minor_version);
// TODO:
@@ -317,16 +283,16 @@ static int gsr_capture_nvfbc_start(gsr_capture *cap, gsr_capture_metadata *captu
goto error_cleanup;
}
capture_metadata->width = self->tracking_width;
capture_metadata->height = self->tracking_height;
capture_metadata->video_width = self->tracking_width;
capture_metadata->video_height = self->tracking_height;
if(self->params.output_resolution.x > 0 && self->params.output_resolution.y > 0) {
self->params.output_resolution = scale_keep_aspect_ratio((vec2i){capture_metadata->width, capture_metadata->height}, self->params.output_resolution);
capture_metadata->width = self->params.output_resolution.x;
capture_metadata->height = self->params.output_resolution.y;
self->params.output_resolution = scale_keep_aspect_ratio((vec2i){capture_metadata->recording_width, capture_metadata->recording_height}, self->params.output_resolution);
capture_metadata->video_width = self->params.output_resolution.x;
capture_metadata->video_height = self->params.output_resolution.y;
} else if(self->params.region_size.x > 0 && self->params.region_size.y > 0) {
capture_metadata->width = self->params.region_size.x;
capture_metadata->height = self->params.region_size.y;
capture_metadata->video_width = self->params.region_size.x;
capture_metadata->video_height = self->params.region_size.y;
}
return 0;
@@ -367,11 +333,8 @@ static int gsr_capture_nvfbc_capture(gsr_capture *cap, gsr_capture_metadata *cap
if(self->params.region_size.x > 0 && self->params.region_size.y > 0)
frame_size = self->params.region_size;
const bool is_scaled = self->params.output_resolution.x > 0 && self->params.output_resolution.y > 0;
vec2i output_size = is_scaled ? self->params.output_resolution : frame_size;
output_size = scale_keep_aspect_ratio(frame_size, output_size);
const vec2i target_pos = { max_int(0, capture_metadata->width / 2 - output_size.x / 2), max_int(0, capture_metadata->height / 2 - output_size.y / 2) };
const vec2i output_size = scale_keep_aspect_ratio(frame_size, (vec2i){capture_metadata->recording_width, capture_metadata->recording_height});
const vec2i target_pos = { max_int(0, capture_metadata->video_width / 2 - output_size.x / 2), max_int(0, capture_metadata->video_height / 2 - output_size.y / 2) };
NVFBC_FRAME_GRAB_INFO frame_info;
memset(&frame_info, 0, sizeof(frame_info));
@@ -397,7 +360,7 @@ static int gsr_capture_nvfbc_capture(gsr_capture *cap, gsr_capture_metadata *cap
gsr_color_conversion_draw(color_conversion, self->setup_params.dwTextures[grab_params.dwTextureIndex],
target_pos, (vec2i){output_size.x, output_size.y},
self->params.region_position, frame_size, original_frame_size,
GSR_ROT_0, GSR_SOURCE_COLOR_BGR, false, false);
GSR_ROT_0, GSR_SOURCE_COLOR_BGR, false);
//self->params.egl->glFlush();
//self->params.egl->glFinish();

View File

@@ -293,12 +293,12 @@ static int gsr_capture_portal_start(gsr_capture *cap, gsr_capture_metadata *capt
}
if(self->params.output_resolution.x == 0 && self->params.output_resolution.y == 0) {
capture_metadata->width = self->capture_size.x;
capture_metadata->height = self->capture_size.y;
capture_metadata->video_width = self->capture_size.x;
capture_metadata->video_height = self->capture_size.y;
} else {
self->params.output_resolution = scale_keep_aspect_ratio(self->capture_size, self->params.output_resolution);
capture_metadata->width = self->params.output_resolution.x;
capture_metadata->height = self->params.output_resolution.y;
capture_metadata->video_width = self->params.output_resolution.x;
capture_metadata->video_height = self->params.output_resolution.y;
}
return 0;
@@ -353,11 +353,10 @@ static int gsr_capture_portal_capture(gsr_capture *cap, gsr_capture_metadata *ca
}
}
const bool is_scaled = self->params.output_resolution.x > 0 && self->params.output_resolution.y > 0;
vec2i output_size = is_scaled ? self->params.output_resolution : self->capture_size;
output_size = scale_keep_aspect_ratio(self->capture_size, output_size);
const vec2i target_pos = { max_int(0, capture_metadata->width / 2 - output_size.x / 2), max_int(0, capture_metadata->height / 2 - output_size.y / 2) };
const vec2i output_size = scale_keep_aspect_ratio(self->capture_size, (vec2i){capture_metadata->recording_width, capture_metadata->recording_height});
const vec2i target_pos = { max_int(0, capture_metadata->video_width / 2 - output_size.x / 2), max_int(0, capture_metadata->video_height / 2 - output_size.y / 2) };
const vec2i actual_texture_size = {self->pipewire_data.texture_width, self->pipewire_data.texture_height};
//self->params.egl->glFlush();
//self->params.egl->glFinish();
@@ -370,8 +369,8 @@ static int gsr_capture_portal_capture(gsr_capture *cap, gsr_capture_metadata *ca
gsr_color_conversion_draw(color_conversion, self->pipewire_data.using_external_image ? self->texture_map.external_texture_id : self->texture_map.texture_id,
target_pos, output_size,
(vec2i){self->pipewire_data.region.x, self->pipewire_data.region.y}, self->capture_size, self->capture_size,
gsr_monitor_rotation_to_rotation(self->pipewire_data.rotation), GSR_SOURCE_COLOR_RGB, self->pipewire_data.using_external_image, fourcc_alpha);
(vec2i){self->pipewire_data.region.x, self->pipewire_data.region.y}, (vec2i){self->pipewire_data.region.width, self->pipewire_data.region.height}, actual_texture_size,
gsr_monitor_rotation_to_rotation(self->pipewire_data.rotation), GSR_SOURCE_COLOR_RGB, self->pipewire_data.using_external_image);
if(self->params.record_cursor && self->texture_map.cursor_texture_id > 0 && self->pipewire_data.cursor_region.width > 0) {
const vec2d scale = {
@@ -392,7 +391,7 @@ static int gsr_capture_portal_capture(gsr_capture *cap, gsr_capture_metadata *ca
(vec2i){0, 0},
(vec2i){self->pipewire_data.cursor_region.width, self->pipewire_data.cursor_region.height},
(vec2i){self->pipewire_data.cursor_region.width, self->pipewire_data.cursor_region.height},
gsr_monitor_rotation_to_rotation(self->pipewire_data.rotation), GSR_SOURCE_COLOR_RGB, false, true);
gsr_monitor_rotation_to_rotation(self->pipewire_data.rotation), GSR_SOURCE_COLOR_RGB, false);
self->params.egl->glDisable(GL_SCISSOR_TEST);
}

View File

@@ -104,11 +104,11 @@ static int gsr_capture_xcomposite_start(gsr_capture *cap, gsr_capture_metadata *
self->texture_size.y = self->window_texture.window_height;
if(self->params.output_resolution.x == 0 && self->params.output_resolution.y == 0) {
capture_metadata->width = self->texture_size.x;
capture_metadata->height = self->texture_size.y;
capture_metadata->video_width = self->texture_size.x;
capture_metadata->video_height = self->texture_size.y;
} else {
capture_metadata->width = self->params.output_resolution.x;
capture_metadata->height = self->params.output_resolution.y;
capture_metadata->video_width = self->params.output_resolution.x;
capture_metadata->video_height = self->params.output_resolution.y;
}
self->window_resize_timer = clock_get_monotonic_seconds();
@@ -224,7 +224,7 @@ static bool gsr_capture_xcomposite_should_stop(gsr_capture *cap, bool *err) {
return false;
}
static int gsr_capture_xcomposite_capture(gsr_capture *cap, gsr_capture_metadata *capture_metdata, gsr_color_conversion *color_conversion) {
static int gsr_capture_xcomposite_capture(gsr_capture *cap, gsr_capture_metadata *capture_metadata, gsr_color_conversion *color_conversion) {
gsr_capture_xcomposite *self = cap->priv;
if(self->clear_background) {
@@ -232,11 +232,8 @@ static int gsr_capture_xcomposite_capture(gsr_capture *cap, gsr_capture_metadata
gsr_color_conversion_clear(color_conversion);
}
const bool is_scaled = self->params.output_resolution.x > 0 && self->params.output_resolution.y > 0;
vec2i output_size = is_scaled ? self->params.output_resolution : self->texture_size;
output_size = scale_keep_aspect_ratio(self->texture_size, output_size);
const vec2i target_pos = { max_int(0, capture_metdata->width / 2 - output_size.x / 2), max_int(0, capture_metdata->height / 2 - output_size.y / 2) };
const vec2i output_size = scale_keep_aspect_ratio(self->texture_size, (vec2i){capture_metadata->recording_width, capture_metadata->recording_height});
const vec2i target_pos = { max_int(0, capture_metadata->video_width / 2 - output_size.x / 2), max_int(0, capture_metadata->video_height / 2 - output_size.y / 2) };
//self->params.egl->glFlush();
//self->params.egl->glFinish();
@@ -244,7 +241,7 @@ static int gsr_capture_xcomposite_capture(gsr_capture *cap, gsr_capture_metadata
gsr_color_conversion_draw(color_conversion, window_texture_get_opengl_texture_id(&self->window_texture),
target_pos, output_size,
(vec2i){0, 0}, self->texture_size, self->texture_size,
GSR_ROT_0, GSR_SOURCE_COLOR_RGB, false, false);
GSR_ROT_0, GSR_SOURCE_COLOR_RGB, false);
if(self->params.record_cursor && self->cursor.visible) {
const vec2d scale = {
@@ -265,7 +262,7 @@ static int gsr_capture_xcomposite_capture(gsr_capture *cap, gsr_capture_metadata
gsr_color_conversion_draw(color_conversion, self->cursor.texture_id,
cursor_pos, (vec2i){self->cursor.size.x * scale.x, self->cursor.size.y * scale.y},
(vec2i){0, 0}, self->cursor.size, self->cursor.size,
GSR_ROT_0, GSR_SOURCE_COLOR_RGB, false, true);
GSR_ROT_0, GSR_SOURCE_COLOR_RGB, false);
}
//self->params.egl->glFlush();

View File

@@ -59,14 +59,14 @@ static int gsr_capture_ximage_start(gsr_capture *cap, gsr_capture_metadata *capt
if(self->params.output_resolution.x > 0 && self->params.output_resolution.y > 0) {
self->params.output_resolution = scale_keep_aspect_ratio(self->capture_size, self->params.output_resolution);
capture_metadata->width = self->params.output_resolution.x;
capture_metadata->height = self->params.output_resolution.y;
capture_metadata->video_width = self->params.output_resolution.x;
capture_metadata->video_height = self->params.output_resolution.y;
} else if(self->params.region_size.x > 0 && self->params.region_size.y > 0) {
capture_metadata->width = self->params.region_size.x;
capture_metadata->height = self->params.region_size.y;
capture_metadata->video_width = self->params.region_size.x;
capture_metadata->video_height = self->params.region_size.y;
} else {
capture_metadata->width = self->capture_size.x;
capture_metadata->height = self->capture_size.y;
capture_metadata->video_width = self->capture_size.x;
capture_metadata->video_height = self->capture_size.y;
}
self->texture_id = gl_create_texture(self->params.egl, self->capture_size.x, self->capture_size.y, GL_RGB8, GL_RGB, GL_LINEAR);
@@ -147,20 +147,17 @@ static bool gsr_capture_ximage_upload_to_texture(gsr_capture_ximage *self, int x
return success;
}
static int gsr_capture_ximage_capture(gsr_capture *cap, gsr_capture_metadata *capture_metdata, gsr_color_conversion *color_conversion) {
static int gsr_capture_ximage_capture(gsr_capture *cap, gsr_capture_metadata *capture_metadata, gsr_color_conversion *color_conversion) {
gsr_capture_ximage *self = cap->priv;
const bool is_scaled = self->params.output_resolution.x > 0 && self->params.output_resolution.y > 0;
vec2i output_size = is_scaled ? self->params.output_resolution : self->capture_size;
output_size = scale_keep_aspect_ratio(self->capture_size, output_size);
const vec2i target_pos = { max_int(0, capture_metdata->width / 2 - output_size.x / 2), max_int(0, capture_metdata->height / 2 - output_size.y / 2) };
const vec2i output_size = scale_keep_aspect_ratio(self->capture_size, (vec2i){capture_metadata->recording_width, capture_metadata->recording_height});
const vec2i target_pos = { max_int(0, capture_metadata->video_width / 2 - output_size.x / 2), max_int(0, capture_metadata->video_height / 2 - output_size.y / 2) };
gsr_capture_ximage_upload_to_texture(self, self->capture_pos.x + self->params.region_position.x, self->capture_pos.y + self->params.region_position.y, self->capture_size.x, self->capture_size.y);
gsr_color_conversion_draw(color_conversion, self->texture_id,
target_pos, output_size,
(vec2i){0, 0}, self->capture_size, self->capture_size,
GSR_ROT_0, GSR_SOURCE_COLOR_RGB, false, false);
GSR_ROT_0, GSR_SOURCE_COLOR_RGB, false);
if(self->params.record_cursor && self->cursor.visible) {
const vec2d scale = {
@@ -181,7 +178,7 @@ static int gsr_capture_ximage_capture(gsr_capture *cap, gsr_capture_metadata *ca
gsr_color_conversion_draw(color_conversion, self->cursor.texture_id,
cursor_pos, (vec2i){self->cursor.size.x * scale.x, self->cursor.size.y * scale.y},
(vec2i){0, 0}, self->cursor.size, self->cursor.size,
GSR_ROT_0, GSR_SOURCE_COLOR_RGB, false, true);
GSR_ROT_0, GSR_SOURCE_COLOR_RGB, false);
self->params.egl->glDisable(GL_SCISSOR_TEST);
}

View File

@@ -39,10 +39,6 @@
" 0.060118, 0.429412, -0.038049, 0.000000,\n" \
" 0.062745, 0.500000, 0.500000, 1.000000);\n"
static int max_int(int a, int b) {
return a > b ? a : b;
}
static const char* color_format_range_get_transform_matrix(gsr_destination_color color_format, gsr_color_range color_range) {
switch(color_format) {
case GSR_DESTINATION_COLOR_NV12: {
@@ -483,6 +479,12 @@ static void gsr_color_conversion_draw_graphics(gsr_color_conversion *self, unsig
const vec2i dest_texture_size = self->params.destination_textures_size[0];
const int texture_target = external_texture ? GL_TEXTURE_EXTERNAL_OES : GL_TEXTURE_2D;
if(rotation == GSR_ROT_90 || rotation == GSR_ROT_270) {
const float tmp = texture_size.x;
texture_size.x = texture_size.y;
texture_size.y = tmp;
}
self->params.egl->glBindTexture(texture_target, texture_id);
gsr_color_conversion_swizzle_texture_source(self, source_color);
@@ -501,18 +503,11 @@ static void gsr_color_conversion_draw_graphics(gsr_color_conversion *self, unsig
(float)source_position.y / (texture_size.y == 0 ? 1.0f : (float)texture_size.y),
};
vec2f texture_size_norm = {
const vec2f texture_size_norm = {
(float)source_size.x / (texture_size.x == 0 ? 1.0f : (float)texture_size.x),
(float)source_size.y / (texture_size.y == 0 ? 1.0f : (float)texture_size.y),
};
if(rotation == GSR_ROT_90 || rotation == GSR_ROT_270) {
const float ratio_x = (double)source_size.x / (double)source_size.y;
const float ratio_y = (double)source_size.y / (double)source_size.x;
texture_size_norm.x *= ratio_y;
texture_size_norm.y *= ratio_x;
}
const float vertices[] = {
-1.0f + 0.0f, -1.0f + 0.0f + size_norm.y, texture_pos_norm.x, texture_pos_norm.y + texture_size_norm.y,
-1.0f + 0.0f, -1.0f + 0.0f, texture_pos_norm.x, texture_pos_norm.y,
@@ -574,7 +569,7 @@ static void gsr_color_conversion_draw_graphics(gsr_color_conversion *self, unsig
self->params.egl->glBindFramebuffer(GL_FRAMEBUFFER, 0);
}
void gsr_color_conversion_draw(gsr_color_conversion *self, unsigned int texture_id, vec2i destination_pos, vec2i destination_size, vec2i source_pos, vec2i source_size, vec2i texture_size, gsr_rotation rotation, gsr_source_color source_color, bool external_texture, bool alpha_blending) {
void gsr_color_conversion_draw(gsr_color_conversion *self, unsigned int texture_id, vec2i destination_pos, vec2i destination_size, vec2i source_pos, vec2i source_size, vec2i texture_size, gsr_rotation rotation, gsr_source_color source_color, bool external_texture) {
assert(!external_texture || self->params.load_external_image_shader);
if(external_texture && !self->params.load_external_image_shader) {
fprintf(stderr, "gsr error: gsr_color_conversion_draw: external texture not loaded\n");

View File

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

View File

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

View File

@@ -48,7 +48,6 @@ extern "C" {
extern "C" {
#include <libavutil/pixfmt.h>
#include <libavcodec/avcodec.h>
#include <libavcodec/defs.h>
#include <libavformat/avformat.h>
#include <libavutil/opt.h>
#include <libswresample/swresample.h>
@@ -123,12 +122,12 @@ static void get_monitor_by_position_callback(const gsr_monitor *monitor, void *u
std::swap(monitor_size.x, monitor_size.y);
}
if(!data->output_name && data->position.x >= monitor_position.x && data->position.x <= monitor_position.x + monitor->size.x
&& data->position.y >= monitor_position.y && data->position.y <= monitor_position.y + monitor->size.y)
if(!data->output_name && data->position.x >= monitor_position.x && data->position.x <= monitor_position.x + monitor_size.x
&& data->position.y >= monitor_position.y && data->position.y <= monitor_position.y + monitor_size.y)
{
data->output_name = strdup(monitor->name);
data->monitor_pos = monitor_position;
data->monitor_size = monitor->size;
data->monitor_size = monitor_size;
}
}
@@ -381,8 +380,9 @@ static AVCodecContext *create_video_codec_context(AVPixelFormat pix_fmt, const A
codec_context->colorspace = AVCOL_SPC_BT709;
}
//codec_context->chroma_sample_location = AVCHROMA_LOC_CENTER;
if(codec->id == AV_CODEC_ID_HEVC)
codec_context->codec_tag = MKTAG('h', 'v', 'c', '1'); // QuickTime on MacOS requires this or the video wont be playable
// Can't use this because it's fucking broken in ffmpeg 8 or new mesa. It produces garbage output
//if(codec->id == AV_CODEC_ID_HEVC)
// codec_context->codec_tag = MKTAG('h', 'v', 'c', '1'); // QuickTime on MacOS requires this or the video wont be playable
if(arg_parser.bitrate_mode == GSR_BITRATE_MODE_CBR) {
codec_context->bit_rate = arg_parser.video_bitrate;
@@ -1268,6 +1268,11 @@ static RecordingStartAudio* get_recording_start_item_by_stream_index(RecordingSt
return nullptr;
}
struct AudioPtsOffset {
int64_t pts_offset = 0;
int stream_index = 0;
};
static void save_replay_async(AVCodecContext *video_codec_context, int video_stream_index, const std::vector<AudioTrack> &audio_tracks, gsr_replay_buffer *replay_buffer, std::string output_dir, const char *container_format, const std::string &file_extension, bool date_folders, bool hdr, gsr_capture *capture, int current_save_replay_seconds) {
if(save_replay_thread.valid())
return;
@@ -1279,14 +1284,15 @@ static void save_replay_async(AVCodecContext *video_codec_context, int video_str
return;
}
const gsr_replay_buffer_iterator audio_start_iterator = gsr_replay_buffer_find_keyframe(replay_buffer, video_start_iterator, video_stream_index, true);
// if(audio_start_index == (size_t)-1) {
// fprintf(stderr, "gsr error: failed to save replay: failed to find an audio keyframe. perhaps replay was saved too fast, before anything has been recorded\n");
// return;
// }
const int64_t video_pts_offset = gsr_replay_buffer_iterator_get_packet(replay_buffer, video_start_iterator)->pts;
const int64_t audio_pts_offset = audio_start_iterator.packet_index == (size_t)-1 ? 0 : gsr_replay_buffer_iterator_get_packet(replay_buffer, audio_start_iterator)->pts;
std::vector<AudioPtsOffset> audio_pts_offsets;
audio_pts_offsets.reserve(audio_tracks.size());
for(const AudioTrack &audio_track : audio_tracks) {
const gsr_replay_buffer_iterator audio_start_iterator = gsr_replay_buffer_find_keyframe(replay_buffer, video_start_iterator, audio_track.stream_index, false);
const int64_t audio_pts_offset = audio_start_iterator.packet_index == (size_t)-1 ? 0 : gsr_replay_buffer_iterator_get_packet(replay_buffer, audio_start_iterator)->pts;
audio_pts_offsets.push_back(AudioPtsOffset{audio_pts_offset, audio_track.stream_index});
}
gsr_replay_buffer *cloned_replay_buffer = gsr_replay_buffer_clone(replay_buffer);
if(!cloned_replay_buffer) {
@@ -1302,7 +1308,7 @@ static void save_replay_async(AVCodecContext *video_codec_context, int video_str
save_replay_output_filepath = std::move(output_filepath);
save_replay_thread = std::async(std::launch::async, [video_stream_index, recording_start_result, video_start_iterator, video_pts_offset, audio_pts_offset, video_codec_context, cloned_replay_buffer]() mutable {
save_replay_thread = std::async(std::launch::async, [video_stream_index, recording_start_result, video_start_iterator, video_pts_offset, audio_pts_offsets{std::move(audio_pts_offsets)}, video_codec_context, cloned_replay_buffer]() mutable {
gsr_replay_buffer_iterator replay_iterator = video_start_iterator;
for(;;) {
AVPacket *replay_packet = gsr_replay_buffer_iterator_get_packet(cloned_replay_buffer, replay_iterator);
@@ -1350,8 +1356,10 @@ static void save_replay_async(AVCodecContext *video_codec_context, int video_str
stream = recording_start_audio->stream;
codec_context = audio_track->codec_context;
av_packet.pts -= audio_pts_offset;
av_packet.dts -= audio_pts_offset;
const AudioPtsOffset &audio_pts_offset = audio_pts_offsets[av_packet.stream_index - 1];
assert(audio_pts_offset.stream_index == av_packet.stream_index);
av_packet.pts -= audio_pts_offset.pts_offset;
av_packet.dts -= audio_pts_offset.pts_offset;
}
//av_packet.stream_index = stream->index;
@@ -1637,6 +1645,41 @@ static bool get_supported_video_codecs(gsr_egl *egl, gsr_video_codec video_codec
return false;
}
static void force_cpu_encoding(args_parser *args_parser) {
args_parser->video_codec = GSR_VIDEO_CODEC_H264;
args_parser->video_encoder = GSR_VIDEO_ENCODER_HW_CPU;
if(args_parser->bitrate_mode == GSR_BITRATE_MODE_VBR) {
fprintf(stderr, "gsr warning: bitrate mode has been forcefully set to qp because software encoding option doesn't support vbr option\n");
args_parser->bitrate_mode = GSR_BITRATE_MODE_QP;
}
}
static bool get_supported_video_codecs_with_cpu_fallback(gsr_egl *egl, args_parser *args_parser, bool cleanup, gsr_supported_video_codecs *video_codecs) {
if(get_supported_video_codecs(egl, args_parser->video_codec, args_parser->video_encoder == GSR_VIDEO_ENCODER_HW_CPU, cleanup, video_codecs)) {
if(args_parser->video_encoder == GSR_VIDEO_ENCODER_HW_CPU || !args_parser->fallback_cpu_encoding)
return true;
else if(args_parser->video_encoder == GSR_VIDEO_ENCODER_HW_GPU && video_codecs->h264.supported && (args_parser->video_codec == (gsr_video_codec)GSR_VIDEO_CODEC_AUTO || args_parser->video_codec == GSR_VIDEO_CODEC_H264))
return true;
}
if(args_parser->video_encoder == GSR_VIDEO_ENCODER_HW_CPU || !args_parser->fallback_cpu_encoding)
return false;
fprintf(stderr, "gsr warning: gpu encoding is not available on your system, trying cpu encoding instead because -fallback-cpu-encoding is enabled. Install the proper vaapi drivers on your system (if supported) if you experience performance issues\n");
if(get_supported_video_codecs(egl, GSR_VIDEO_CODEC_H264, true, cleanup, video_codecs)) {
if(args_parser->video_codec != (gsr_video_codec)GSR_VIDEO_CODEC_AUTO && args_parser->video_codec != GSR_VIDEO_CODEC_H264) {
fprintf(stderr, "gsr warning: cpu encoding is used but video codec isn't set to h264. Forcing video codec to h264\n");
args_parser->video_codec = GSR_VIDEO_CODEC_H264;
}
force_cpu_encoding(args_parser);
return true;
}
return false;
}
static void xwayland_check_callback(const gsr_monitor *monitor, void *userdata) {
bool *xwayland_found = (bool*)userdata;
if(monitor->name_len >= 8 && strncmp(monitor->name, "XWAYLAND", 8) == 0)
@@ -2276,7 +2319,7 @@ static int video_quality_to_image_quality_value(gsr_video_quality video_quality)
case GSR_VIDEO_QUALITY_HIGH:
return 85;
case GSR_VIDEO_QUALITY_VERY_HIGH:
return 90;
return 91; // Quality above 90 makes the jpeg image encoder (stb_image_writer) use yuv444 instead of yuv420, which greatly improves small colored text quality on dark background
case GSR_VIDEO_QUALITY_ULTRA:
return 97;
}
@@ -2292,8 +2335,10 @@ static void capture_image_to_file(args_parser &arg_parser, gsr_egl *egl, gsr_ima
gsr_capture *capture = create_capture_impl(arg_parser, egl, prefer_ximage);
gsr_capture_metadata capture_metadata;
capture_metadata.width = 0;
capture_metadata.height = 0;
capture_metadata.video_width = 0;
capture_metadata.video_height = 0;
capture_metadata.recording_width = 0;
capture_metadata.recording_height = 0;
capture_metadata.fps = fps;
int capture_result = gsr_capture_start(capture, &capture_metadata);
@@ -2302,8 +2347,11 @@ static void capture_image_to_file(args_parser &arg_parser, gsr_egl *egl, gsr_ima
_exit(capture_result);
}
capture_metadata.recording_width = capture_metadata.video_width;
capture_metadata.recording_height = capture_metadata.video_height;
gsr_image_writer image_writer;
if(!gsr_image_writer_init_opengl(&image_writer, egl, capture_metadata.width, capture_metadata.height)) {
if(!gsr_image_writer_init_opengl(&image_writer, egl, capture_metadata.video_width, capture_metadata.video_height)) {
fprintf(stderr, "gsr error: capture_image_to_file_wayland: gsr_image_write_gl_init failed\n");
_exit(1);
}
@@ -2315,7 +2363,7 @@ static void capture_image_to_file(args_parser &arg_parser, gsr_egl *egl, gsr_ima
color_conversion_params.load_external_image_shader = gsr_capture_uses_external_image(capture);
color_conversion_params.destination_textures[0] = image_writer.texture;
color_conversion_params.destination_textures_size[0] = { capture_metadata.width, capture_metadata.height };
color_conversion_params.destination_textures_size[0] = { capture_metadata.video_width, capture_metadata.video_height };
color_conversion_params.num_destination_textures = 1;
color_conversion_params.destination_color = GSR_DESTINATION_COLOR_RGB8;
@@ -2706,37 +2754,49 @@ static bool codec_supports_resolution(vec2i codec_max_resolution, vec2i capture_
return codec_max_resolution.x >= capture_resolution.x && codec_max_resolution.y >= capture_resolution.y;
}
static const AVCodec* pick_video_codec(gsr_video_codec *video_codec, gsr_egl *egl, bool use_software_video_encoder, bool video_codec_auto, bool is_flv, bool *low_power, gsr_supported_video_codecs *supported_video_codecs) {
static void print_codec_error(gsr_video_codec video_codec) {
if(video_codec == (gsr_video_codec)GSR_VIDEO_CODEC_AUTO)
video_codec = GSR_VIDEO_CODEC_H264;
const char *video_codec_name = video_codec_to_string(video_codec);
fprintf(stderr, "gsr error: your gpu does not support '%s' video codec. If you are sure that your gpu does support '%s' video encoding and you are using an AMD/Intel GPU,\n"
" then make sure you have installed the GPU specific vaapi packages (intel-media-driver, libva-intel-driver, libva-mesa-driver and linux-firmware).\n"
" It's also possible that your distro has disabled hardware accelerated video encoding for '%s' video codec.\n"
" This may be the case on corporate distros such as Manjaro, Fedora or OpenSUSE.\n"
" You can test this by running 'vainfo | grep VAEntrypointEncSlice' to see if it matches any H264/HEVC/AV1/VP8/VP9 profile.\n"
" On such distros, you need to manually install mesa from source to enable H264/HEVC hardware acceleration, or use a more user friendly distro. Alternatively record with AV1 if supported by your GPU.\n"
" You can alternatively use the flatpak version of GPU Screen Recorder (https://flathub.org/apps/com.dec05eba.gpu_screen_recorder) which bypasses system issues with patented H264/HEVC codecs.\n"
" If your GPU doesn't support hardware accelerated video encoding then you can use '-fallback-cpu-encoding yes' option to encode with your cpu instead.\n", video_codec_name, video_codec_name, video_codec_name);
}
static const AVCodec* pick_video_codec(gsr_egl *egl, args_parser *args_parser, bool use_fallback_codec, bool *low_power, gsr_supported_video_codecs *supported_video_codecs) {
// TODO: software encoder for hevc, av1, vp8 and vp9
*low_power = false;
const AVCodec *video_codec_f = get_av_codec_if_supported(*video_codec, egl, use_software_video_encoder, supported_video_codecs);
const AVCodec *video_codec_f = get_av_codec_if_supported(args_parser->video_codec, egl, args_parser->video_encoder == GSR_VIDEO_ENCODER_HW_CPU, supported_video_codecs);
if(!video_codec_auto && !video_codec_f && !is_flv) {
switch(*video_codec) {
if(!video_codec_f && use_fallback_codec && args_parser->video_encoder != GSR_VIDEO_ENCODER_HW_CPU) {
switch(args_parser->video_codec) {
case GSR_VIDEO_CODEC_H264: {
fprintf(stderr, "gsr warning: selected video codec h264 is not supported, trying hevc instead\n");
*video_codec = GSR_VIDEO_CODEC_HEVC;
if(supported_video_codecs->hevc.supported)
video_codec_f = get_ffmpeg_video_codec(*video_codec, egl->gpu_info.vendor);
fprintf(stderr, "gsr error: selected video codec h264 is not supported\n");
if(args_parser->fallback_cpu_encoding) {
fprintf(stderr, "gsr warning: gpu encoding is not available on your system, trying cpu encoding instead because -fallback-cpu-encoding is enabled. Install the proper vaapi drivers on your system (if supported) if you experience performance issues\n");
force_cpu_encoding(args_parser);
}
break;
}
case GSR_VIDEO_CODEC_HEVC:
case GSR_VIDEO_CODEC_HEVC_HDR:
case GSR_VIDEO_CODEC_HEVC_10BIT: {
fprintf(stderr, "gsr warning: selected video codec hevc is not supported, trying h264 instead\n");
*video_codec = GSR_VIDEO_CODEC_H264;
if(supported_video_codecs->h264.supported)
video_codec_f = get_ffmpeg_video_codec(*video_codec, egl->gpu_info.vendor);
break;
args_parser->video_codec = GSR_VIDEO_CODEC_H264;
return pick_video_codec(egl, args_parser, true, low_power, supported_video_codecs);
}
case GSR_VIDEO_CODEC_AV1:
case GSR_VIDEO_CODEC_AV1_HDR:
case GSR_VIDEO_CODEC_AV1_10BIT: {
fprintf(stderr, "gsr warning: selected video codec av1 is not supported, trying h264 instead\n");
*video_codec = GSR_VIDEO_CODEC_H264;
if(supported_video_codecs->h264.supported)
video_codec_f = get_ffmpeg_video_codec(*video_codec, egl->gpu_info.vendor);
break;
args_parser->video_codec = GSR_VIDEO_CODEC_H264;
return pick_video_codec(egl, args_parser, true, low_power, supported_video_codecs);
}
case GSR_VIDEO_CODEC_VP8:
case GSR_VIDEO_CODEC_VP9:
@@ -2744,54 +2804,44 @@ static const AVCodec* pick_video_codec(gsr_video_codec *video_codec, gsr_egl *eg
break;
case GSR_VIDEO_CODEC_H264_VULKAN: {
fprintf(stderr, "gsr warning: selected video codec h264_vulkan is not supported, trying h264 instead\n");
*video_codec = GSR_VIDEO_CODEC_H264;
args_parser->video_codec = GSR_VIDEO_CODEC_H264;
// Need to do a query again because this time it's without vulkan
if(!get_supported_video_codecs(egl, *video_codec, use_software_video_encoder, true, supported_video_codecs)) {
if(!get_supported_video_codecs_with_cpu_fallback(egl, args_parser, true, supported_video_codecs)) {
fprintf(stderr, "gsr error: failed to query for supported video codecs\n");
print_codec_error(args_parser->video_codec);
_exit(11);
}
if(supported_video_codecs->h264.supported)
video_codec_f = get_ffmpeg_video_codec(*video_codec, egl->gpu_info.vendor);
break;
return pick_video_codec(egl, args_parser, true, low_power, supported_video_codecs);
}
case GSR_VIDEO_CODEC_HEVC_VULKAN: {
fprintf(stderr, "gsr warning: selected video codec hevc_vulkan is not supported, trying hevc instead\n");
*video_codec = GSR_VIDEO_CODEC_HEVC;
args_parser->video_codec = GSR_VIDEO_CODEC_HEVC;
// Need to do a query again because this time it's without vulkan
if(!get_supported_video_codecs(egl, *video_codec, use_software_video_encoder, true, supported_video_codecs)) {
if(!get_supported_video_codecs_with_cpu_fallback(egl, args_parser, true, supported_video_codecs)) {
fprintf(stderr, "gsr error: failed to query for supported video codecs\n");
print_codec_error(args_parser->video_codec);
_exit(11);
}
if(supported_video_codecs->hevc.supported)
video_codec_f = get_ffmpeg_video_codec(*video_codec, egl->gpu_info.vendor);
break;
return pick_video_codec(egl, args_parser, true, low_power, supported_video_codecs);
}
}
video_codec_f = get_av_codec_if_supported(args_parser->video_codec, egl, args_parser->video_encoder == GSR_VIDEO_ENCODER_HW_CPU, supported_video_codecs);
}
if(!video_codec_f) {
const char *video_codec_name = video_codec_to_string(*video_codec);
fprintf(stderr, "gsr error: your gpu does not support '%s' video codec. If you are sure that your gpu does support '%s' video encoding and you are using an AMD/Intel GPU,\n"
" then make sure you have installed the GPU specific vaapi packages (intel-media-driver, libva-intel-driver, libva-mesa-driver and linux-firmware).\n"
" It's also possible that your distro has disabled hardware accelerated video encoding for '%s' video codec.\n"
" This may be the case on corporate distros such as Manjaro, Fedora or OpenSUSE.\n"
" You can test this by running 'vainfo | grep VAEntrypointEncSlice' to see if it matches any H264/HEVC/AV1/VP8/VP9 profile.\n"
" On such distros, you need to manually install mesa from source to enable H264/HEVC hardware acceleration, or use a more user friendly distro. Alternatively record with AV1 if supported by your GPU.\n"
" You can alternatively use the flatpak version of GPU Screen Recorder (https://flathub.org/apps/com.dec05eba.gpu_screen_recorder) which bypasses system issues with patented H264/HEVC codecs.\n"
" Make sure you have mesa-extra freedesktop runtime installed when using the flatpak (this should be the default), which can be installed with this command:\n"
" flatpak install --system org.freedesktop.Platform.GL.default//23.08-extra\n"
" If your GPU doesn't support hardware accelerated video encoding then you can use '-encoder cpu' option to encode with your cpu instead.\n", video_codec_name, video_codec_name, video_codec_name);
print_codec_error(args_parser->video_codec);
_exit(54);
}
*low_power = video_codec_only_supports_low_power_mode(*supported_video_codecs, *video_codec);
*low_power = video_codec_only_supports_low_power_mode(*supported_video_codecs, args_parser->video_codec);
return video_codec_f;
}
/* Returns -1 if none is available */
static gsr_video_codec select_appropriate_video_codec_automatically(gsr_capture_metadata capture_metadata, const gsr_supported_video_codecs *supported_video_codecs) {
const vec2i capture_size = {capture_metadata.width, capture_metadata.height};
const vec2i capture_size = {capture_metadata.video_width, capture_metadata.video_height};
if(supported_video_codecs->h264.supported && codec_supports_resolution(supported_video_codecs->h264.max_resolution, capture_size)) {
fprintf(stderr, "gsr info: using h264 encoder because a codec was not specified\n");
return GSR_VIDEO_CODEC_H264;
@@ -2810,72 +2860,54 @@ static gsr_video_codec select_appropriate_video_codec_automatically(gsr_capture_
}
}
static const AVCodec* select_video_codec_with_fallback(gsr_capture_metadata capture_metadata, gsr_video_codec *video_codec, const char *file_extension, bool use_software_video_encoder, gsr_egl *egl, bool *low_power) {
static const AVCodec* select_video_codec_with_fallback(gsr_capture_metadata capture_metadata, args_parser *args_parser, const char *file_extension, gsr_egl *egl, bool *low_power) {
gsr_supported_video_codecs supported_video_codecs;
if(!get_supported_video_codecs(egl, *video_codec, use_software_video_encoder, true, &supported_video_codecs)) {
fprintf(stderr, "gsr error: failed to query for supported video codecs\n");
_exit(11);
}
get_supported_video_codecs(egl, args_parser->video_codec, args_parser->video_encoder == GSR_VIDEO_ENCODER_HW_CPU, true, &supported_video_codecs);
// TODO: Use gsr_supported_video_codecs *supported_video_codecs_vulkan here to properly query vulkan video support
set_supported_video_codecs_ffmpeg(&supported_video_codecs, nullptr, egl->gpu_info.vendor);
const bool video_codec_auto = *video_codec == (gsr_video_codec)GSR_VIDEO_CODEC_AUTO;
const bool video_codec_auto = args_parser->video_codec == (gsr_video_codec)GSR_VIDEO_CODEC_AUTO;
if(video_codec_auto) {
if(strcmp(file_extension, "webm") == 0) {
fprintf(stderr, "gsr info: using vp8 encoder because a codec was not specified and the file extension is .webm\n");
*video_codec = GSR_VIDEO_CODEC_VP8;
} else if(use_software_video_encoder) {
args_parser->video_codec = GSR_VIDEO_CODEC_VP8;
} else if(args_parser->video_encoder == GSR_VIDEO_ENCODER_HW_CPU) {
fprintf(stderr, "gsr info: using h264 encoder because a codec was not specified\n");
*video_codec = GSR_VIDEO_CODEC_H264;
} else {
*video_codec = select_appropriate_video_codec_automatically(capture_metadata, &supported_video_codecs);
if(*video_codec == (gsr_video_codec)-1) {
fprintf(stderr, "gsr error: no video encoder was specified and neither h264, hevc nor av1 are supported on your system or you are trying to capture at a resolution higher than your system supports for each codec\n");
_exit(52);
args_parser->video_codec = GSR_VIDEO_CODEC_H264;
} else if(args_parser->video_encoder != GSR_VIDEO_ENCODER_HW_CPU) {
args_parser->video_codec = select_appropriate_video_codec_automatically(capture_metadata, &supported_video_codecs);
if(args_parser->video_codec == (gsr_video_codec)-1) {
if(args_parser->fallback_cpu_encoding) {
fprintf(stderr, "gsr warning: gpu encoding is not available on your system or your gpu doesn't support recording at the resolution you are trying to record, trying cpu encoding instead because -fallback-cpu-encoding is enabled. Install the proper vaapi drivers on your system (if supported) if you experience performance issues\n");
force_cpu_encoding(args_parser);
} else {
fprintf(stderr, "gsr error: no video encoder was specified and neither h264, hevc nor av1 are supported on your system or you are trying to capture at a resolution higher than your system supports for each codec.\n");
fprintf(stderr, " Ensure that you have installed the proper vaapi driver. If your gpu doesn't support video encoding then you can run gpu-screen-recorder with \"-fallback-cpu-encoding yes\" option to use cpu encoding.\n");
_exit(52);
}
}
}
}
// TODO: Allow hevc, vp9 and av1 in (enhanced) flv (supported since ffmpeg 6.1)
const bool is_flv = strcmp(file_extension, "flv") == 0;
if(is_flv) {
if(*video_codec != GSR_VIDEO_CODEC_H264) {
*video_codec = GSR_VIDEO_CODEC_H264;
fprintf(stderr, "gsr warning: hevc/av1 is not compatible with flv, falling back to h264 instead.\n");
if(LIBAVFORMAT_VERSION_INT < AV_VERSION_INT(60, 10, 100) && strcmp(file_extension, "flv") == 0) {
if(args_parser->video_codec != GSR_VIDEO_CODEC_H264) {
args_parser->video_codec = GSR_VIDEO_CODEC_H264;
fprintf(stderr, "gsr warning: hevc/av1 is not compatible with flv in your outdated version of ffmpeg, falling back to h264 instead.\n");
}
// if(audio_codec != GSR_AUDIO_CODEC_AAC) {
// audio_codec_to_use = "aac";
// audio_codec = GSR_AUDIO_CODEC_AAC;
// fprintf(stderr, "gsr warning: flv only supports aac, falling back to aac instead.\n");
// }
}
const bool is_hls = strcmp(file_extension, "m3u8") == 0;
if(is_hls) {
if(video_codec_is_av1(*video_codec)) {
*video_codec = GSR_VIDEO_CODEC_HEVC;
} else if(strcmp(file_extension, "m3u8") == 0) {
if(video_codec_is_av1(args_parser->video_codec)) {
args_parser->video_codec = GSR_VIDEO_CODEC_HEVC;
fprintf(stderr, "gsr warning: av1 is not compatible with hls (m3u8), falling back to hevc instead.\n");
}
// if(audio_codec != GSR_AUDIO_CODEC_AAC) {
// audio_codec_to_use = "aac";
// audio_codec = GSR_AUDIO_CODEC_AAC;
// fprintf(stderr, "gsr warning: hls (m3u8) only supports aac, falling back to aac instead.\n");
// }
}
if(use_software_video_encoder && *video_codec != GSR_VIDEO_CODEC_H264) {
fprintf(stderr, "gsr error: \"-encoder cpu\" option is currently only available when using h264 codec option (-k)\n");
args_parser_print_usage();
_exit(1);
}
const AVCodec *codec = pick_video_codec(egl, args_parser, true, low_power, &supported_video_codecs);
const AVCodec *codec = pick_video_codec(video_codec, egl, use_software_video_encoder, video_codec_auto, is_flv, low_power, &supported_video_codecs);
const vec2i codec_max_resolution = codec_get_max_resolution(*video_codec, use_software_video_encoder, &supported_video_codecs);
const vec2i capture_size = {capture_metadata.width, capture_metadata.height};
const vec2i codec_max_resolution = codec_get_max_resolution(args_parser->video_codec, args_parser->video_encoder == GSR_VIDEO_ENCODER_HW_CPU, &supported_video_codecs);
const vec2i capture_size = {capture_metadata.video_width, capture_metadata.video_height};
if(!codec_supports_resolution(codec_max_resolution, capture_size)) {
const char *video_codec_name = video_codec_to_string(*video_codec);
const char *video_codec_name = video_codec_to_string(args_parser->video_codec);
fprintf(stderr, "gsr error: The max resolution for video codec %s is %dx%d while you are trying to capture at resolution %dx%d. Change capture resolution or video codec and try again\n",
video_codec_name, codec_max_resolution.x, codec_max_resolution.y, capture_size.x, capture_size.y);
_exit(53);
@@ -3073,6 +3105,11 @@ int main(int argc, char **argv) {
set_display_server_environment_variables();
// Linux nvidia driver 580.105.08 added the environment variable CUDA_DISABLE_PERF_BOOST to disable the p2 power level issue,
// where running cuda (which includes nvenc) causes the gpu to be forcefully set to p2 power level which on many nvidia gpus
// decreases gpu performance in games. On my GTX 1080 it decreased game performance by 10% for absolutely no reason.
// TODO: This only seems to allow the gpu to go to lower power level states, but not higher than p2.
setenv("CUDA_DISABLE_PERF_BOOST", "1", true);
// Stop nvidia driver from buffering frames
setenv("__GL_MaxFramesAllowed", "1", true);
// If this is set to 1 then cuGraphicsGLRegisterImage will fail for egl context with error: invalid OpenGL or DirectX context,
@@ -3110,6 +3147,15 @@ int main(int argc, char **argv) {
if(!args_parser_parse(&arg_parser, argc, argv, &arg_handlers, NULL))
_exit(1);
if(arg_parser.overclock) {
int driver_major_version = 0;
int driver_minor_version = 0;
if(get_nvidia_driver_version(&driver_major_version, &driver_minor_version) && (driver_major_version > 580 || (driver_major_version == 580 && driver_minor_version >= 105))) {
fprintf(stderr, "gsr info: overclocking was set by has been forcefully disabled since your gpu supports CUDA_DISABLE_PERF_BOOST to workaround driver issue (overclocking is not needed)\n");
arg_parser.overclock = false;
}
}
//av_log_set_level(AV_LOG_TRACE);
const Arg *audio_input_arg = args_parser_get_arg(&arg_parser, "-a");
@@ -3259,8 +3305,10 @@ int main(int argc, char **argv) {
gsr_capture *capture = create_capture_impl(arg_parser, &egl, false);
gsr_capture_metadata capture_metadata;
capture_metadata.width = 0;
capture_metadata.height = 0;
capture_metadata.video_width = 0;
capture_metadata.video_height = 0;
capture_metadata.recording_width = 0;
capture_metadata.recording_height = 0;
capture_metadata.fps = arg_parser.fps;
int capture_result = gsr_capture_start(capture, &capture_metadata);
@@ -3281,11 +3329,16 @@ int main(int argc, char **argv) {
AVStream *video_stream = nullptr;
std::vector<AudioTrack> audio_tracks;
if(arg_parser.video_encoder == GSR_VIDEO_ENCODER_HW_CPU && arg_parser.video_codec != (gsr_video_codec)GSR_VIDEO_CODEC_AUTO && arg_parser.video_codec != GSR_VIDEO_CODEC_H264) {
fprintf(stderr, "gsr error: -encoder cpu was specified but a codec other than h264 was specified. -encoder cpu supports only h264 at the moment\n");
_exit(1);
}
bool low_power = false;
const AVCodec *video_codec_f = select_video_codec_with_fallback(capture_metadata, &arg_parser.video_codec, file_extension.c_str(), arg_parser.video_encoder == GSR_VIDEO_ENCODER_HW_CPU, &egl, &low_power);
const AVCodec *video_codec_f = select_video_codec_with_fallback(capture_metadata, &arg_parser, file_extension.c_str(), &egl, &low_power);
const enum AVPixelFormat video_pix_fmt = get_pixel_format(arg_parser.video_codec, egl.gpu_info.vendor, arg_parser.video_encoder == GSR_VIDEO_ENCODER_HW_CPU);
AVCodecContext *video_codec_context = create_video_codec_context(video_pix_fmt, video_codec_f, egl, arg_parser, capture_metadata.width, capture_metadata.height);
AVCodecContext *video_codec_context = create_video_codec_context(video_pix_fmt, video_codec_f, egl, arg_parser, capture_metadata.video_width, capture_metadata.video_height);
if(!is_replaying)
video_stream = create_stream(av_format_context, video_codec_context);
@@ -3295,8 +3348,8 @@ int main(int argc, char **argv) {
_exit(1);
}
video_frame->format = video_codec_context->pix_fmt;
video_frame->width = capture_metadata.width;
video_frame->height = capture_metadata.height;
video_frame->width = capture_metadata.video_width;
video_frame->height = capture_metadata.video_height;
video_frame->color_range = video_codec_context->color_range;
video_frame->color_primaries = video_codec_context->color_primaries;
video_frame->color_trc = video_codec_context->color_trc;
@@ -3321,9 +3374,12 @@ int main(int argc, char **argv) {
_exit(1);
}
capture_metadata.recording_width = capture_metadata.video_width;
capture_metadata.recording_height = capture_metadata.video_height;
// TODO: What if this updated resolution is above max resolution?
capture_metadata.width = video_codec_context->width;
capture_metadata.height = video_codec_context->height;
capture_metadata.video_width = video_codec_context->width;
capture_metadata.video_height = video_codec_context->height;
const Arg *plugin_arg = args_parser_get_arg(&arg_parser, "-p");
assert(plugin_arg);
@@ -3336,8 +3392,8 @@ int main(int argc, char **argv) {
assert(color_depth == GSR_COLOR_DEPTH_8_BITS || color_depth == GSR_COLOR_DEPTH_10_BITS);
const gsr_plugin_init_params plugin_init_params = {
(unsigned int)capture_metadata.width,
(unsigned int)capture_metadata.height,
(unsigned int)capture_metadata.video_width,
(unsigned int)capture_metadata.video_height,
(unsigned int)arg_parser.fps,
color_depth == GSR_COLOR_DEPTH_8_BITS ? GSR_PLUGIN_COLOR_DEPTH_8_BITS : GSR_PLUGIN_COLOR_DEPTH_10_BITS,
egl.context_type == GSR_GL_CONTEXT_TYPE_GLX ? GSR_PLUGIN_GRAPHICS_API_GLX : GSR_PLUGIN_GRAPHICS_API_EGL_ES,
@@ -3777,9 +3833,9 @@ int main(int argc, char **argv) {
if(plugins.num_plugins > 0) {
gsr_plugins_draw(&plugins);
gsr_color_conversion_draw(&color_conversion, plugins.texture,
{0, 0}, {capture_metadata.width, capture_metadata.height},
{0, 0}, {capture_metadata.width, capture_metadata.height},
{capture_metadata.width, capture_metadata.height}, GSR_ROT_0, GSR_SOURCE_COLOR_RGB, false, true);
{0, 0}, {capture_metadata.video_width, capture_metadata.video_height},
{0, 0}, {capture_metadata.video_width, capture_metadata.video_height},
{capture_metadata.video_width, capture_metadata.video_height}, GSR_ROT_0, GSR_SOURCE_COLOR_RGB, false);
}
if(capture_has_synchronous_task) {

View File

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

View File

@@ -148,6 +148,8 @@ int get_connector_type_by_name(const char *name) {
return 3;
else if(len >= 4 && strncmp(name, "eDP-", 4) == 0)
return 4;
else if(len >= 4 && strncmp(name, "DVI-", 4) == 0)
return 5;
else
return -1;
}
@@ -606,3 +608,37 @@ unsigned int gl_create_texture(gsr_egl *egl, int width, int height, int internal
egl->glBindTexture(GL_TEXTURE_2D, 0);
return texture_id;
}
/* TODO: Test with optimus and open kernel modules */
bool get_nvidia_driver_version(int *major, int *minor) {
*major = 0;
*minor = 0;
FILE *f = fopen("/proc/driver/nvidia/version", "rb");
if(!f) {
fprintf(stderr, "gsr warning: failed to get nvidia driver version (failed to read /proc/driver/nvidia/version)\n");
return false;
}
char buffer[2048];
size_t bytes_read = fread(buffer, 1, sizeof(buffer) - 1, f);
buffer[bytes_read] = '\0';
bool success = false;
const char *p = strstr(buffer, "Kernel Module");
if(p) {
p += 13;
int driver_major_version = 0, driver_minor_version = 0;
if(sscanf(p, "%d.%d", &driver_major_version, &driver_minor_version) == 2) {
*major = driver_major_version;
*minor = driver_minor_version;
success = true;
}
}
if(!success)
fprintf(stderr, "gsr warning: failed to get nvidia driver version\n");
fclose(f);
return success;
}