mirror of
https://repo.dec05eba.com/gpu-screen-recorder
synced 2026-04-05 11:06:40 +09:00
Compare commits
11 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d3235a0be0 | ||
|
|
d4ee27716a | ||
|
|
fcb45b82f2 | ||
|
|
59d16899ab | ||
|
|
f3fb8c4a93 | ||
|
|
c073d43e30 | ||
|
|
a1ef9eec2e | ||
|
|
5a93d292ea | ||
|
|
48932dfdfb | ||
|
|
88ab64127e | ||
|
|
b500704008 |
21
README.md
21
README.md
@@ -67,7 +67,7 @@ Here are some known unofficial packages:
|
||||
* Debian/Ubuntu: [Pacstall](https://pacstall.dev/packages/gpu-screen-recorder)
|
||||
* Nix: [NixOS wiki](https://wiki.nixos.org/wiki/Gpu-screen-recorder)
|
||||
* openSUSE: [openSUSE software repository](https://software.opensuse.org/package/gpu-screen-recorder)
|
||||
* Fedora: [Copr](https://copr.fedorainfracloud.org/coprs/brycensranch/gpu-screen-recorder-git/)
|
||||
* Fedora, CentOS: [Copr](https://copr.fedorainfracloud.org/coprs/brycensranch/gpu-screen-recorder-git/)
|
||||
* OpenMandriva: [gpu-screen-recorder](https://github.com/OpenMandrivaAssociation/gpu-screen-recorder)
|
||||
* Solus: [gpu-screen-recorder](https://github.com/getsolus/packages/tree/main/packages/g/gpu-screen-recorder)
|
||||
* Nobara: [Nobara wiki](https://wiki.nobaraproject.org/en/general-usage/additional-software/GPU-Screen-Recorder)
|
||||
@@ -108,7 +108,7 @@ There are also additional dependencies needed at runtime depending on your GPU v
|
||||
* xnvctrl (libxnvctrl0, when using the `-oc` option)
|
||||
|
||||
## Optional dependencies
|
||||
When compiling GPU Screen Recorder with portal support (`-Dportal=true`, which is enabled by default) these dependencies are also needed:
|
||||
When building GPU Screen Recorder with portal support (`-Dportal=true` meson option, which is enabled by default) these dependencies are also needed:
|
||||
* libdbus
|
||||
* libpipewire (and libspa which is usually part of libpipewire)
|
||||
|
||||
@@ -159,6 +159,21 @@ If you installed GPU Screen Recorder from AUR or from source and you are running
|
||||
It's configured with `$HOME/.config/gpu-screen-recorder.env` (create it if it doesn't exist). You can look at [extra/gpu-screen-recorder.env](https://git.dec05eba.com/gpu-screen-recorder/plain/extra/gpu-screen-recorder.env) to see an example.
|
||||
You can see which variables that you can use in the `gpu-screen-recorder.env` file by looking at the `extra/gpu-screen-recorder.service` file. Note that all of the variables are optional, you only have to set the ones that are you interested in.
|
||||
You can use the `scripts/save-replay.sh` script to save a replay and by default the systemd service saves videos in `$HOME/Videos`.
|
||||
## Run a script when a video is saved
|
||||
Run `gpu-screen-recorder` with the `-sc` option to specify a script that should be run when a recording/replay a saved, for example `gpu-screen-recorder -w screen -sc ./script.sh -o video.mp4`.\
|
||||
The first argument to the script is the file path to the saved video. The second argument is either "regular" for regular recordings, "replay" for replays or "screenshot" for screenshots.\
|
||||
This can be used to for example showing a notification with the name of video or moving a video to a folder based on the name of the game that was recorded.
|
||||
## Plugins
|
||||
GPU Screen Recorder supports plugins for rendering additional graphics on top of the monitor/window capture. The plugin interface is defined in `plugin/plugin.h` and it gets installed to `gsr/plugin.h` in the systems include directory (usually `/usr/include`).
|
||||
An example plugin can be found at `plugin/examples/hello_triangle`.\
|
||||
Run `gpu-screen-recorder` with the `-p` option to specify a plugin to load, for example `gpu-screen-recorder -w screen -p ./triangle.so -o video.mp4`.
|
||||
`-p` can be specified multiple times to load multiple plugins.\
|
||||
Build GPU Screen Recorder with the `-Dplugin_examples=true` meson option to build plugin examples.
|
||||
## Smoother recording
|
||||
If you record at your monitors refresh rate and enabled vsync in a game then there might be a desync between the game updating a frame and GPU Screen Recorder capturing a frame.
|
||||
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.
|
||||
@@ -213,4 +228,4 @@ then GPU Screen Recorder will automatically use that same GPU for recording and
|
||||
## 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.
|
||||
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.
|
||||
|
||||
23
TODO
23
TODO
@@ -71,26 +71,16 @@ Test if p2 state can be worked around by using pure nvenc api and overwriting cu
|
||||
|
||||
Drop frames if live streaming cant keep up with target fps, or dynamically change resolution/quality.
|
||||
|
||||
Support low power option.
|
||||
|
||||
Instead of sending a big list of drm data back to kms client, send the monitor we want to record to kms server and the server should respond with only the matching monitor, and cursor.
|
||||
|
||||
Tonemap hdr to sdr when hdr is enabled and when hevc_hdr/av1_hdr is not used.
|
||||
|
||||
Add 10 bit record option, h264_10bit, hevc_10bit and av1_10bit.
|
||||
|
||||
Rotate cursor texture properly (around top left origin).
|
||||
|
||||
Setup hardware video context so we can query constraints and capabilities for better default and better error messages.
|
||||
|
||||
Use CAP_SYS_NICE in flatpak too on the main gpu screen recorder binary. It makes recording smoother, especially with constant framerate.
|
||||
|
||||
Modify ffmpeg to accept opengl texture for nvenc encoding. Removes extra buffers and copies.
|
||||
|
||||
When vulkan encode is added, mention minimum nvidia driver required. (550.54.14?).
|
||||
|
||||
Support drm plane rotation. Neither X11 nor any Wayland compositor currently rotates drm planes so this might not be needed.
|
||||
|
||||
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?
|
||||
|
||||
@@ -104,6 +94,9 @@ Enable b-frames.
|
||||
|
||||
Support vfr matching games exact fps all the time. On x11 use damage tracking, on wayland? maybe there is drm plane damage tracking. But that may not be accurate as the compositor may update it every monitor hz anyways. On wayland maybe only support it for desktop portal + pipewire capture.
|
||||
Another method to track damage that works regardless of the display server would be to do a diff between frames with a shader.
|
||||
A 1x1 texture could be created and then write to the texture with imageStore in glsl.
|
||||
Multiple textures aren't needed for diff, the diff between the color conversion output can be done by using it as an input
|
||||
as well, which would diff it against the previous frame.
|
||||
|
||||
Support selecting which gpu to use. This can be done in egl with eglQueryDevicesEXT and then eglGetPlatformDisplayEXT. This will automatically work on AMD and Intel as vaapi uses the same device. On nvidia we need to use eglQueryDeviceAttribEXT with EGL_CUDA_DEVICE_NV.
|
||||
Maybe on glx (nvidia x11 nvfbc) we need to use __NV_PRIME_RENDER_OFFLOAD, __NV_PRIME_RENDER_OFFLOAD_PROVIDER, __GLX_VENDOR_LIBRARY_NAME, __VK_LAYER_NV_optimus, VK_ICD_FILENAMES instead. Just look at prime-run /usr/bin/prime-run.
|
||||
@@ -321,4 +314,12 @@ kde plasma portal capture for screenshot doesn't work well because the portal ui
|
||||
It's possible for microphone audio to get desynced when recording together with desktop audio, when not recording app audio as well.
|
||||
Test recording desktop audio and microphone audio together (-a "default_output|default_input") for around 30 minutes.
|
||||
|
||||
We can use dri2connect/dri3open to get the /dev/dri/card device. Note that this doesn't work on nvidia x11.
|
||||
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).
|
||||
|
||||
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.
|
||||
|
||||
Check if pipewire audio link-factory is available before attempting to use app audio or merging audio with pipewire.
|
||||
Also do the same in supports_app_audio check in gpu-screen-recorder --info output.
|
||||
@@ -8,7 +8,7 @@
|
||||
|
||||
typedef struct gsr_egl gsr_egl;
|
||||
|
||||
#define NUM_ARGS 30
|
||||
#define NUM_ARGS 31
|
||||
|
||||
typedef enum {
|
||||
ARG_TYPE_STRING,
|
||||
|
||||
@@ -105,6 +105,7 @@ typedef void(*__GLXextFuncPtr)(void);
|
||||
#define GL_RGBA 0x1908
|
||||
#define GL_RGB8 0x8051
|
||||
#define GL_RGBA8 0x8058
|
||||
#define GL_RGBA16 0x805B
|
||||
#define GL_R8 0x8229
|
||||
#define GL_RG8 0x822B
|
||||
#define GL_R16 0x822A
|
||||
|
||||
38
include/plugins.h
Normal file
38
include/plugins.h
Normal file
@@ -0,0 +1,38 @@
|
||||
#ifndef GSR_PLUGINS_H
|
||||
#define GSR_PLUGINS_H
|
||||
|
||||
#include "../plugin/plugin.h"
|
||||
#include <stdbool.h>
|
||||
#include "color_conversion.h"
|
||||
|
||||
#define GSR_MAX_PLUGINS 128
|
||||
|
||||
typedef bool (*gsr_plugin_init_func)(const gsr_plugin_init_params *params, gsr_plugin_init_return *ret);
|
||||
typedef void (*gsr_plugin_deinit_func)(void *userdata);
|
||||
|
||||
typedef struct {
|
||||
gsr_plugin_init_return data;
|
||||
void *lib;
|
||||
gsr_plugin_init_func gsr_plugin_init;
|
||||
gsr_plugin_deinit_func gsr_plugin_deinit;
|
||||
} gsr_plugin;
|
||||
|
||||
typedef struct {
|
||||
gsr_plugin plugins[GSR_MAX_PLUGINS];
|
||||
int num_plugins;
|
||||
|
||||
gsr_plugin_init_params init_params;
|
||||
gsr_egl *egl;
|
||||
|
||||
unsigned int texture;
|
||||
gsr_color_conversion color_conversion;
|
||||
} gsr_plugins;
|
||||
|
||||
bool gsr_plugins_init(gsr_plugins *self, gsr_plugin_init_params init_params, gsr_egl *egl);
|
||||
/* Plugins are unloaded in reverse order */
|
||||
void gsr_plugins_deinit(gsr_plugins *self);
|
||||
|
||||
bool gsr_plugins_load_plugin(gsr_plugins *self, const char *plugin_filepath);
|
||||
void gsr_plugins_draw(gsr_plugins *self);
|
||||
|
||||
#endif /* GSR_PLUGINS_H */
|
||||
16
meson.build
16
meson.build
@@ -1,4 +1,4 @@
|
||||
project('gpu-screen-recorder', ['c', 'cpp'], version : '5.6.5', default_options : ['warning_level=2'])
|
||||
project('gpu-screen-recorder', ['c', 'cpp'], version : '5.6.7', default_options : ['warning_level=2'])
|
||||
|
||||
add_project_arguments('-Wshadow', language : ['c', 'cpp'])
|
||||
if get_option('buildtype') == 'debug'
|
||||
@@ -43,6 +43,7 @@ src = [
|
||||
'src/image_writer.c',
|
||||
'src/args_parser.c',
|
||||
'src/defs.c',
|
||||
'src/plugins.c',
|
||||
'src/sound.cpp',
|
||||
'src/main.cpp',
|
||||
]
|
||||
@@ -50,8 +51,12 @@ src = [
|
||||
subdir('protocol')
|
||||
src += protocol_src
|
||||
|
||||
cc = meson.get_compiler('c')
|
||||
m_dep = cc.find_library('m', required : true)
|
||||
|
||||
dep = [
|
||||
dependency('threads'),
|
||||
m_dep,
|
||||
dependency('libavcodec'),
|
||||
dependency('libavformat'),
|
||||
dependency('libavutil'),
|
||||
@@ -109,6 +114,8 @@ add_project_arguments('-DGSR_VERSION="' + meson.project_version() + '"', languag
|
||||
executable('gsr-kms-server', 'kms/server/kms_server.c', dependencies : dependency('libdrm'), c_args : '-fstack-protector-all', install : true)
|
||||
executable('gpu-screen-recorder', src, dependencies : dep, install : true)
|
||||
|
||||
install_headers('plugin/plugin.h', install_dir : 'include/gsr')
|
||||
|
||||
if get_option('systemd') == true
|
||||
install_data(files('extra/gpu-screen-recorder.service'), install_dir : 'lib/systemd/user')
|
||||
endif
|
||||
@@ -120,3 +127,10 @@ endif
|
||||
if get_option('nvidia_suspend_fix') == true
|
||||
install_data(files('extra/gsr-nvidia.conf'), install_dir : 'lib/modprobe.d')
|
||||
endif
|
||||
|
||||
if get_option('plugin_examples') == true
|
||||
shared_library('triangle', 'plugin/examples/hello_triangle/triangle.c',
|
||||
dependencies: [dependency('gl'), m_dep],
|
||||
name_prefix : '',
|
||||
name_suffix: 'so')
|
||||
endif
|
||||
|
||||
@@ -3,3 +3,4 @@ option('capabilities', type : 'boolean', value : true, description : 'Set binary
|
||||
option('nvidia_suspend_fix', type : 'boolean', value : true, description : 'Install nvidia modprobe config file to tell nvidia driver to preserve video memory on suspend. This is a workaround for an nvidia driver bug that breaks cuda (and gpu screen recorder) on suspend')
|
||||
option('portal', type : 'boolean', value : true, description : 'Build with support for xdg desktop portal ScreenCast capture (wayland only) (-w portal option). Requires pipewire')
|
||||
option('app_audio', type : 'boolean', value : true, description : 'Build with support for recording a single audio source (-a app: option). Requires pipewire')
|
||||
option('plugin_examples', type : 'boolean', value : false, description : 'Build plugin examples')
|
||||
|
||||
108
plugin/examples/hello_triangle/triangle.c
Normal file
108
plugin/examples/hello_triangle/triangle.c
Normal file
@@ -0,0 +1,108 @@
|
||||
#include <gsr/plugin.h>
|
||||
#include <stddef.h>
|
||||
#include <stdlib.h>
|
||||
#include <math.h>
|
||||
#define GL_GLEXT_PROTOTYPES
|
||||
#include <GL/gl.h>
|
||||
|
||||
const char vertex_shader_source[] =
|
||||
"attribute vec4 vertex_pos; \n"
|
||||
"void main() { \n"
|
||||
" gl_Position = vertex_pos; \n"
|
||||
"}";
|
||||
|
||||
const char fragment_shader_source[] =
|
||||
"precision mediump float; \n"
|
||||
"uniform vec3 color; \n"
|
||||
"void main() { \n"
|
||||
" gl_FragColor = vec4(color, 1.0); \n"
|
||||
"}";
|
||||
|
||||
typedef struct {
|
||||
GLuint shader_program;
|
||||
GLuint vao;
|
||||
GLuint vbo;
|
||||
GLint color_uniform;
|
||||
unsigned int counter;
|
||||
} Triangle;
|
||||
|
||||
static GLuint load_shader(const char *shaderSrc, GLenum type) {
|
||||
GLuint shader = glCreateShader(type);
|
||||
glShaderSource(shader, 1, &shaderSrc, NULL);
|
||||
glCompileShader(shader);
|
||||
return shader;
|
||||
}
|
||||
|
||||
static void draw(const gsr_plugin_draw_params *params, void *userdata) {
|
||||
Triangle *triangle = userdata;
|
||||
|
||||
GLfloat glverts[6];
|
||||
|
||||
glverts[0] = -0.5f;
|
||||
glverts[1] = -0.5f;
|
||||
|
||||
glverts[2] = 0.5f;
|
||||
glverts[3] = -0.5f;
|
||||
|
||||
glverts[4] = 0.0f;
|
||||
glverts[5] = 0.5f;
|
||||
|
||||
glBindVertexArray(triangle->vao);
|
||||
glBindBuffer(GL_ARRAY_BUFFER, triangle->vbo);
|
||||
glBufferSubData(GL_ARRAY_BUFFER, 0, 6 * sizeof(float), glverts);
|
||||
|
||||
glUseProgram(triangle->shader_program);
|
||||
const double pp = triangle->counter * 0.05;
|
||||
glUniform3f(triangle->color_uniform, 0.5 + sin(pp)*0.5, 0.5 + cos(pp)*0.5, 0.5 + sin(0.2 + pp)*0.5);
|
||||
glDrawArrays(GL_TRIANGLES, 0, 3);
|
||||
|
||||
glBindVertexArray(0);
|
||||
glUseProgram(0);
|
||||
|
||||
++triangle->counter;
|
||||
}
|
||||
|
||||
bool gsr_plugin_init(const gsr_plugin_init_params *params, gsr_plugin_init_return *ret) {
|
||||
Triangle *triangle = calloc(1, sizeof(Triangle));
|
||||
if(!triangle)
|
||||
return false;
|
||||
|
||||
triangle->shader_program = glCreateProgram();
|
||||
|
||||
const GLuint vertex_shader = load_shader(vertex_shader_source, GL_VERTEX_SHADER);
|
||||
const GLuint fragment_shader = load_shader(fragment_shader_source, GL_FRAGMENT_SHADER);
|
||||
|
||||
glAttachShader(triangle->shader_program, vertex_shader);
|
||||
glAttachShader(triangle->shader_program, fragment_shader);
|
||||
glBindAttribLocation(triangle->shader_program, 0, "vertex_pos");
|
||||
glLinkProgram(triangle->shader_program);
|
||||
|
||||
glGenVertexArrays(1, &triangle->vao);
|
||||
glBindVertexArray(triangle->vao);
|
||||
|
||||
glGenBuffers(1, &triangle->vbo);
|
||||
glBindBuffer(GL_ARRAY_BUFFER, triangle->vbo);
|
||||
glBufferData(GL_ARRAY_BUFFER, 6 * sizeof(float), NULL, GL_DYNAMIC_DRAW);
|
||||
|
||||
glEnableVertexAttribArray(0);
|
||||
glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 2 * sizeof(float), (void *)0);
|
||||
|
||||
glBindVertexArray(0);
|
||||
|
||||
glDeleteShader(vertex_shader);
|
||||
glDeleteShader(fragment_shader);
|
||||
|
||||
triangle->color_uniform = glGetUniformLocation(triangle->shader_program, "color");
|
||||
|
||||
ret->name = "hello_triangle";
|
||||
ret->version = 1;
|
||||
ret->userdata = triangle;
|
||||
ret->draw = draw;
|
||||
return true;
|
||||
}
|
||||
|
||||
void gsr_plugin_deinit(void *userdata) {
|
||||
Triangle *triangle = userdata;
|
||||
glDeleteProgram(triangle->shader_program);
|
||||
free(triangle);
|
||||
}
|
||||
54
plugin/plugin.h
Normal file
54
plugin/plugin.h
Normal file
@@ -0,0 +1,54 @@
|
||||
#ifndef GSR_PLUGIN_H
|
||||
#define GSR_PLUGIN_H
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#define GSR_PLUGIN_INTERFACE_MAJOR_VERSION 0
|
||||
#define GSR_PLUGIN_INTERFACE_MINOR_VERSION 1
|
||||
#define GSR_PLUGIN_INTERFACE_VERSION ((GSR_PLUGIN_INTERFACE_MAJOR_VERSION << 16) | GSR_PLUGIN_INTERFACE_MINOR_VERSION)
|
||||
|
||||
#include <stdbool.h>
|
||||
|
||||
typedef enum {
|
||||
GSR_PLUGIN_GRAPHICS_API_EGL_ES,
|
||||
GSR_PLUGIN_GRAPHICS_API_GLX,
|
||||
} gsr_plugin_graphics_api;
|
||||
|
||||
typedef enum {
|
||||
GSR_PLUGIN_COLOR_DEPTH_8_BITS,
|
||||
GSR_PLUGIN_COLOR_DEPTH_10_BITS,
|
||||
} gsr_plugin_color_depth;
|
||||
|
||||
typedef struct {
|
||||
unsigned int width;
|
||||
unsigned int height;
|
||||
} gsr_plugin_draw_params;
|
||||
|
||||
typedef struct {
|
||||
unsigned int width;
|
||||
unsigned int height;
|
||||
unsigned int fps;
|
||||
gsr_plugin_color_depth color_depth;
|
||||
gsr_plugin_graphics_api graphics_api;
|
||||
} gsr_plugin_init_params;
|
||||
|
||||
typedef struct {
|
||||
const char *name; /* Mandatory */
|
||||
unsigned int version; /* Mandatory, can't be 0 */
|
||||
void *userdata; /* Optional */
|
||||
|
||||
/* Optional, called when the plugin is expected to draw something to the current framebuffer */
|
||||
void (*draw)(const gsr_plugin_draw_params *params, void *userdata);
|
||||
} gsr_plugin_init_return;
|
||||
|
||||
/* The plugin is expected to implement these functions and export them: */
|
||||
bool gsr_plugin_init(const gsr_plugin_init_params *params, gsr_plugin_init_return *ret);
|
||||
void gsr_plugin_deinit(void *userdata);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* GSR_PLUGIN_H */
|
||||
@@ -1,11 +1,11 @@
|
||||
[package]
|
||||
name = "gpu-screen-recorder"
|
||||
type = "executable"
|
||||
version = "5.6.5"
|
||||
version = "5.6.7"
|
||||
platforms = ["posix"]
|
||||
|
||||
[config]
|
||||
ignore_dirs = ["kms/server", "build", "debug-build"]
|
||||
ignore_dirs = ["kms/server", "build", "debug-build", "plugin/examples"]
|
||||
#error_on_warning = "true"
|
||||
|
||||
[define]
|
||||
|
||||
@@ -190,7 +190,13 @@ static double args_get_double_by_key(Arg *args, int num_args, const char *key, d
|
||||
static void usage_header() {
|
||||
const bool inside_flatpak = getenv("FLATPAK_ID") != NULL;
|
||||
const char *program_name = inside_flatpak ? "flatpak run --command=gpu-screen-recorder com.dec05eba.gpu_screen_recorder" : "gpu-screen-recorder";
|
||||
printf("usage: %s -w <window_id|monitor|focused|portal|region> [-c <container_format>] [-s WxH] [-region WxH+X+Y] [-f <fps>] [-a <audio_input>] [-q <quality>] [-r <replay_buffer_size_sec>] [-replay-storage ram|disk] [-restart-replay-on-save yes|no] [-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>] [-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] [-v yes|no] [-gl-debug yes|no] [--version] [-h|--help]\n", program_name);
|
||||
printf("usage: %s -w <window_id|monitor|focused|portal|region> [-c <container_format>] [-s WxH] [-region WxH+X+Y] [-f <fps>] [-a <audio_input>] "
|
||||
"[-q <quality>] [-r <replay_buffer_size_sec>] [-replay-storage ram|disk] [-restart-replay-on-save yes|no] "
|
||||
"[-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] "
|
||||
"[-v yes|no] [-gl-debug yes|no] [--version] [-h|--help]\n", program_name);
|
||||
fflush(stdout);
|
||||
}
|
||||
|
||||
@@ -305,8 +311,11 @@ static void usage_full() {
|
||||
printf("\n");
|
||||
printf(" -df Organise replays in folders based on the current date.\n");
|
||||
printf("\n");
|
||||
printf(" -sc Run a script on the saved video file (asynchronously). The first argument to the script is the filepath to the saved video file and the second argument is the recording type (either \"regular\" or \"replay\").\n");
|
||||
printf(" -sc Run a script on the saved video file (asynchronously). The first argument to the script is the filepath to the saved video/screenshot file and the second argument is the recording type (either \"regular\", \"replay\" or \"screenshot\").\n");
|
||||
printf(" Not applicable for live streams.\n");
|
||||
printf(" Note: the script has to be executable.\n");
|
||||
printf("\n");
|
||||
printf(" -p A plugin (.so) to load. This can be specified multiple times to load multiple plugins.\n");
|
||||
printf("\n");
|
||||
printf(" -cursor\n");
|
||||
printf(" Record cursor. Optional, set to 'yes' by default.\n");
|
||||
@@ -394,13 +403,14 @@ static void usage_full() {
|
||||
printf(" Send signal SIGRTMIN to gpu-screen-recorder (pkill -SIGRTMIN -f gpu-screen-recorder) to start/stop recording a regular video when in replay/streaming mode.\n");
|
||||
printf("\n");
|
||||
printf("EXAMPLES:\n");
|
||||
printf(" %s -w screen -o video.mp4\n", program_name);
|
||||
printf(" %s -w screen -f 60 -a default_output -o video.mp4\n", program_name);
|
||||
printf(" %s -w screen -f 60 -a default_output -a default_input -o video.mp4\n", program_name);
|
||||
printf(" %s -w $(xdotool selectwindow) -f 60 -a default_output -o video.mp4\n", program_name);
|
||||
printf(" %s -w screen -f 60 -a \"default_output|default_input\" -o video.mp4\n", program_name);
|
||||
printf(" %s -w screen -f 60 -a default_output -c mkv -r 60 -o \"$HOME/Videos\"\n", program_name);
|
||||
printf(" %s -w screen -f 60 -a default_output -c mkv -r 1800 -replay-storage disk -bm cbr -q 40000 -o \"$HOME/Videos\"\n", program_name);
|
||||
printf(" %s -w screen -f 60 -a default_output -c mkv -sc script.sh -r 60 -o \"$HOME/Videos\"\n", program_name);
|
||||
printf(" %s -w screen -f 60 -a default_output -c mkv -sc ./script.sh -r 60 -o \"$HOME/Videos\"\n", program_name);
|
||||
printf(" %s -w portal -f 60 -a default_output -restore-portal-session yes -o video.mp4\n", program_name);
|
||||
printf(" %s -w screen -f 60 -a default_output -bm cbr -q 15000 -o video.mp4\n", program_name);
|
||||
printf(" %s -w screen -f 60 -a \"app:firefox|app:csgo\" -o video.mp4\n", program_name);
|
||||
@@ -411,6 +421,7 @@ static void usage_full() {
|
||||
printf(" %s -w region -region 640x480+100+100 -o video.mp4\n", program_name);
|
||||
printf(" %s -w region -region $(slop) -o video.mp4\n", program_name);
|
||||
printf(" %s -w region -region $(slurp -f \"%%wx%%h+%%x+%%y\") -o video.mp4\n", program_name);
|
||||
printf(" %s -w screen -p ./plugin.so -o video.mp4\n", program_name);
|
||||
//fprintf(stderr, " gpu-screen-recorder -w screen -f 60 -q ultra -pixfmt yuv444 -o video.mp4\n");
|
||||
fflush(stdout);
|
||||
}
|
||||
@@ -745,6 +756,7 @@ bool args_parser_parse(args_parser *self, int argc, char **argv, const args_hand
|
||||
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 = "-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);
|
||||
|
||||
for(int i = 1; i < argc; i += 2) {
|
||||
|
||||
@@ -870,7 +870,7 @@ static void gsr_color_conversion_draw_graphics(gsr_color_conversion *self, unsig
|
||||
self->params.egl->glViewport(0, 0, dest_texture_size.x, dest_texture_size.y);
|
||||
|
||||
/* TODO: this, also cleanup */
|
||||
//self->params.egl->glBindBuffer(GL_ARRAY_BUFFER, self->vertex_buffer_object_id);
|
||||
self->params.egl->glBindBuffer(GL_ARRAY_BUFFER, self->vertex_buffer_object_id);
|
||||
self->params.egl->glBufferSubData(GL_ARRAY_BUFFER, 0, 24 * sizeof(float), vertices);
|
||||
|
||||
switch(self->params.destination_color) {
|
||||
|
||||
@@ -65,6 +65,7 @@ bool gsr_cuda_load(gsr_cuda *self, Display *display, bool do_overclock) {
|
||||
goto fail;
|
||||
}
|
||||
|
||||
// TODO: Use the device associated with the opengl graphics context
|
||||
CUdevice cu_dev;
|
||||
res = self->cuDeviceGet(&cu_dev, 0);
|
||||
if(res != CUDA_SUCCESS) {
|
||||
|
||||
@@ -40,7 +40,7 @@ static bool gsr_egl_create_window(gsr_egl *self, bool enable_debug) {
|
||||
};
|
||||
|
||||
int32_t ctxattr[] = {
|
||||
EGL_CONTEXT_CLIENT_VERSION, 2,
|
||||
EGL_CONTEXT_CLIENT_VERSION, 3,
|
||||
EGL_NONE, EGL_NONE, EGL_NONE
|
||||
};
|
||||
|
||||
|
||||
106
src/main.cpp
106
src/main.cpp
@@ -26,6 +26,7 @@ extern "C" {
|
||||
#include "../include/color_conversion.h"
|
||||
#include "../include/image_writer.h"
|
||||
#include "../include/args_parser.h"
|
||||
#include "../include/plugins.h"
|
||||
}
|
||||
|
||||
#include <assert.h>
|
||||
@@ -75,6 +76,12 @@ static const int VIDEO_STREAM_INDEX = 0;
|
||||
|
||||
static thread_local char av_error_buffer[AV_ERROR_MAX_STRING_SIZE];
|
||||
|
||||
enum class AudioMergeType {
|
||||
NONE,
|
||||
AMIX,
|
||||
PIPEWIRE
|
||||
};
|
||||
|
||||
typedef struct {
|
||||
const gsr_window *window;
|
||||
} MonitorOutputCallbackUserdata;
|
||||
@@ -2346,11 +2353,16 @@ static void capture_image_to_file(args_parser &arg_parser, gsr_egl *egl, gsr_ima
|
||||
}
|
||||
|
||||
gsr_egl_swap_buffers(egl);
|
||||
|
||||
const int image_quality = video_quality_to_image_quality_value(arg_parser.video_quality);
|
||||
if(!gsr_image_writer_write_to_file(&image_writer, arg_parser.filename, image_format, image_quality)) {
|
||||
fprintf(stderr, "gsr error: capture_image_to_file_wayland: failed to write opengl texture to image output file %s\n", arg_parser.filename);
|
||||
_exit(1);
|
||||
|
||||
if(!should_stop_error) {
|
||||
const int image_quality = video_quality_to_image_quality_value(arg_parser.video_quality);
|
||||
if(!gsr_image_writer_write_to_file(&image_writer, arg_parser.filename, image_format, image_quality)) {
|
||||
fprintf(stderr, "gsr error: capture_image_to_file_wayland: failed to write opengl texture to image output file %s\n", arg_parser.filename);
|
||||
_exit(1);
|
||||
}
|
||||
|
||||
if(arg_parser.recording_saved_script)
|
||||
run_recording_saved_script_async(arg_parser.recording_saved_script, arg_parser.filename, "screenshot");
|
||||
}
|
||||
|
||||
gsr_image_writer_deinit(&image_writer);
|
||||
@@ -3046,7 +3058,7 @@ static void set_display_server_environment_variables() {
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
setlocale(LC_ALL, "C"); // Sigh... stupid C
|
||||
#ifdef __linux__
|
||||
#ifdef __GLIBC__
|
||||
mallopt(M_MMAP_THRESHOLD, 65536);
|
||||
#endif
|
||||
|
||||
@@ -3113,12 +3125,24 @@ int main(int argc, char **argv) {
|
||||
std::vector<MergedAudioInputs> requested_audio_inputs = parse_audio_inputs(audio_devices, audio_input_arg);
|
||||
|
||||
const bool uses_app_audio = merged_audio_inputs_has_app_audio(requested_audio_inputs);
|
||||
AudioMergeType audio_merge_type = AudioMergeType::NONE;
|
||||
std::vector<std::string> app_audio_names;
|
||||
#ifdef GSR_APP_AUDIO
|
||||
const bool audio_server_is_pipewire = audio_input_arg->num_values > 0 && pulseaudio_server_is_pipewire();
|
||||
if(merged_audio_inputs_should_use_amix(requested_audio_inputs)) {
|
||||
if(audio_server_is_pipewire || uses_app_audio)
|
||||
audio_merge_type = AudioMergeType::PIPEWIRE;
|
||||
else
|
||||
audio_merge_type = AudioMergeType::AMIX;
|
||||
}
|
||||
|
||||
gsr_pipewire_audio pipewire_audio;
|
||||
memset(&pipewire_audio, 0, sizeof(pipewire_audio));
|
||||
if(uses_app_audio) {
|
||||
if(!pulseaudio_server_is_pipewire()) {
|
||||
// TODO: When recording multiple audio devices and merging them (for example desktop audio and microphone) then one (or more) of the audio sources
|
||||
// can get desynced. I'm unable to reproduce this but some others are. Instead of merging audio with ffmpeg amix, merge audio with pipewire (if available).
|
||||
// This fixes the issue for people that had the issue.
|
||||
if(audio_merge_type == AudioMergeType::PIPEWIRE || uses_app_audio) {
|
||||
if(!audio_server_is_pipewire) {
|
||||
fprintf(stderr, "gsr error: your sound server is not PipeWire. Application audio is only available when running PipeWire audio server\n");
|
||||
_exit(2);
|
||||
}
|
||||
@@ -3134,6 +3158,14 @@ int main(int argc, char **argv) {
|
||||
return true;
|
||||
}, &app_audio_names);
|
||||
}
|
||||
#else
|
||||
if(merged_audio_inputs_should_use_amix(requested_audio_inputs))
|
||||
audio_merge_type = AudioMergeType::AMIX;
|
||||
|
||||
if(uses_app_audio) {
|
||||
fprintf(stderr, "gsr error: application audio can't be recorded because GPU Screen Recorder is built without application audio support (-Dapp_audio option)\n");
|
||||
_exit(2);
|
||||
}
|
||||
#endif
|
||||
|
||||
validate_merged_audio_inputs_app_audio(requested_audio_inputs, app_audio_names);
|
||||
@@ -3239,8 +3271,7 @@ int main(int argc, char **argv) {
|
||||
const bool force_no_audio_offset = arg_parser.is_livestream || arg_parser.is_output_piped || (file_extension != "mp4" && file_extension != "mkv" && file_extension != "webm");
|
||||
const double target_fps = 1.0 / (double)arg_parser.fps;
|
||||
|
||||
const bool uses_amix = merged_audio_inputs_should_use_amix(requested_audio_inputs);
|
||||
arg_parser.audio_codec = select_audio_codec_with_fallback(arg_parser.audio_codec, file_extension, uses_amix);
|
||||
arg_parser.audio_codec = select_audio_codec_with_fallback(arg_parser.audio_codec, file_extension, audio_merge_type == AudioMergeType::AMIX);
|
||||
|
||||
gsr_capture *capture = create_capture_impl(arg_parser, &egl, false);
|
||||
|
||||
@@ -3311,6 +3342,33 @@ int main(int argc, char **argv) {
|
||||
capture_metadata.width = video_codec_context->width;
|
||||
capture_metadata.height = video_codec_context->height;
|
||||
|
||||
const Arg *plugin_arg = args_parser_get_arg(&arg_parser, "-p");
|
||||
assert(plugin_arg);
|
||||
|
||||
gsr_plugins plugins;
|
||||
memset(&plugins, 0, sizeof(plugins));
|
||||
|
||||
if(plugin_arg->num_values > 0) {
|
||||
const gsr_color_depth color_depth = video_codec_to_bit_depth(arg_parser.video_codec);
|
||||
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)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,
|
||||
};
|
||||
|
||||
if(!gsr_plugins_init(&plugins, plugin_init_params, &egl))
|
||||
_exit(1);
|
||||
|
||||
for(int i = 0; i < plugin_arg->num_values; ++i) {
|
||||
if(!gsr_plugins_load_plugin(&plugins, plugin_arg->values[i]))
|
||||
_exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
gsr_color_conversion_params color_conversion_params;
|
||||
memset(&color_conversion_params, 0, sizeof(color_conversion_params));
|
||||
color_conversion_params.color_range = arg_parser.color_range;
|
||||
@@ -3326,6 +3384,8 @@ int main(int argc, char **argv) {
|
||||
|
||||
gsr_color_conversion_clear(&color_conversion);
|
||||
|
||||
gsr_color_conversion *output_color_conversion = plugins.num_plugins > 0 ? &plugins.color_conversion : &color_conversion;
|
||||
|
||||
if(arg_parser.video_encoder == GSR_VIDEO_ENCODER_HW_CPU) {
|
||||
open_video_software(video_codec_context, arg_parser);
|
||||
} else {
|
||||
@@ -3368,7 +3428,7 @@ int main(int argc, char **argv) {
|
||||
std::vector<AVFilterContext*> src_filter_ctx;
|
||||
AVFilterGraph *graph = nullptr;
|
||||
AVFilterContext *sink = nullptr;
|
||||
if(use_amix) {
|
||||
if(use_amix && audio_merge_type == AudioMergeType::AMIX) {
|
||||
int err = init_filter_graph(audio_codec_context, &graph, &sink, src_filter_ctx, merged_audio_inputs.audio_inputs.size());
|
||||
if(err < 0) {
|
||||
fprintf(stderr, "gsr error: failed to create audio filter\n");
|
||||
@@ -3385,8 +3445,7 @@ int main(int argc, char **argv) {
|
||||
const double num_audio_frames_shift = audio_startup_time_seconds / timeout_sec;
|
||||
|
||||
std::vector<AudioDeviceData> audio_track_audio_devices;
|
||||
if(audio_inputs_has_app_audio(merged_audio_inputs.audio_inputs)) {
|
||||
assert(!use_amix);
|
||||
if((use_amix && audio_merge_type == AudioMergeType::PIPEWIRE) || audio_inputs_has_app_audio(merged_audio_inputs.audio_inputs)) {
|
||||
#ifdef GSR_APP_AUDIO
|
||||
audio_track_audio_devices.push_back(create_application_audio_audio_input(merged_audio_inputs, audio_codec_context, num_channels, num_audio_frames_shift, &pipewire_audio));
|
||||
#endif
|
||||
@@ -3601,7 +3660,7 @@ int main(int argc, char **argv) {
|
||||
}
|
||||
|
||||
std::thread amix_thread;
|
||||
if(uses_amix) {
|
||||
if(audio_merge_type == AudioMergeType::AMIX) {
|
||||
amix_thread = std::thread([&]() {
|
||||
AVFrame *aframe = av_frame_alloc();
|
||||
while(running) {
|
||||
@@ -3642,15 +3701,14 @@ int main(int argc, char **argv) {
|
||||
bool hdr_metadata_set = false;
|
||||
const bool hdr = video_codec_is_hdr(arg_parser.video_codec);
|
||||
|
||||
double damage_timeout_seconds = arg_parser.framerate_mode == GSR_FRAMERATE_MODE_CONTENT ? 0.5 : 0.1;
|
||||
damage_timeout_seconds = std::max(damage_timeout_seconds, target_fps);
|
||||
|
||||
bool use_damage_tracking = false;
|
||||
gsr_damage damage;
|
||||
memset(&damage, 0, sizeof(damage));
|
||||
if(gsr_window_get_display_server(window) == GSR_DISPLAY_SERVER_X11) {
|
||||
gsr_damage_init(&damage, &egl, arg_parser.record_cursor);
|
||||
use_damage_tracking = true;
|
||||
} else if(!capture->is_damaged) {
|
||||
fprintf(stderr, "gsr warning: \"-fm content\" has no effect on Wayland when recording a monitor. Either record a monitor on X11 or capture with desktop portal instead (-w portal)\n");
|
||||
}
|
||||
|
||||
if(is_monitor_capture)
|
||||
@@ -3728,7 +3786,15 @@ int main(int argc, char **argv) {
|
||||
}
|
||||
}
|
||||
|
||||
gsr_capture_capture(capture, &capture_metadata, &color_conversion);
|
||||
gsr_capture_capture(capture, &capture_metadata, output_color_conversion);
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
if(capture_has_synchronous_task) {
|
||||
paused_time_offset = paused_time_offset + (clock_get_monotonic_seconds() - paused_time_start);
|
||||
@@ -3736,7 +3802,7 @@ int main(int argc, char **argv) {
|
||||
}
|
||||
|
||||
gsr_egl_swap_buffers(&egl);
|
||||
gsr_video_encoder_copy_textures_to_frame(video_encoder, video_frame, &color_conversion);
|
||||
gsr_video_encoder_copy_textures_to_frame(video_encoder, video_frame, output_color_conversion);
|
||||
|
||||
if(hdr && !hdr_metadata_set && !is_replaying && add_hdr_metadata_to_video_stream(capture, video_stream))
|
||||
hdr_metadata_set = true;
|
||||
@@ -3903,6 +3969,8 @@ int main(int argc, char **argv) {
|
||||
}
|
||||
}
|
||||
|
||||
gsr_plugins_deinit(&plugins);
|
||||
|
||||
if(replay_recording_start_result.av_format_context) {
|
||||
for(size_t id : replay_recording_items) {
|
||||
gsr_encoder_remove_recording_destination(&encoder, id);
|
||||
|
||||
@@ -116,8 +116,6 @@ static const struct pw_core_events core_events = {
|
||||
|
||||
static void on_process_cb(void *user_data) {
|
||||
gsr_pipewire_video *self = user_data;
|
||||
struct spa_meta_cursor *cursor = NULL;
|
||||
//struct spa_meta *video_damage = NULL;
|
||||
|
||||
/* Find the most recent buffer */
|
||||
struct pw_buffer *pw_buf = NULL;
|
||||
@@ -137,12 +135,11 @@ static void on_process_cb(void *user_data) {
|
||||
|
||||
struct spa_buffer *buffer = pw_buf->buffer;
|
||||
const bool has_buffer = buffer->datas[0].chunk->size != 0;
|
||||
if(!has_buffer)
|
||||
goto read_metadata;
|
||||
|
||||
pthread_mutex_lock(&self->mutex);
|
||||
|
||||
if(buffer->datas[0].type == SPA_DATA_DmaBuf) {
|
||||
bool buffer_updated = false;
|
||||
if(has_buffer && buffer->datas[0].type == SPA_DATA_DmaBuf) {
|
||||
for(size_t i = 0; i < self->dmabuf_num_planes; ++i) {
|
||||
if(self->dmabuf_data[i].fd > 0) {
|
||||
close(self->dmabuf_data[i].fd);
|
||||
@@ -160,9 +157,7 @@ static void on_process_cb(void *user_data) {
|
||||
self->dmabuf_data[i].stride = buffer->datas[i].chunk->stride;
|
||||
}
|
||||
|
||||
self->damaged = true;
|
||||
} else {
|
||||
// TODO:
|
||||
buffer_updated = true;
|
||||
}
|
||||
|
||||
// TODO: Move down to read_metadata
|
||||
@@ -201,32 +196,30 @@ static void on_process_cb(void *user_data) {
|
||||
break;
|
||||
}
|
||||
|
||||
pthread_mutex_unlock(&self->mutex);
|
||||
const struct spa_meta *video_damage = spa_buffer_find_meta(buffer, SPA_META_VideoDamage);
|
||||
if(video_damage) {
|
||||
struct spa_meta_region *meta_region = NULL;
|
||||
spa_meta_for_each(meta_region, video_damage) {
|
||||
if(meta_region->region.size.width == 0 || meta_region->region.size.height == 0)
|
||||
continue;
|
||||
|
||||
read_metadata:
|
||||
self->damaged = true;
|
||||
break;
|
||||
}
|
||||
} else if(buffer_updated) {
|
||||
self->damaged = true;
|
||||
}
|
||||
|
||||
// video_damage = spa_buffer_find_meta(buffer, SPA_META_VideoDamage);
|
||||
// if(video_damage) {
|
||||
// struct spa_meta_region *r = spa_meta_first(video_damage);
|
||||
// if(spa_meta_check(r, video_damage)) {
|
||||
// //fprintf(stderr, "damage: %d,%d %ux%u\n", r->region.position.x, r->region.position.y, r->region.size.width, r->region.size.height);
|
||||
// pthread_mutex_lock(&self->mutex);
|
||||
// self->damaged = true;
|
||||
// pthread_mutex_unlock(&self->mutex);
|
||||
// }
|
||||
// }
|
||||
|
||||
cursor = spa_buffer_find_meta_data(buffer, SPA_META_Cursor, sizeof(*cursor));
|
||||
const struct spa_meta_cursor *cursor = spa_buffer_find_meta_data(buffer, SPA_META_Cursor, sizeof(*cursor));
|
||||
self->cursor.valid = cursor && spa_meta_cursor_is_valid(cursor);
|
||||
|
||||
if (self->cursor.visible && self->cursor.valid) {
|
||||
pthread_mutex_lock(&self->mutex);
|
||||
|
||||
struct spa_meta_bitmap *bitmap = NULL;
|
||||
if (cursor->bitmap_offset)
|
||||
bitmap = SPA_MEMBER(cursor, cursor->bitmap_offset, struct spa_meta_bitmap);
|
||||
|
||||
if (bitmap && bitmap->size.width > 0 && bitmap->size.height && is_cursor_format_supported(bitmap->format)) {
|
||||
// TODO: Maybe check if the cursor is actually visible by checking if there are visible pixels
|
||||
if (bitmap && bitmap->size.width > 0 && bitmap->size.height > 0 && is_cursor_format_supported(bitmap->format)) {
|
||||
const uint8_t *bitmap_data = SPA_MEMBER(bitmap, bitmap->offset, uint8_t);
|
||||
fprintf(stderr, "gsr info: pipewire: cursor bitmap update, size: %dx%d, format: %s\n",
|
||||
(int)bitmap->size.width, (int)bitmap->size.height, spa_debug_type_find_name(spa_type_video_format, bitmap->format));
|
||||
@@ -243,15 +236,19 @@ read_metadata:
|
||||
self->cursor.hotspot_y = cursor->hotspot.y;
|
||||
self->cursor.width = bitmap->size.width;
|
||||
self->cursor.height = bitmap->size.height;
|
||||
self->damaged = true;
|
||||
}
|
||||
|
||||
if(cursor->position.x != self->cursor.x || cursor->position.y != self->cursor.y)
|
||||
self->damaged = true;
|
||||
|
||||
self->cursor.x = cursor->position.x;
|
||||
self->cursor.y = cursor->position.y;
|
||||
pthread_mutex_unlock(&self->mutex);
|
||||
|
||||
//fprintf(stderr, "gsr info: pipewire: cursor: %d %d %d %d\n", cursor->hotspot.x, cursor->hotspot.y, cursor->position.x, cursor->position.y);
|
||||
}
|
||||
|
||||
pthread_mutex_unlock(&self->mutex);
|
||||
pw_stream_queue_buffer(self->stream, pw_buf);
|
||||
}
|
||||
|
||||
|
||||
137
src/plugins.c
Normal file
137
src/plugins.c
Normal file
@@ -0,0 +1,137 @@
|
||||
#include "../include/plugins.h"
|
||||
#include "../include/utils.h"
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <dlfcn.h>
|
||||
#include <assert.h>
|
||||
|
||||
static int color_depth_to_gl_internal_format(gsr_plugin_color_depth color_depth) {
|
||||
switch(color_depth) {
|
||||
case GSR_PLUGIN_COLOR_DEPTH_8_BITS:
|
||||
return GL_RGBA8;
|
||||
case GSR_PLUGIN_COLOR_DEPTH_10_BITS:
|
||||
return GL_RGBA16;
|
||||
}
|
||||
assert(false);
|
||||
return GL_RGBA8;
|
||||
}
|
||||
|
||||
bool gsr_plugins_init(gsr_plugins *self, gsr_plugin_init_params init_params, gsr_egl *egl) {
|
||||
memset(self, 0, sizeof(*self));
|
||||
self->init_params = init_params;
|
||||
self->egl = egl;
|
||||
|
||||
/* TODO: GL_RGB8? */
|
||||
const unsigned int texture = gl_create_texture(egl, init_params.width, init_params.height, color_depth_to_gl_internal_format(init_params.color_depth), GL_RGBA, GL_LINEAR);
|
||||
if(texture == 0) {
|
||||
fprintf(stderr, "gsr error: gsr_plugins_init failed to create texture\n");
|
||||
return false;
|
||||
}
|
||||
self->texture = texture;
|
||||
|
||||
gsr_color_conversion_params color_conversion_params = {
|
||||
.egl = egl,
|
||||
.destination_color = GSR_DESTINATION_COLOR_RGB8, /* TODO: Support 10-bits, use init_params.color_depth */
|
||||
.destination_textures[0] = self->texture,
|
||||
.num_destination_textures = 1,
|
||||
.color_range = GSR_COLOR_RANGE_FULL,
|
||||
.load_external_image_shader = false,
|
||||
//.force_graphics_shader = false,
|
||||
};
|
||||
color_conversion_params.destination_textures[0] = self->texture;
|
||||
|
||||
if(gsr_color_conversion_init(&self->color_conversion, &color_conversion_params) != 0) {
|
||||
fprintf(stderr, "gsr error: gsr_plugins_init failed to create color conversion\n");
|
||||
gsr_plugins_deinit(self);
|
||||
return false;
|
||||
}
|
||||
gsr_color_conversion_clear(&self->color_conversion);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void gsr_plugins_deinit(gsr_plugins *self) {
|
||||
for(int i = self->num_plugins - 1; i >= 0; --i) {
|
||||
gsr_plugin *plugin = &self->plugins[i];
|
||||
plugin->gsr_plugin_deinit(plugin->data.userdata);
|
||||
fprintf(stderr, "gsr info: unloaded plugin: %s\n", plugin->data.name);
|
||||
}
|
||||
self->num_plugins = 0;
|
||||
|
||||
if(self->texture > 0) {
|
||||
self->egl->glDeleteTextures(1, &self->texture);
|
||||
self->texture = 0;
|
||||
}
|
||||
|
||||
gsr_color_conversion_deinit(&self->color_conversion);
|
||||
}
|
||||
|
||||
bool gsr_plugins_load_plugin(gsr_plugins *self, const char *plugin_filepath) {
|
||||
if(self->num_plugins >= GSR_MAX_PLUGINS) {
|
||||
fprintf(stderr, "gsr error: gsr_plugins_load_plugin failed, more plugins can't load more than %d plugins. Report this as an issue\n", GSR_MAX_PLUGINS);
|
||||
return false;
|
||||
}
|
||||
|
||||
gsr_plugin plugin;
|
||||
memset(&plugin, 0, sizeof(plugin));
|
||||
|
||||
plugin.lib = dlopen(plugin_filepath, RTLD_LAZY);
|
||||
if(!plugin.lib) {
|
||||
fprintf(stderr, "gsr error: gsr_plugins_load_plugin failed to load \"%s\", error: %s\n", plugin_filepath, dlerror());
|
||||
return false;
|
||||
}
|
||||
|
||||
plugin.gsr_plugin_init = dlsym(plugin.lib, "gsr_plugin_init");
|
||||
if(!plugin.gsr_plugin_init) {
|
||||
fprintf(stderr, "gsr error: gsr_plugins_load_plugin failed to find \"gsr_plugin_init\" in plugin \"%s\"\n", plugin_filepath);
|
||||
goto fail;
|
||||
}
|
||||
|
||||
plugin.gsr_plugin_deinit = dlsym(plugin.lib, "gsr_plugin_deinit");
|
||||
if(!plugin.gsr_plugin_deinit) {
|
||||
fprintf(stderr, "gsr error: gsr_plugins_load_plugin failed to find \"gsr_plugin_deinit\" in plugin \"%s\"\n", plugin_filepath);
|
||||
goto fail;
|
||||
}
|
||||
|
||||
if(!plugin.gsr_plugin_init(&self->init_params, &plugin.data)) {
|
||||
fprintf(stderr, "gsr error: gsr_plugins_load_plugin failed to load plugin \"%s\", gsr_plugin_init in the plugin failed\n", plugin_filepath);
|
||||
goto fail;
|
||||
}
|
||||
|
||||
if(!plugin.data.name) {
|
||||
fprintf(stderr, "gsr error: gsr_plugins_load_plugin failed to load plugin \"%s\", the plugin didn't set the name (gsr_plugin_init_return.name)\n", plugin_filepath);
|
||||
goto fail;
|
||||
}
|
||||
|
||||
if(plugin.data.version == 0) {
|
||||
fprintf(stderr, "gsr error: gsr_plugins_load_plugin failed to load plugin \"%s\", the plugin didn't set the version (gsr_plugin_init_return.version)\n", plugin_filepath);
|
||||
goto fail;
|
||||
}
|
||||
|
||||
fprintf(stderr, "gsr info: loaded plugin: %s, name: %s, version: %u\n", plugin_filepath, plugin.data.name, plugin.data.version);
|
||||
self->plugins[self->num_plugins] = plugin;
|
||||
++self->num_plugins;
|
||||
return true;
|
||||
|
||||
fail:
|
||||
dlclose(plugin.lib);
|
||||
return false;
|
||||
}
|
||||
|
||||
void gsr_plugins_draw(gsr_plugins *self) {
|
||||
const gsr_plugin_draw_params params = {
|
||||
.width = self->init_params.width,
|
||||
.height = self->init_params.height,
|
||||
};
|
||||
|
||||
self->egl->glBindFramebuffer(GL_FRAMEBUFFER, self->color_conversion.framebuffers[0]);
|
||||
self->egl->glViewport(0, 0, self->init_params.width, self->init_params.height);
|
||||
|
||||
for(int i = 0; i < self->num_plugins; ++i) {
|
||||
const gsr_plugin *plugin = &self->plugins[i];
|
||||
if(plugin->data.draw)
|
||||
plugin->data.draw(¶ms, plugin->data.userdata);
|
||||
}
|
||||
|
||||
self->egl->glBindFramebuffer(GL_FRAMEBUFFER, 0);
|
||||
}
|
||||
Reference in New Issue
Block a user