Add plugin support (-p option)

This commit is contained in:
dec05eba
2025-08-14 14:55:37 +02:00
parent c7d156aef7
commit b500704008
13 changed files with 419 additions and 8 deletions

View File

@@ -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);
}
@@ -307,6 +313,9 @@ static void usage_full() {
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(" 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) {

View File

@@ -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) {

View File

@@ -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>
@@ -3311,6 +3312,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 +3354,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 {
@@ -3728,7 +3758,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 +3774,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 +3941,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);

137
src/plugins.c Normal file
View 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(&params, plugin->data.userdata);
}
self->egl->glBindFramebuffer(GL_FRAMEBUFFER, 0);
}