mirror of
https://repo.dec05eba.com/gpu-screen-recorder
synced 2026-04-03 10:06:37 +09:00
Compare commits
7 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a1ef9eec2e | ||
|
|
5a93d292ea | ||
|
|
48932dfdfb | ||
|
|
88ab64127e | ||
|
|
b500704008 | ||
|
|
c7d156aef7 | ||
|
|
9a8fd312e0 |
12
README.md
12
README.md
@@ -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,16 @@ 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.
|
||||
# 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.
|
||||
|
||||
9
TODO
9
TODO
@@ -315,3 +315,12 @@ Colors are correct, but they look incorrect for thin elements, such as colored t
|
||||
Record first video/audio frame immediately.
|
||||
|
||||
Disable GL_DEPTH_TEST, GL_CULL_FACE.
|
||||
|
||||
kde plasma portal capture for screenshot doesn't work well because the portal ui is still visible when taking a screenshot because of its animation.
|
||||
|
||||
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.
|
||||
|
||||
Add support for QVBR (QP with target bitrate).
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
|
||||
typedef struct gsr_egl gsr_egl;
|
||||
|
||||
#define NUM_ARGS 30
|
||||
#define NUM_ARGS 31
|
||||
|
||||
typedef enum {
|
||||
ARG_TYPE_STRING,
|
||||
|
||||
@@ -17,8 +17,6 @@ typedef struct {
|
||||
int width;
|
||||
int height;
|
||||
int fps;
|
||||
AVCodecContext *video_codec_context; /* can be NULL */
|
||||
AVFrame *frame; /* can be NULL, but will never be NULL if |video_codec_context| is set */
|
||||
} gsr_capture_metadata;
|
||||
|
||||
struct gsr_capture {
|
||||
|
||||
@@ -2,10 +2,12 @@
|
||||
#define GSR_CODEC_QUERY_H
|
||||
|
||||
#include <stdbool.h>
|
||||
#include "../vec2.h"
|
||||
|
||||
typedef struct {
|
||||
bool supported;
|
||||
bool low_power;
|
||||
vec2i max_resolution;
|
||||
} gsr_supported_video_codec;
|
||||
|
||||
typedef struct {
|
||||
|
||||
@@ -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.4', default_options : ['warning_level=2'])
|
||||
project('gpu-screen-recorder', ['c', 'cpp'], version : '5.6.6', 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.4"
|
||||
version = "5.6.6"
|
||||
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) {
|
||||
|
||||
@@ -6,6 +6,11 @@
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
#define NVENCAPI_MAJOR_VERSION_470 11
|
||||
#define NVENCAPI_MINOR_VERSION_470 1
|
||||
#define NVENCAPI_VERSION_470 (NVENCAPI_MAJOR_VERSION_470 | (NVENCAPI_MINOR_VERSION_470 << 24))
|
||||
#define NVENCAPI_STRUCT_VERSION_CUSTOM(nvenc_api_version, struct_version) ((uint32_t)(nvenc_api_version) | ((struct_version)<<16) | (0x7 << 28))
|
||||
|
||||
static void* open_nvenc_library(void) {
|
||||
dlerror(); /* clear */
|
||||
void *lib = dlopen("libnvidia-encode.so.1", RTLD_LAZY);
|
||||
@@ -75,7 +80,28 @@ static bool profile_is_av1(const GUID *profile_guid) {
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool encoder_get_supported_profiles(const NV_ENCODE_API_FUNCTION_LIST *function_list, void *nvenc_encoder, const GUID *encoder_guid, gsr_supported_video_codecs *supported_video_codecs) {
|
||||
/* Returns 0 on error */
|
||||
static int nvenc_get_encoding_capability(const NV_ENCODE_API_FUNCTION_LIST *function_list, void *nvenc_encoder, const GUID *encode_guid, uint32_t nvenc_api_version, NV_ENC_CAPS cap) {
|
||||
NV_ENC_CAPS_PARAM param = {
|
||||
.version = NVENCAPI_STRUCT_VERSION_CUSTOM(nvenc_api_version, 1),
|
||||
.capsToQuery = cap
|
||||
};
|
||||
|
||||
int value = 0;
|
||||
if(function_list->nvEncGetEncodeCaps(nvenc_encoder, *encode_guid, ¶m, &value) != NV_ENC_SUCCESS)
|
||||
return 0;
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
static vec2i encoder_get_max_resolution(const NV_ENCODE_API_FUNCTION_LIST *function_list, void *nvenc_encoder, const GUID *encode_guid, uint32_t nvenc_api_version) {
|
||||
return (vec2i){
|
||||
.x = nvenc_get_encoding_capability(function_list, nvenc_encoder, encode_guid, nvenc_api_version, NV_ENC_CAPS_WIDTH_MAX),
|
||||
.y = nvenc_get_encoding_capability(function_list, nvenc_encoder, encode_guid, nvenc_api_version, NV_ENC_CAPS_HEIGHT_MAX),
|
||||
};
|
||||
}
|
||||
|
||||
static bool encoder_get_supported_profiles(const NV_ENCODE_API_FUNCTION_LIST *function_list, void *nvenc_encoder, const GUID *encoder_guid, gsr_supported_video_codecs *supported_video_codecs, uint32_t nvenc_api_version) {
|
||||
bool success = false;
|
||||
GUID *profile_guids = NULL;
|
||||
|
||||
@@ -99,18 +125,19 @@ static bool encoder_get_supported_profiles(const NV_ENCODE_API_FUNCTION_LIST *fu
|
||||
goto fail;
|
||||
}
|
||||
|
||||
const vec2i max_resolution = encoder_get_max_resolution(function_list, nvenc_encoder, encoder_guid, nvenc_api_version);
|
||||
for(uint32_t i = 0; i < profile_guid_count; ++i) {
|
||||
if(profile_is_h264(&profile_guids[i])) {
|
||||
supported_video_codecs->h264 = (gsr_supported_video_codec){ true, false };
|
||||
supported_video_codecs->h264 = (gsr_supported_video_codec){ true, false, max_resolution };
|
||||
} else if(profile_is_hevc(&profile_guids[i])) {
|
||||
supported_video_codecs->hevc = (gsr_supported_video_codec){ true, false };
|
||||
supported_video_codecs->hevc = (gsr_supported_video_codec){ true, false, max_resolution };
|
||||
} else if(profile_is_hevc_10bit(&profile_guids[i])) {
|
||||
supported_video_codecs->hevc_hdr = (gsr_supported_video_codec){ true, false };
|
||||
supported_video_codecs->hevc_10bit = (gsr_supported_video_codec){ true, false };
|
||||
supported_video_codecs->hevc_hdr = (gsr_supported_video_codec){ true, false, max_resolution };
|
||||
supported_video_codecs->hevc_10bit = (gsr_supported_video_codec){ true, false, max_resolution };
|
||||
} else if(profile_is_av1(&profile_guids[i])) {
|
||||
supported_video_codecs->av1 = (gsr_supported_video_codec){ true, false };
|
||||
supported_video_codecs->av1_hdr = (gsr_supported_video_codec){ true, false };
|
||||
supported_video_codecs->av1_10bit = (gsr_supported_video_codec){ true, false };
|
||||
supported_video_codecs->av1 = (gsr_supported_video_codec){ true, false, max_resolution };
|
||||
supported_video_codecs->av1_hdr = (gsr_supported_video_codec){ true, false, max_resolution };
|
||||
supported_video_codecs->av1_10bit = (gsr_supported_video_codec){ true, false, max_resolution };
|
||||
}
|
||||
}
|
||||
|
||||
@@ -123,7 +150,7 @@ static bool encoder_get_supported_profiles(const NV_ENCODE_API_FUNCTION_LIST *fu
|
||||
return success;
|
||||
}
|
||||
|
||||
static bool get_supported_video_codecs(const NV_ENCODE_API_FUNCTION_LIST *function_list, void *nvenc_encoder, gsr_supported_video_codecs *supported_video_codecs) {
|
||||
static bool get_supported_video_codecs(const NV_ENCODE_API_FUNCTION_LIST *function_list, void *nvenc_encoder, gsr_supported_video_codecs *supported_video_codecs, uint32_t nvenc_api_version) {
|
||||
bool success = false;
|
||||
GUID *encoder_guids = NULL;
|
||||
*supported_video_codecs = (gsr_supported_video_codecs){0};
|
||||
@@ -149,7 +176,7 @@ static bool get_supported_video_codecs(const NV_ENCODE_API_FUNCTION_LIST *functi
|
||||
}
|
||||
|
||||
for(uint32_t i = 0; i < encode_guid_count; ++i) {
|
||||
encoder_get_supported_profiles(function_list, nvenc_encoder, &encoder_guids[i], supported_video_codecs);
|
||||
encoder_get_supported_profiles(function_list, nvenc_encoder, &encoder_guids[i], supported_video_codecs, nvenc_api_version);
|
||||
}
|
||||
|
||||
success = true;
|
||||
@@ -161,9 +188,6 @@ static bool get_supported_video_codecs(const NV_ENCODE_API_FUNCTION_LIST *functi
|
||||
return success;
|
||||
}
|
||||
|
||||
#define NVENCAPI_VERSION_470 (11 | (1 << 24))
|
||||
#define NVENCAPI_STRUCT_VERSION_470(ver) ((uint32_t)NVENCAPI_VERSION_470 | ((ver)<<16) | (0x7 << 28))
|
||||
|
||||
bool gsr_get_supported_video_codecs_nvenc(gsr_supported_video_codecs *video_codecs, bool cleanup) {
|
||||
memset(video_codecs, 0, sizeof(*video_codecs));
|
||||
|
||||
@@ -206,13 +230,13 @@ bool gsr_get_supported_video_codecs_nvenc(gsr_supported_video_codecs *video_code
|
||||
if(function_list.nvEncOpenEncodeSessionEx(¶ms, &nvenc_encoder) != NV_ENC_SUCCESS) {
|
||||
// Old nvidia gpus dont support the new nvenc api (which is required for av1).
|
||||
// In such cases fallback to old api version if possible and try again.
|
||||
function_list.version = NVENCAPI_STRUCT_VERSION_470(2);
|
||||
function_list.version = NVENCAPI_STRUCT_VERSION_CUSTOM(NVENCAPI_VERSION_470, 2);
|
||||
if(nvEncodeAPICreateInstance(&function_list) != NV_ENC_SUCCESS) {
|
||||
fprintf(stderr, "gsr error: gsr_get_supported_video_codecs_nvenc: nvEncodeAPICreateInstance (retry) failed\n");
|
||||
goto done;
|
||||
}
|
||||
|
||||
params.version = NVENCAPI_STRUCT_VERSION_470(1);
|
||||
params.version = NVENCAPI_STRUCT_VERSION_CUSTOM(NVENCAPI_VERSION_470, 1);
|
||||
params.apiVersion = NVENCAPI_VERSION_470;
|
||||
if(function_list.nvEncOpenEncodeSessionEx(¶ms, &nvenc_encoder) != NV_ENC_SUCCESS) {
|
||||
fprintf(stderr, "gsr error: gsr_get_supported_video_codecs_nvenc: nvEncOpenEncodeSessionEx (retry) failed\n");
|
||||
@@ -220,7 +244,7 @@ bool gsr_get_supported_video_codecs_nvenc(gsr_supported_video_codecs *video_code
|
||||
}
|
||||
}
|
||||
|
||||
success = get_supported_video_codecs(&function_list, nvenc_encoder, video_codecs);
|
||||
success = get_supported_video_codecs(&function_list, nvenc_encoder, video_codecs, params.apiVersion);
|
||||
|
||||
done:
|
||||
if(cleanup) {
|
||||
|
||||
@@ -77,32 +77,51 @@ static bool profile_is_vp9(VAProfile profile) {
|
||||
}
|
||||
}
|
||||
|
||||
static bool profile_supports_video_encoding(VADisplay va_dpy, VAProfile profile, bool *low_power) {
|
||||
*low_power = false;
|
||||
/* Returns 0, 0 on error */
|
||||
static vec2i profile_entrypoint_get_max_resolution(VADisplay va_dpy, VAProfile profile, VAEntrypoint entrypoint) {
|
||||
VAConfigAttrib attribs[2] = {
|
||||
{
|
||||
.type = VAConfigAttribMaxPictureWidth,
|
||||
},
|
||||
{
|
||||
.type = VAConfigAttribMaxPictureHeight,
|
||||
}
|
||||
};
|
||||
if(vaGetConfigAttributes(va_dpy, profile, entrypoint, attribs, 2) != VA_STATUS_SUCCESS)
|
||||
return (vec2i){0, 0};
|
||||
|
||||
return (vec2i){ attribs[0].value, attribs[1].value };
|
||||
}
|
||||
|
||||
/* Returns 0 on error or if none is supported */
|
||||
static VAEntrypoint profile_get_video_encoding_entrypoint(VADisplay va_dpy, VAProfile profile) {
|
||||
int num_entrypoints = vaMaxNumEntrypoints(va_dpy);
|
||||
if(num_entrypoints <= 0)
|
||||
return false;
|
||||
return 0;
|
||||
|
||||
VAEntrypoint *entrypoint_list = calloc(num_entrypoints, sizeof(VAEntrypoint));
|
||||
if(!entrypoint_list)
|
||||
return false;
|
||||
return 0;
|
||||
|
||||
bool supports_encoding = false;
|
||||
bool supports_low_power_encoding = false;
|
||||
int encoding_entrypoint_index = -1;
|
||||
int lower_power_entrypoint_index = -1;
|
||||
if(vaQueryConfigEntrypoints(va_dpy, profile, entrypoint_list, &num_entrypoints) == VA_STATUS_SUCCESS) {
|
||||
for(int i = 0; i < num_entrypoints; ++i) {
|
||||
if(entrypoint_list[i] == VAEntrypointEncSlice)
|
||||
supports_encoding = true;
|
||||
encoding_entrypoint_index = i;
|
||||
else if(entrypoint_list[i] == VAEntrypointEncSliceLP)
|
||||
supports_low_power_encoding = true;
|
||||
lower_power_entrypoint_index = i;
|
||||
}
|
||||
}
|
||||
|
||||
if(!supports_encoding && supports_low_power_encoding)
|
||||
*low_power = true;
|
||||
VAEntrypoint encoding_entrypoint = 0;
|
||||
if(encoding_entrypoint_index != -1)
|
||||
encoding_entrypoint = entrypoint_list[encoding_entrypoint_index];
|
||||
else if(lower_power_entrypoint_index != -1)
|
||||
encoding_entrypoint = entrypoint_list[lower_power_entrypoint_index];
|
||||
|
||||
free(entrypoint_list);
|
||||
return supports_encoding || supports_low_power_encoding;
|
||||
return encoding_entrypoint;
|
||||
}
|
||||
|
||||
static bool get_supported_video_codecs(VADisplay va_dpy, gsr_supported_video_codecs *video_codecs, bool cleanup) {
|
||||
@@ -128,31 +147,45 @@ static bool get_supported_video_codecs(VADisplay va_dpy, gsr_supported_video_cod
|
||||
goto fail;
|
||||
|
||||
for(int i = 0; i < num_profiles; ++i) {
|
||||
bool low_power = false;
|
||||
if(profile_is_h264(profile_list[i])) {
|
||||
if(profile_supports_video_encoding(va_dpy, profile_list[i], &low_power)) {
|
||||
video_codecs->h264 = (gsr_supported_video_codec){ true, low_power };
|
||||
const VAEntrypoint encoding_entrypoint = profile_get_video_encoding_entrypoint(va_dpy, profile_list[i]);
|
||||
if(encoding_entrypoint != 0) {
|
||||
const vec2i max_resolution = profile_entrypoint_get_max_resolution(va_dpy, profile_list[i], encoding_entrypoint);
|
||||
video_codecs->h264 = (gsr_supported_video_codec){ true, encoding_entrypoint == VAEntrypointEncSliceLP, max_resolution };
|
||||
}
|
||||
} else if(profile_is_hevc_8bit(profile_list[i])) {
|
||||
if(profile_supports_video_encoding(va_dpy, profile_list[i], &low_power))
|
||||
video_codecs->hevc = (gsr_supported_video_codec){ true, low_power };
|
||||
const VAEntrypoint encoding_entrypoint = profile_get_video_encoding_entrypoint(va_dpy, profile_list[i]);
|
||||
if(encoding_entrypoint != 0) {
|
||||
const vec2i max_resolution = profile_entrypoint_get_max_resolution(va_dpy, profile_list[i], encoding_entrypoint);
|
||||
video_codecs->hevc = (gsr_supported_video_codec){ true, encoding_entrypoint == VAEntrypointEncSliceLP, max_resolution };
|
||||
}
|
||||
} else if(profile_is_hevc_10bit(profile_list[i])) {
|
||||
if(profile_supports_video_encoding(va_dpy, profile_list[i], &low_power)) {
|
||||
video_codecs->hevc_hdr = (gsr_supported_video_codec){ true, low_power };
|
||||
video_codecs->hevc_10bit = (gsr_supported_video_codec){ true, low_power };
|
||||
const VAEntrypoint encoding_entrypoint = profile_get_video_encoding_entrypoint(va_dpy, profile_list[i]);
|
||||
if(encoding_entrypoint != 0) {
|
||||
const vec2i max_resolution = profile_entrypoint_get_max_resolution(va_dpy, profile_list[i], encoding_entrypoint);
|
||||
video_codecs->hevc_hdr = (gsr_supported_video_codec){ true, encoding_entrypoint == VAEntrypointEncSliceLP, max_resolution };
|
||||
video_codecs->hevc_10bit = (gsr_supported_video_codec){ true, encoding_entrypoint == VAEntrypointEncSliceLP, max_resolution };
|
||||
}
|
||||
} else if(profile_is_av1(profile_list[i])) {
|
||||
if(profile_supports_video_encoding(va_dpy, profile_list[i], &low_power)) {
|
||||
video_codecs->av1 = (gsr_supported_video_codec){ true, low_power };
|
||||
video_codecs->av1_hdr = (gsr_supported_video_codec){ true, low_power };
|
||||
video_codecs->av1_10bit = (gsr_supported_video_codec){ true, low_power };
|
||||
const VAEntrypoint encoding_entrypoint = profile_get_video_encoding_entrypoint(va_dpy, profile_list[i]);
|
||||
if(encoding_entrypoint != 0) {
|
||||
const vec2i max_resolution = profile_entrypoint_get_max_resolution(va_dpy, profile_list[i], encoding_entrypoint);
|
||||
video_codecs->av1 = (gsr_supported_video_codec){ true, encoding_entrypoint == VAEntrypointEncSliceLP, max_resolution };
|
||||
video_codecs->av1_hdr = (gsr_supported_video_codec){ true, encoding_entrypoint == VAEntrypointEncSliceLP, max_resolution };
|
||||
video_codecs->av1_10bit = (gsr_supported_video_codec){ true, encoding_entrypoint == VAEntrypointEncSliceLP, max_resolution };
|
||||
}
|
||||
} else if(profile_is_vp8(profile_list[i])) {
|
||||
if(profile_supports_video_encoding(va_dpy, profile_list[i], &low_power))
|
||||
video_codecs->vp8 = (gsr_supported_video_codec){ true, low_power };
|
||||
const VAEntrypoint encoding_entrypoint = profile_get_video_encoding_entrypoint(va_dpy, profile_list[i]);
|
||||
if(encoding_entrypoint != 0) {
|
||||
const vec2i max_resolution = profile_entrypoint_get_max_resolution(va_dpy, profile_list[i], encoding_entrypoint);
|
||||
video_codecs->vp8 = (gsr_supported_video_codec){ true, encoding_entrypoint == VAEntrypointEncSliceLP, max_resolution };
|
||||
}
|
||||
} else if(profile_is_vp9(profile_list[i])) {
|
||||
if(profile_supports_video_encoding(va_dpy, profile_list[i], &low_power))
|
||||
video_codecs->vp9 = (gsr_supported_video_codec){ true, low_power };
|
||||
const VAEntrypoint encoding_entrypoint = profile_get_video_encoding_entrypoint(va_dpy, profile_list[i]);
|
||||
if(encoding_entrypoint != 0) {
|
||||
const vec2i max_resolution = profile_entrypoint_get_max_resolution(va_dpy, profile_list[i], encoding_entrypoint);
|
||||
video_codecs->vp9 = (gsr_supported_video_codec){ true, encoding_entrypoint == VAEntrypointEncSliceLP, max_resolution };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
};
|
||||
|
||||
|
||||
336
src/main.cpp
336
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>
|
||||
@@ -336,7 +337,7 @@ static int vbr_get_quality_parameter(AVCodecContext *codec_context, gsr_video_qu
|
||||
return 22 * qp_multiply;
|
||||
}
|
||||
|
||||
static AVCodecContext *create_video_codec_context(AVPixelFormat pix_fmt, const AVCodec *codec, const gsr_egl &egl, const args_parser &arg_parser) {
|
||||
static AVCodecContext *create_video_codec_context(AVPixelFormat pix_fmt, const AVCodec *codec, const gsr_egl &egl, const args_parser &arg_parser, int width, int height) {
|
||||
const bool use_software_video_encoder = arg_parser.video_encoder == GSR_VIDEO_ENCODER_HW_CPU;
|
||||
const bool hdr = video_codec_is_hdr(arg_parser.video_codec);
|
||||
AVCodecContext *codec_context = avcodec_alloc_context3(codec);
|
||||
@@ -345,6 +346,8 @@ static AVCodecContext *create_video_codec_context(AVPixelFormat pix_fmt, const A
|
||||
|
||||
assert(codec->type == AVMEDIA_TYPE_VIDEO);
|
||||
codec_context->codec_id = codec->id;
|
||||
codec_context->width = width;
|
||||
codec_context->height = height;
|
||||
// Timebase: This is the fundamental unit of time (in seconds) in terms
|
||||
// of which frame timestamps are represented. For fixed-fps content,
|
||||
// timebase should be 1/framerate and timestamp increments should be
|
||||
@@ -515,6 +518,9 @@ static AVCodecContext *create_video_codec_context(AVPixelFormat pix_fmt, const A
|
||||
|
||||
//av_opt_set(codec_context->priv_data, "bsf", "hevc_metadata=colour_primaries=9:transfer_characteristics=16:matrix_coefficients=9", 0);
|
||||
|
||||
if(arg_parser.tune == GSR_TUNE_QUALITY)
|
||||
codec_context->max_b_frames = 2;
|
||||
|
||||
codec_context->flags |= AV_CODEC_FLAG_GLOBAL_HEADER;
|
||||
|
||||
return codec_context;
|
||||
@@ -1611,7 +1617,8 @@ static bool get_supported_video_codecs(gsr_egl *egl, gsr_video_codec video_codec
|
||||
memset(video_codecs, 0, sizeof(*video_codecs));
|
||||
|
||||
if(use_software_video_encoder) {
|
||||
video_codecs->h264.supported = true;
|
||||
video_codecs->h264.supported = avcodec_find_encoder_by_name("libx264");
|
||||
video_codecs->h264.max_resolution = {4096, 2304};
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -1751,14 +1758,16 @@ static void set_supported_video_codecs_ffmpeg(gsr_supported_video_codecs *suppor
|
||||
supported_video_codecs->vp9.supported = false;
|
||||
}
|
||||
|
||||
if(!get_ffmpeg_video_codec(GSR_VIDEO_CODEC_H264_VULKAN, vendor)) {
|
||||
supported_video_codecs_vulkan->h264.supported = false;
|
||||
}
|
||||
if(supported_video_codecs_vulkan) {
|
||||
if(!get_ffmpeg_video_codec(GSR_VIDEO_CODEC_H264_VULKAN, vendor)) {
|
||||
supported_video_codecs_vulkan->h264.supported = false;
|
||||
}
|
||||
|
||||
if(!get_ffmpeg_video_codec(GSR_VIDEO_CODEC_HEVC_VULKAN, vendor)) {
|
||||
supported_video_codecs_vulkan->hevc.supported = false;
|
||||
supported_video_codecs_vulkan->hevc_hdr.supported = false;
|
||||
supported_video_codecs_vulkan->hevc_10bit.supported = false;
|
||||
if(!get_ffmpeg_video_codec(GSR_VIDEO_CODEC_HEVC_VULKAN, vendor)) {
|
||||
supported_video_codecs_vulkan->hevc.supported = false;
|
||||
supported_video_codecs_vulkan->hevc_hdr.supported = false;
|
||||
supported_video_codecs_vulkan->hevc_10bit.supported = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2286,8 +2295,6 @@ static void capture_image_to_file(args_parser &arg_parser, gsr_egl *egl, gsr_ima
|
||||
capture_metadata.width = 0;
|
||||
capture_metadata.height = 0;
|
||||
capture_metadata.fps = fps;
|
||||
capture_metadata.video_codec_context = nullptr;
|
||||
capture_metadata.frame = nullptr;
|
||||
|
||||
int capture_result = gsr_capture_start(capture, &capture_metadata);
|
||||
if(capture_result != 0) {
|
||||
@@ -2340,11 +2347,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);
|
||||
@@ -2559,85 +2571,149 @@ static bool video_codec_only_supports_low_power_mode(const gsr_supported_video_c
|
||||
return false;
|
||||
}
|
||||
|
||||
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) {
|
||||
// TODO: software encoder for hevc, av1, vp8 and vp9
|
||||
*low_power = false;
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
const AVCodec *video_codec_f = nullptr;
|
||||
|
||||
switch(*video_codec) {
|
||||
static const AVCodec* get_av_codec_if_supported(gsr_video_codec video_codec, gsr_egl *egl, bool use_software_video_encoder, const gsr_supported_video_codecs *supported_video_codecs) {
|
||||
switch(video_codec) {
|
||||
case GSR_VIDEO_CODEC_H264: {
|
||||
if(use_software_video_encoder)
|
||||
video_codec_f = avcodec_find_encoder_by_name("libx264");
|
||||
else if(supported_video_codecs.h264.supported)
|
||||
video_codec_f = get_ffmpeg_video_codec(*video_codec, egl->gpu_info.vendor);
|
||||
return avcodec_find_encoder_by_name("libx264");
|
||||
else if(supported_video_codecs->h264.supported)
|
||||
return get_ffmpeg_video_codec(video_codec, egl->gpu_info.vendor);
|
||||
break;
|
||||
}
|
||||
case GSR_VIDEO_CODEC_HEVC: {
|
||||
if(supported_video_codecs.hevc.supported)
|
||||
video_codec_f = get_ffmpeg_video_codec(*video_codec, egl->gpu_info.vendor);
|
||||
if(supported_video_codecs->hevc.supported)
|
||||
return get_ffmpeg_video_codec(video_codec, egl->gpu_info.vendor);
|
||||
break;
|
||||
}
|
||||
case GSR_VIDEO_CODEC_HEVC_HDR: {
|
||||
if(supported_video_codecs.hevc_hdr.supported)
|
||||
video_codec_f = get_ffmpeg_video_codec(*video_codec, egl->gpu_info.vendor);
|
||||
if(supported_video_codecs->hevc_hdr.supported)
|
||||
return get_ffmpeg_video_codec(video_codec, egl->gpu_info.vendor);
|
||||
break;
|
||||
}
|
||||
case GSR_VIDEO_CODEC_HEVC_10BIT: {
|
||||
if(supported_video_codecs.hevc_10bit.supported)
|
||||
video_codec_f = get_ffmpeg_video_codec(*video_codec, egl->gpu_info.vendor);
|
||||
if(supported_video_codecs->hevc_10bit.supported)
|
||||
return get_ffmpeg_video_codec(video_codec, egl->gpu_info.vendor);
|
||||
break;
|
||||
}
|
||||
case GSR_VIDEO_CODEC_AV1: {
|
||||
if(supported_video_codecs.av1.supported)
|
||||
video_codec_f = get_ffmpeg_video_codec(*video_codec, egl->gpu_info.vendor);
|
||||
if(supported_video_codecs->av1.supported)
|
||||
return get_ffmpeg_video_codec(video_codec, egl->gpu_info.vendor);
|
||||
break;
|
||||
}
|
||||
case GSR_VIDEO_CODEC_AV1_HDR: {
|
||||
if(supported_video_codecs.av1_hdr.supported)
|
||||
video_codec_f = get_ffmpeg_video_codec(*video_codec, egl->gpu_info.vendor);
|
||||
if(supported_video_codecs->av1_hdr.supported)
|
||||
return get_ffmpeg_video_codec(video_codec, egl->gpu_info.vendor);
|
||||
break;
|
||||
}
|
||||
case GSR_VIDEO_CODEC_AV1_10BIT: {
|
||||
if(supported_video_codecs.av1_10bit.supported)
|
||||
video_codec_f = get_ffmpeg_video_codec(*video_codec, egl->gpu_info.vendor);
|
||||
if(supported_video_codecs->av1_10bit.supported)
|
||||
return get_ffmpeg_video_codec(video_codec, egl->gpu_info.vendor);
|
||||
break;
|
||||
}
|
||||
case GSR_VIDEO_CODEC_VP8: {
|
||||
if(supported_video_codecs.vp8.supported)
|
||||
video_codec_f = get_ffmpeg_video_codec(*video_codec, egl->gpu_info.vendor);
|
||||
if(supported_video_codecs->vp8.supported)
|
||||
return get_ffmpeg_video_codec(video_codec, egl->gpu_info.vendor);
|
||||
break;
|
||||
}
|
||||
case GSR_VIDEO_CODEC_VP9: {
|
||||
if(supported_video_codecs.vp9.supported)
|
||||
video_codec_f = get_ffmpeg_video_codec(*video_codec, egl->gpu_info.vendor);
|
||||
if(supported_video_codecs->vp9.supported)
|
||||
return get_ffmpeg_video_codec(video_codec, egl->gpu_info.vendor);
|
||||
break;
|
||||
}
|
||||
case GSR_VIDEO_CODEC_H264_VULKAN: {
|
||||
if(supported_video_codecs.h264.supported)
|
||||
video_codec_f = get_ffmpeg_video_codec(*video_codec, egl->gpu_info.vendor);
|
||||
if(supported_video_codecs->h264.supported)
|
||||
return get_ffmpeg_video_codec(video_codec, egl->gpu_info.vendor);
|
||||
break;
|
||||
}
|
||||
case GSR_VIDEO_CODEC_HEVC_VULKAN: {
|
||||
// TODO: hdr, 10 bit
|
||||
if(supported_video_codecs.hevc.supported)
|
||||
video_codec_f = get_ffmpeg_video_codec(*video_codec, egl->gpu_info.vendor);
|
||||
if(supported_video_codecs->hevc.supported)
|
||||
return get_ffmpeg_video_codec(video_codec, egl->gpu_info.vendor);
|
||||
break;
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
static vec2i codec_get_max_resolution(gsr_video_codec video_codec, bool use_software_video_encoder, const gsr_supported_video_codecs *supported_video_codecs) {
|
||||
switch(video_codec) {
|
||||
case GSR_VIDEO_CODEC_H264: {
|
||||
if(use_software_video_encoder)
|
||||
return {4096, 2304};
|
||||
else if(supported_video_codecs->h264.supported)
|
||||
return supported_video_codecs->h264.max_resolution;
|
||||
break;
|
||||
}
|
||||
case GSR_VIDEO_CODEC_HEVC: {
|
||||
if(supported_video_codecs->hevc.supported)
|
||||
return supported_video_codecs->hevc.max_resolution;
|
||||
break;
|
||||
}
|
||||
case GSR_VIDEO_CODEC_HEVC_HDR: {
|
||||
if(supported_video_codecs->hevc_hdr.supported)
|
||||
return supported_video_codecs->hevc_hdr.max_resolution;
|
||||
break;
|
||||
}
|
||||
case GSR_VIDEO_CODEC_HEVC_10BIT: {
|
||||
if(supported_video_codecs->hevc_10bit.supported)
|
||||
return supported_video_codecs->hevc_10bit.max_resolution;
|
||||
break;
|
||||
}
|
||||
case GSR_VIDEO_CODEC_AV1: {
|
||||
if(supported_video_codecs->av1.supported)
|
||||
return supported_video_codecs->av1.max_resolution;
|
||||
break;
|
||||
}
|
||||
case GSR_VIDEO_CODEC_AV1_HDR: {
|
||||
if(supported_video_codecs->av1_hdr.supported)
|
||||
return supported_video_codecs->av1_hdr.max_resolution;
|
||||
break;
|
||||
}
|
||||
case GSR_VIDEO_CODEC_AV1_10BIT: {
|
||||
if(supported_video_codecs->av1_10bit.supported)
|
||||
return supported_video_codecs->av1_10bit.max_resolution;
|
||||
break;
|
||||
}
|
||||
case GSR_VIDEO_CODEC_VP8: {
|
||||
if(supported_video_codecs->vp8.supported)
|
||||
return supported_video_codecs->vp8.max_resolution;
|
||||
break;
|
||||
}
|
||||
case GSR_VIDEO_CODEC_VP9: {
|
||||
if(supported_video_codecs->vp9.supported)
|
||||
return supported_video_codecs->vp9.max_resolution;
|
||||
break;
|
||||
}
|
||||
case GSR_VIDEO_CODEC_H264_VULKAN: {
|
||||
if(supported_video_codecs->h264.supported)
|
||||
return supported_video_codecs->h264.max_resolution;
|
||||
break;
|
||||
}
|
||||
case GSR_VIDEO_CODEC_HEVC_VULKAN: {
|
||||
// TODO: hdr, 10 bit
|
||||
if(supported_video_codecs->hevc.supported)
|
||||
return supported_video_codecs->hevc.max_resolution;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return {0, 0};
|
||||
}
|
||||
|
||||
static bool codec_supports_resolution(vec2i codec_max_resolution, vec2i capture_resolution) {
|
||||
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) {
|
||||
// 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);
|
||||
|
||||
if(!video_codec_auto && !video_codec_f && !is_flv) {
|
||||
switch(*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)
|
||||
if(supported_video_codecs->hevc.supported)
|
||||
video_codec_f = get_ffmpeg_video_codec(*video_codec, egl->gpu_info.vendor);
|
||||
break;
|
||||
}
|
||||
@@ -2646,7 +2722,7 @@ static const AVCodec* pick_video_codec(gsr_video_codec *video_codec, gsr_egl *eg
|
||||
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)
|
||||
if(supported_video_codecs->h264.supported)
|
||||
video_codec_f = get_ffmpeg_video_codec(*video_codec, egl->gpu_info.vendor);
|
||||
break;
|
||||
}
|
||||
@@ -2655,7 +2731,7 @@ static const AVCodec* pick_video_codec(gsr_video_codec *video_codec, gsr_egl *eg
|
||||
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)
|
||||
if(supported_video_codecs->h264.supported)
|
||||
video_codec_f = get_ffmpeg_video_codec(*video_codec, egl->gpu_info.vendor);
|
||||
break;
|
||||
}
|
||||
@@ -2667,11 +2743,11 @@ static const AVCodec* pick_video_codec(gsr_video_codec *video_codec, gsr_egl *eg
|
||||
fprintf(stderr, "gsr warning: selected video codec h264_vulkan is not supported, trying h264 instead\n");
|
||||
*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(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);
|
||||
}
|
||||
if(supported_video_codecs.h264.supported)
|
||||
if(supported_video_codecs->h264.supported)
|
||||
video_codec_f = get_ffmpeg_video_codec(*video_codec, egl->gpu_info.vendor);
|
||||
break;
|
||||
}
|
||||
@@ -2679,11 +2755,11 @@ static const AVCodec* pick_video_codec(gsr_video_codec *video_codec, gsr_egl *eg
|
||||
fprintf(stderr, "gsr warning: selected video codec hevc_vulkan is not supported, trying hevc instead\n");
|
||||
*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(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);
|
||||
}
|
||||
if(supported_video_codecs.hevc.supported)
|
||||
if(supported_video_codecs->hevc.supported)
|
||||
video_codec_f = get_ffmpeg_video_codec(*video_codec, egl->gpu_info.vendor);
|
||||
break;
|
||||
}
|
||||
@@ -2702,23 +2778,57 @@ static const AVCodec* pick_video_codec(gsr_video_codec *video_codec, gsr_egl *eg
|
||||
" 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);
|
||||
_exit(2);
|
||||
_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, *video_codec);
|
||||
|
||||
return video_codec_f;
|
||||
}
|
||||
|
||||
static const AVCodec* select_video_codec_with_fallback(gsr_video_codec *video_codec, const char *file_extension, bool use_software_video_encoder, gsr_egl *egl, bool *low_power) {
|
||||
/* 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};
|
||||
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;
|
||||
} else if(supported_video_codecs->hevc.supported && codec_supports_resolution(supported_video_codecs->hevc.max_resolution, capture_size)) {
|
||||
fprintf(stderr, "gsr info: using hevc encoder because a codec was not specified and h264 supported max resolution (%dx%d) is less than capture resolution (%dx%d)\n",
|
||||
supported_video_codecs->h264.max_resolution.x, supported_video_codecs->h264.max_resolution.y,
|
||||
capture_size.x, capture_size.y);
|
||||
return GSR_VIDEO_CODEC_HEVC;
|
||||
} else if(supported_video_codecs->av1.supported && codec_supports_resolution(supported_video_codecs->av1.max_resolution, capture_size)) {
|
||||
fprintf(stderr, "gsr info: using av1 encoder because a codec was not specified and hevc supported max resolution (%dx%d) is less than capture resolution (%dx%d)\n",
|
||||
supported_video_codecs->hevc.max_resolution.x, supported_video_codecs->hevc.max_resolution.y,
|
||||
capture_size.x, capture_size.y);
|
||||
return GSR_VIDEO_CODEC_AV1;
|
||||
} else {
|
||||
return (gsr_video_codec)-1;
|
||||
}
|
||||
}
|
||||
|
||||
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) {
|
||||
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);
|
||||
}
|
||||
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;
|
||||
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 {
|
||||
} else if(use_software_video_encoder) {
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2757,7 +2867,18 @@ static const AVCodec* select_video_codec_with_fallback(gsr_video_codec *video_co
|
||||
_exit(1);
|
||||
}
|
||||
|
||||
return pick_video_codec(video_codec, egl, use_software_video_encoder, video_codec_auto, is_flv, low_power);
|
||||
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};
|
||||
if(!codec_supports_resolution(codec_max_resolution, capture_size)) {
|
||||
const char *video_codec_name = video_codec_to_string(*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);
|
||||
}
|
||||
|
||||
return codec;
|
||||
}
|
||||
|
||||
static std::vector<AudioDeviceData> create_device_audio_inputs(const std::vector<AudioInput> &audio_inputs, AVCodecContext *audio_codec_context, int num_channels, double num_audio_frames_shift, std::vector<AVFilterContext*> &src_filter_ctx, bool use_amix) {
|
||||
@@ -3126,10 +3247,19 @@ int main(int argc, char **argv) {
|
||||
|
||||
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);
|
||||
bool low_power = false;
|
||||
const AVCodec *video_codec_f = select_video_codec_with_fallback(&arg_parser.video_codec, file_extension.c_str(), arg_parser.video_encoder == GSR_VIDEO_ENCODER_HW_CPU, &egl, &low_power);
|
||||
|
||||
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.fps = arg_parser.fps;
|
||||
|
||||
int capture_result = gsr_capture_start(capture, &capture_metadata);
|
||||
if(capture_result != 0) {
|
||||
fprintf(stderr, "gsr error: gsr_capture_start failed\n");
|
||||
_exit(capture_result);
|
||||
}
|
||||
|
||||
// (Some?) livestreaming services require at least one audio track to work.
|
||||
// If not audio is provided then create one silent audio track.
|
||||
@@ -3143,46 +3273,28 @@ int main(int argc, char **argv) {
|
||||
AVStream *video_stream = nullptr;
|
||||
std::vector<AudioTrack> audio_tracks;
|
||||
|
||||
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 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);
|
||||
AVCodecContext *video_codec_context = create_video_codec_context(video_pix_fmt, video_codec_f, egl, arg_parser, capture_metadata.width, capture_metadata.height);
|
||||
if(!is_replaying)
|
||||
video_stream = create_stream(av_format_context, video_codec_context);
|
||||
|
||||
if(arg_parser.tune == GSR_TUNE_QUALITY)
|
||||
video_codec_context->max_b_frames = 2;
|
||||
|
||||
AVFrame *video_frame = av_frame_alloc();
|
||||
if(!video_frame) {
|
||||
fprintf(stderr, "gsr error: Failed to allocate video frame\n");
|
||||
_exit(1);
|
||||
}
|
||||
video_frame->format = video_codec_context->pix_fmt;
|
||||
video_frame->width = 0;
|
||||
video_frame->height = 0;
|
||||
video_frame->width = capture_metadata.width;
|
||||
video_frame->height = capture_metadata.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;
|
||||
video_frame->colorspace = video_codec_context->colorspace;
|
||||
video_frame->chroma_location = video_codec_context->chroma_sample_location;
|
||||
|
||||
gsr_capture_metadata capture_metadata;
|
||||
capture_metadata.width = 0;
|
||||
capture_metadata.height = 0;
|
||||
capture_metadata.fps = arg_parser.fps;
|
||||
capture_metadata.video_codec_context = video_codec_context;
|
||||
capture_metadata.frame = video_frame;
|
||||
|
||||
int capture_result = gsr_capture_start(capture, &capture_metadata);
|
||||
if(capture_result != 0) {
|
||||
fprintf(stderr, "gsr error: gsr_capture_start failed\n");
|
||||
_exit(capture_result);
|
||||
}
|
||||
|
||||
video_codec_context->width = capture_metadata.width;
|
||||
video_codec_context->height = capture_metadata.height;
|
||||
video_frame->width = capture_metadata.width;
|
||||
video_frame->height = capture_metadata.height;
|
||||
|
||||
const size_t estimated_replay_buffer_packets = calculate_estimated_replay_buffer_packets(arg_parser.replay_buffer_size_secs, arg_parser.fps, arg_parser.audio_codec, requested_audio_inputs);
|
||||
gsr_encoder encoder;
|
||||
if(!gsr_encoder_init(&encoder, arg_parser.replay_storage, estimated_replay_buffer_packets, arg_parser.replay_buffer_size_secs, arg_parser.filename)) {
|
||||
@@ -3201,9 +3313,37 @@ int main(int argc, char **argv) {
|
||||
_exit(1);
|
||||
}
|
||||
|
||||
// TODO: What if this updated resolution is above max resolution?
|
||||
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;
|
||||
@@ -3219,6 +3359,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 {
|
||||
@@ -3621,7 +3763,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);
|
||||
@@ -3629,7 +3779,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;
|
||||
@@ -3796,6 +3946,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
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);
|
||||
}
|
||||
10
src/utils.c
10
src/utils.c
@@ -285,13 +285,9 @@ typedef struct {
|
||||
bool match_found;
|
||||
} get_monitor_by_connector_id_userdata;
|
||||
|
||||
static bool vec2i_eql(vec2i a, vec2i b) {
|
||||
return a.x == b.x && a.y == b.y;
|
||||
}
|
||||
|
||||
static void get_monitor_by_name_and_size_callback(const gsr_monitor *monitor, void *userdata) {
|
||||
static void get_monitor_by_name_wayland_callback(const gsr_monitor *monitor, void *userdata) {
|
||||
get_monitor_by_connector_id_userdata *data = (get_monitor_by_connector_id_userdata*)userdata;
|
||||
if(monitor->name && data->monitor->name && strcmp(monitor->name, data->monitor->name) == 0 && vec2i_eql(monitor->size, data->monitor->size)) {
|
||||
if(monitor->name && data->monitor->name && strcmp(monitor->name, data->monitor->name) == 0) {
|
||||
data->rotation = monitor->rotation;
|
||||
data->position = monitor->pos;
|
||||
data->match_found = true;
|
||||
@@ -320,7 +316,7 @@ bool drm_monitor_get_display_server_data(const gsr_window *window, const gsr_mon
|
||||
userdata.rotation = GSR_MONITOR_ROT_0;
|
||||
userdata.position = (vec2i){0, 0};
|
||||
userdata.match_found = false;
|
||||
gsr_window_for_each_active_monitor_output_cached(window, get_monitor_by_name_and_size_callback, &userdata);
|
||||
gsr_window_for_each_active_monitor_output_cached(window, get_monitor_by_name_wayland_callback, &userdata);
|
||||
if(userdata.match_found) {
|
||||
*monitor_rotation = userdata.rotation;
|
||||
*monitor_position = userdata.position;
|
||||
|
||||
Reference in New Issue
Block a user