follow focused

This commit is contained in:
dec05eba 2022-12-01 00:47:30 +01:00
parent 6a6bb703bc
commit 4e6fc174fe
14 changed files with 345 additions and 277 deletions

View File

@ -27,7 +27,7 @@ If you are running another distro then you can run `install.sh` as root: `sudo .
You can also install gpu screen recorder ([the gtk gui version](https://git.dec05eba.com/gpu-screen-recorder-gtk/)) from [flathub](https://flathub.org/apps/details/com.dec05eba.gpu_screen_recorder).
# Dependencies
`libgl (and libegl) (libglvnd), ffmpeg, libx11, libxcomposite, libpulse`. You need to additionally have `libcuda.so` installed when you run `gpu-screen-recorder` and `libnvidia-fbc.so.1` when using nvfbc.\
`libglvnd (which provides libgl and libegl), (mesa if you are using an amd or intel gpu), ffmpeg, libx11, libxcomposite, libpulse`. You need to additionally have `libcuda.so` installed when you run `gpu-screen-recorder` and `libnvidia-fbc.so.1` when using nvfbc.\
Recording monitors requires a gpu with NvFBC support (note: this is not required when recording a single window!). Normally only tesla and quadro gpus support this, but by using [nvidia-patch](https://github.com/keylase/nvidia-patch) or [nvlax](https://github.com/illnyang/nvlax) you can do this on all gpus that support nvenc as well (gpus as old as the nvidia 600 series), provided you are not using outdated gpu drivers.
# How to use
@ -56,5 +56,5 @@ FFMPEG only uses the GPU with CUDA when doing transcoding from an input video to
# TODO
* Dynamically change bitrate/resolution to match desired fps. This would be helpful when streaming for example, where the encode output speed also depends on upload speed to the streaming service.
* Show cursor when recording. Currently the cursor is not visible when recording a window.
* Implement opengl injection to capture texture. This fixes composition issues and (VRR) without having to use NvFBC direct capture.
* Implement opengl injection to capture texture. This fixes VRR without having to use NvFBC direct capture.
* Always use direct capture with NvFBC once the capture issue in mpv fullscreen has been resolved (maybe detect if direct capture fails in nvfbc and switch to non-direct recording. NvFBC says if direct capture fails).

10
TODO
View File

@ -1,6 +1,4 @@
Check for reparent.
Only add window to list if its the window is a topmost window.
Track window damages and only update then. That is better for output file size.
Quickly changing workspace and back while recording under i3 breaks the screen recorder. i3 probably unmaps windows in other workspaces.
See https://trac.ffmpeg.org/wiki/EncodingForStreamingSites for optimizing streaming.
Add option to merge audio tracks into one (muxing?) by adding multiple audio streams in one -a arg separated by comma.
@ -9,9 +7,9 @@ Allow setting a different output resolution than the input resolution.
Use mov+faststart.
Allow recording all monitors/selected monitor without nvfbc by recording the compositor proxy window and only recording the part that matches the monitor(s).
Allow recording a region by recording the compositor proxy window / nvfbc window and copying part of it.
Resizing the target window to be smaller than the initial size is buggy. The window texture ends up duplicated in the video.
Handle frames (especially for applications with rounded client-side decorations, such as gnome applications. They are huge).
Use nvenc directly, which allows removing the use of cuda.
Handle xrandr monitor change in nvfbc.
Add option to track the focused window. In that case the video size should dynamically change (change frame resolution) to match the window size and it should update when the window resizes.
Add option for 4:4:4 chroma sampling for the output video.
Add option for yuv 4:4:4 chroma sampling for the output video.
Implement follow focused in drm.
Support fullscreen capture on amd/intel using external kms process.
Support amf and qsv.

View File

@ -9,6 +9,8 @@ typedef struct _XDisplay Display;
typedef struct {
Window window;
bool follow_focused; /* If this is set then |window| is ignored */
vec2i region_size; /* This is currently only used with |follow_focused| */
} gsr_capture_xcomposite_cuda_params;
gsr_capture* gsr_capture_xcomposite_cuda_create(const gsr_capture_xcomposite_cuda_params *params);

View File

@ -9,6 +9,8 @@ typedef struct _XDisplay Display;
typedef struct {
Window window;
bool follow_focused; /* If this is set then |window| is ignored */
vec2i region_size; /* This is currently only used with |follow_focused| */
} gsr_capture_xcomposite_drm_params;
gsr_capture* gsr_capture_xcomposite_drm_create(const gsr_capture_xcomposite_drm_params *params);

View File

@ -99,12 +99,16 @@ typedef struct {
EGLDisplay (*eglGetDisplay)(EGLNativeDisplayType display_id);
unsigned int (*eglInitialize)(EGLDisplay dpy, int32_t *major, int32_t *minor);
unsigned int (*eglTerminate)(EGLDisplay dpy);
unsigned int (*eglChooseConfig)(EGLDisplay dpy, const int32_t *attrib_list, EGLConfig *configs, int32_t config_size, int32_t *num_config);
EGLSurface (*eglCreateWindowSurface)(EGLDisplay dpy, EGLConfig config, EGLNativeWindowType win, const int32_t *attrib_list);
EGLContext (*eglCreateContext)(EGLDisplay dpy, EGLConfig config, EGLContext share_context, const int32_t *attrib_list);
unsigned int (*eglMakeCurrent)(EGLDisplay dpy, EGLSurface draw, EGLSurface read, EGLContext ctx);
EGLSurface (*eglCreatePixmapSurface)(EGLDisplay dpy, EGLConfig config, EGLNativePixmapType pixmap, const int32_t *attrib_list);
EGLImage (*eglCreateImage)(EGLDisplay dpy, EGLContext ctx, unsigned int target, EGLClientBuffer buffer, const intptr_t *attrib_list);
unsigned int (*eglDestroyContext)(EGLDisplay dpy, EGLContext ctx);
unsigned int (*eglDestroySurface)(EGLDisplay dpy, EGLSurface surface);
unsigned int (*eglDestroyImage)(EGLDisplay dpy, EGLImage image);
unsigned int (*eglBindTexImage)(EGLDisplay dpy, EGLSurface surface, int32_t buffer);
unsigned int (*eglSwapInterval)(EGLDisplay dpy, int32_t interval);
unsigned int (*eglSwapBuffers)(EGLDisplay dpy, EGLSurface surface);
@ -125,6 +129,7 @@ typedef struct {
void (*glGetTexLevelParameteriv)(unsigned int target, int level, unsigned int pname, int *params);
void (*glTexImage2D)(unsigned int target, int level, int internalFormat, int width, int height, int border, unsigned int format, unsigned int type, const void *pixels);
void (*glCopyImageSubData)(unsigned int srcName, unsigned int srcTarget, int srcLevel, int srcX, int srcY, int srcZ, unsigned int dstName, unsigned int dstTarget, int dstLevel, int dstX, int dstY, int dstZ, int srcWidth, int srcHeight, int srcDepth);
void (*glClearTexImage)(unsigned int texture, unsigned int level, unsigned int format, unsigned int type, const void *data);
void (*glGenFramebuffers)(int n, unsigned int *framebuffers);
void (*glBindFramebuffer)(unsigned int target, unsigned int framebuffer);
void (*glViewport)(int x, int y, int width, int height);
@ -164,7 +169,6 @@ typedef struct {
} gsr_egl;
bool gsr_egl_load(gsr_egl *self, Display *dpy);
bool gsr_egl_make_context_current(gsr_egl *self);
void gsr_egl_unload(gsr_egl *self);
#endif /* GSR_EGL_H */

View File

@ -2,6 +2,7 @@
#define GSR_LIBRARY_LOADER_H
#include <dlfcn.h>
#include <stdbool.h>
#include <stdio.h>
typedef struct {

View File

@ -8,9 +8,6 @@ typedef struct {
Window window;
Pixmap pixmap;
unsigned int texture_id;
unsigned int target_texture_id;
int texture_width;
int texture_height;
int redirected;
gsr_egl *egl;
} WindowTexture;

View File

@ -1,7 +1,7 @@
[package]
name = "gpu-screen-recorder"
type = "executable"
version = "1.2.0"
version = "1.3.0"
platforms = ["posix"]
[dependencies]
@ -13,4 +13,3 @@ xcomposite = ">=0.2"
xrandr = ">=1"
libpulse = ">=13"
libswresample = ">=3"
#libdrm = ">=2"

View File

@ -344,8 +344,11 @@ static int gsr_capture_nvfbc_start(gsr_capture *cap, AVCodecContext *video_codec
cap_nvfbc->fbc_handle_created = false;
}
if(video_codec_context->hw_device_ctx)
av_buffer_unref(&video_codec_context->hw_device_ctx);
av_buffer_unref(&video_codec_context->hw_frames_ctx);
// Not needed because the above call to unref device ctx also frees this?
//if(video_codec_context->hw_frames_ctx)
// av_buffer_unref(&video_codec_context->hw_frames_ctx);
gsr_cuda_unload(&cap_nvfbc->cuda);
return -1;
}
@ -413,8 +416,11 @@ static int gsr_capture_nvfbc_capture(gsr_capture *cap, AVFrame *frame) {
static void gsr_capture_nvfbc_destroy(gsr_capture *cap, AVCodecContext *video_codec_context) {
gsr_capture_nvfbc *cap_nvfbc = cap->priv;
gsr_capture_nvfbc_destroy_session(cap);
if(video_codec_context->hw_device_ctx)
av_buffer_unref(&video_codec_context->hw_device_ctx);
av_buffer_unref(&video_codec_context->hw_frames_ctx);
// Not needed because the above call to unref device ctx also frees this?
//if(video_codec_context->hw_frames_ctx)
// av_buffer_unref(&video_codec_context->hw_frames_ctx);
if(cap_nvfbc) {
gsr_cuda_unload(&cap_nvfbc->cuda);
dlclose(cap_nvfbc->library);

View File

@ -20,12 +20,12 @@ typedef struct {
double window_resize_timer;
vec2i window_size;
vec2i window_pos;
unsigned int target_texture_id;
vec2i texture_size;
Window composite_window;
Window window;
WindowTexture window_texture;
Atom net_active_window_atom;
CUgraphicsResource cuda_graphics_resource;
CUarray mapped_array;
@ -42,6 +42,20 @@ static int min_int(int a, int b) {
return a < b ? a : b;
}
static Window get_focused_window(Display *display, Atom net_active_window_atom) {
Atom type;
int format = 0;
unsigned long num_items = 0;
unsigned long bytes_after = 0;
unsigned char *properties = NULL;
if(XGetWindowProperty(display, DefaultRootWindow(display), net_active_window_atom, 0, 1024, False, AnyPropertyType, &type, &format, &num_items, &bytes_after, &properties) == Success && properties) {
Window focused_window = *(unsigned long*)properties;
XFree(properties);
return focused_window;
}
return None;
}
static void gsr_capture_xcomposite_cuda_stop(gsr_capture *cap, AVCodecContext *video_codec_context);
static bool cuda_register_opengl_texture(gsr_capture_xcomposite_cuda *cap_xcomp) {
@ -58,6 +72,7 @@ static bool cuda_register_opengl_texture(gsr_capture_xcomposite_cuda *cap_xcomp)
"Error: cuGraphicsGLRegisterImage failed, error %s, texture "
"id: %u\n",
err_str, cap_xcomp->target_texture_id);
res = cap_xcomp->cuda.cuCtxPopCurrent_v2(&old_ctx);
return false;
}
@ -122,13 +137,6 @@ static bool cuda_create_codec_context(gsr_capture_xcomposite_cuda *cap_xcomp, AV
}
static unsigned int gl_create_texture(gsr_capture_xcomposite_cuda *cap_xcomp, int width, int height) {
// Generating this second texture is needed because
// cuGraphicsGLRegisterImage cant be used with the texture that is mapped
// directly to the pixmap.
// TODO: Investigate if it's somehow possible to use the pixmap texture
// directly, this should improve performance since only less image copy is
// then needed every frame.
// Ignoring failure for now.. TODO: Show proper error
unsigned int texture_id = 0;
cap_xcomp->egl.glGenTextures(1, &texture_id);
cap_xcomp->egl.glBindTexture(GL_TEXTURE_2D, texture_id);
@ -136,8 +144,8 @@ static unsigned int gl_create_texture(gsr_capture_xcomposite_cuda *cap_xcomp, in
cap_xcomp->egl.glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
cap_xcomp->egl.glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
cap_xcomp->egl.glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
cap_xcomp->egl.glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
cap_xcomp->egl.glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
cap_xcomp->egl.glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
cap_xcomp->egl.glBindTexture(GL_TEXTURE_2D, 0);
return texture_id;
@ -146,18 +154,34 @@ static unsigned int gl_create_texture(gsr_capture_xcomposite_cuda *cap_xcomp, in
static int gsr_capture_xcomposite_cuda_start(gsr_capture *cap, AVCodecContext *video_codec_context) {
gsr_capture_xcomposite_cuda *cap_xcomp = cap->priv;
if(cap_xcomp->params.follow_focused) {
cap_xcomp->net_active_window_atom = XInternAtom(cap_xcomp->dpy, "_NET_ACTIVE_WINDOW", False);
if(!cap_xcomp->net_active_window_atom) {
fprintf(stderr, "gsr error: gsr_capture_xcomposite_cuda_start failed: failed to get _NET_ACTIVE_WINDOW atom\n");
return -1;
}
cap_xcomp->window = get_focused_window(cap_xcomp->dpy, cap_xcomp->net_active_window_atom);
} else {
cap_xcomp->window = cap_xcomp->params.window;
}
/* TODO: Do these in tick, and allow error if follow_focused */
XWindowAttributes attr;
if(!XGetWindowAttributes(cap_xcomp->dpy, cap_xcomp->params.window, &attr)) {
fprintf(stderr, "gsr error: gsr_capture_xcomposite_cuda_start failed: invalid window id: %lu\n", cap_xcomp->params.window);
attr.width = 0;
attr.height = 0;
if(!XGetWindowAttributes(cap_xcomp->dpy, cap_xcomp->window, &attr) && !cap_xcomp->params.follow_focused) {
fprintf(stderr, "gsr error: gsr_capture_xcomposite_cuda_start failed: invalid window id: %lu\n", cap_xcomp->window);
return -1;
}
cap_xcomp->window_size.x = max_int(attr.width, 0);
cap_xcomp->window_size.y = max_int(attr.height, 0);
Window c;
XTranslateCoordinates(cap_xcomp->dpy, cap_xcomp->params.window, DefaultRootWindow(cap_xcomp->dpy), 0, 0, &cap_xcomp->window_pos.x, &cap_xcomp->window_pos.y, &c);
XSelectInput(cap_xcomp->dpy, cap_xcomp->params.window, StructureNotifyMask | ExposureMask);
if(cap_xcomp->params.follow_focused)
XSelectInput(cap_xcomp->dpy, DefaultRootWindow(cap_xcomp->dpy), PropertyChangeMask);
XSelectInput(cap_xcomp->dpy, cap_xcomp->window, StructureNotifyMask | ExposureMask);
if(!gsr_egl_load(&cap_xcomp->egl, cap_xcomp->dpy)) {
fprintf(stderr, "gsr error: gsr_capture_xcomposite_cuda_start: failed to load opengl\n");
@ -165,16 +189,16 @@ static int gsr_capture_xcomposite_cuda_start(gsr_capture *cap, AVCodecContext *v
}
cap_xcomp->egl.eglSwapInterval(cap_xcomp->egl.egl_display, 0);
// TODO: Fallback to composite window
if(window_texture_init(&cap_xcomp->window_texture, cap_xcomp->dpy, cap_xcomp->params.window, &cap_xcomp->egl) != 0) {
fprintf(stderr, "gsr error: gsr_capture_xcomposite_cuda_start: failed get window texture for window %ld\n", cap_xcomp->params.window);
if(window_texture_init(&cap_xcomp->window_texture, cap_xcomp->dpy, cap_xcomp->window, &cap_xcomp->egl) != 0 && !cap_xcomp->params.follow_focused) {
fprintf(stderr, "gsr error: gsr_capture_xcomposite_cuda_start: failed get window texture for window %ld\n", cap_xcomp->window);
gsr_egl_unload(&cap_xcomp->egl);
return -1;
}
cap_xcomp->egl.glBindTexture(GL_TEXTURE_2D, window_texture_get_opengl_texture_id(&cap_xcomp->window_texture));
cap_xcomp->texture_size.x = 0;
cap_xcomp->texture_size.y = 0;
cap_xcomp->egl.glBindTexture(GL_TEXTURE_2D, window_texture_get_opengl_texture_id(&cap_xcomp->window_texture));
cap_xcomp->egl.glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_WIDTH, &cap_xcomp->texture_size.x);
cap_xcomp->egl.glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_HEIGHT, &cap_xcomp->texture_size.y);
cap_xcomp->egl.glBindTexture(GL_TEXTURE_2D, 0);
@ -182,16 +206,21 @@ static int gsr_capture_xcomposite_cuda_start(gsr_capture *cap, AVCodecContext *v
cap_xcomp->texture_size.x = max_int(2, cap_xcomp->texture_size.x & ~1);
cap_xcomp->texture_size.y = max_int(2, cap_xcomp->texture_size.y & ~1);
cap_xcomp->target_texture_id = gl_create_texture(cap_xcomp, cap_xcomp->texture_size.x, cap_xcomp->texture_size.y);
video_codec_context->width = cap_xcomp->texture_size.x;
video_codec_context->height = cap_xcomp->texture_size.y;
if(cap_xcomp->params.region_size.x > 0 && cap_xcomp->params.region_size.y) {
video_codec_context->width = cap_xcomp->params.region_size.x;
video_codec_context->height = cap_xcomp->params.region_size.y;
}
cap_xcomp->target_texture_id = gl_create_texture(cap_xcomp, video_codec_context->width, video_codec_context->height);
if(cap_xcomp->target_texture_id == 0) {
fprintf(stderr, "gsr error: gsr_capture_xcomposite_cuda_start: failed to create opengl texture\n");
gsr_capture_xcomposite_cuda_stop(cap, video_codec_context);
return -1;
}
video_codec_context->width = cap_xcomp->texture_size.x;
video_codec_context->height = cap_xcomp->texture_size.y;
if(!gsr_cuda_load(&cap_xcomp->cuda)) {
gsr_capture_xcomposite_cuda_stop(cap, video_codec_context);
return -1;
@ -221,13 +250,11 @@ static void gsr_capture_xcomposite_cuda_stop(gsr_capture *cap, AVCodecContext *v
cap_xcomp->target_texture_id = 0;
}
if(cap_xcomp->composite_window) {
XCompositeUnredirectWindow(cap_xcomp->dpy, cap_xcomp->composite_window, CompositeRedirectAutomatic);
cap_xcomp->composite_window = None;
}
if(video_codec_context->hw_device_ctx)
av_buffer_unref(&video_codec_context->hw_device_ctx);
av_buffer_unref(&video_codec_context->hw_frames_ctx);
// Not needed because the above call to unref device ctx also frees this?
//if(video_codec_context->hw_frames_ctx)
// av_buffer_unref(&video_codec_context->hw_frames_ctx);
if(cap_xcomp->cuda.cu_ctx) {
CUcontext old_ctx;
@ -241,7 +268,6 @@ static void gsr_capture_xcomposite_cuda_stop(gsr_capture *cap, AVCodecContext *v
gsr_egl_unload(&cap_xcomp->egl);
if(cap_xcomp->dpy) {
// TODO: Why is this crashing?
XCloseDisplay(cap_xcomp->dpy);
cap_xcomp->dpy = NULL;
}
@ -268,22 +294,18 @@ static void gsr_capture_xcomposite_cuda_tick(gsr_capture *cap, AVCodecContext *v
cap_xcomp->cuda.cuCtxPopCurrent_v2(&old_ctx);
}
if(XCheckTypedWindowEvent(cap_xcomp->dpy, cap_xcomp->params.window, DestroyNotify, &cap_xcomp->xev)) {
if(!cap_xcomp->params.follow_focused && XCheckTypedWindowEvent(cap_xcomp->dpy, cap_xcomp->window, DestroyNotify, &cap_xcomp->xev)) {
cap_xcomp->should_stop = true;
cap_xcomp->stop_is_error = false;
}
if(XCheckTypedWindowEvent(cap_xcomp->dpy, cap_xcomp->params.window, Expose, &cap_xcomp->xev) && cap_xcomp->xev.xexpose.count == 0) {
if(XCheckTypedWindowEvent(cap_xcomp->dpy, cap_xcomp->window, Expose, &cap_xcomp->xev) && cap_xcomp->xev.xexpose.count == 0) {
cap_xcomp->window_resize_timer = clock_get_monotonic_seconds();
cap_xcomp->window_resized = true;
}
if(XCheckTypedWindowEvent(cap_xcomp->dpy, cap_xcomp->params.window, ConfigureNotify, &cap_xcomp->xev) && cap_xcomp->xev.xconfigure.window == cap_xcomp->params.window) {
while(XCheckTypedWindowEvent(cap_xcomp->dpy, cap_xcomp->params.window, ConfigureNotify, &cap_xcomp->xev)) {}
Window c;
XTranslateCoordinates(cap_xcomp->dpy, cap_xcomp->params.window, DefaultRootWindow(cap_xcomp->dpy), 0, 0, &cap_xcomp->xev.xconfigure.x, &cap_xcomp->xev.xconfigure.y, &c);
cap_xcomp->window_pos.x = cap_xcomp->xev.xconfigure.x;
cap_xcomp->window_pos.y = cap_xcomp->xev.xconfigure.y;
if(XCheckTypedWindowEvent(cap_xcomp->dpy, cap_xcomp->window, ConfigureNotify, &cap_xcomp->xev) && cap_xcomp->xev.xconfigure.window == cap_xcomp->window) {
while(XCheckTypedWindowEvent(cap_xcomp->dpy, cap_xcomp->window, ConfigureNotify, &cap_xcomp->xev)) {}
/* Window resize */
if(cap_xcomp->xev.xconfigure.width != cap_xcomp->window_size.x || cap_xcomp->xev.xconfigure.height != cap_xcomp->window_size.y) {
@ -294,10 +316,31 @@ static void gsr_capture_xcomposite_cuda_tick(gsr_capture *cap, AVCodecContext *v
}
}
if(cap_xcomp->params.follow_focused && XCheckTypedWindowEvent(cap_xcomp->dpy, DefaultRootWindow(cap_xcomp->dpy), PropertyNotify, &cap_xcomp->xev) && cap_xcomp->xev.xproperty.atom == cap_xcomp->net_active_window_atom) {
Window focused_window = get_focused_window(cap_xcomp->dpy, cap_xcomp->net_active_window_atom);
if(focused_window != cap_xcomp->window) {
XSelectInput(cap_xcomp->dpy, cap_xcomp->window, 0);
cap_xcomp->window = focused_window;
XSelectInput(cap_xcomp->dpy, cap_xcomp->window, StructureNotifyMask | ExposureMask);
XWindowAttributes attr;
attr.width = 0;
attr.height = 0;
if(!XGetWindowAttributes(cap_xcomp->dpy, cap_xcomp->window, &attr))
fprintf(stderr, "gsr error: gsr_capture_xcomposite_cuda_start failed: invalid window id: %lu\n", cap_xcomp->window);
cap_xcomp->window_size.x = max_int(attr.width, 0);
cap_xcomp->window_size.y = max_int(attr.height, 0);
cap_xcomp->window_resized = true;
window_texture_deinit(&cap_xcomp->window_texture);
window_texture_init(&cap_xcomp->window_texture, cap_xcomp->dpy, cap_xcomp->window, &cap_xcomp->egl); // TODO: Do not do the below window_texture_on_resize after this
}
}
const double window_resize_timeout = 1.0; // 1 second
if(cap_xcomp->window_resized && clock_get_monotonic_seconds() - cap_xcomp->window_resize_timer >= window_resize_timeout) {
cap_xcomp->window_resized = false;
fprintf(stderr, "Resize window!\n");
if(window_texture_on_resize(&cap_xcomp->window_texture) != 0) {
fprintf(stderr, "gsr error: gsr_capture_xcomposite_cuda_tick: window_texture_on_resize failed\n");
cap_xcomp->should_stop = true;
@ -305,9 +348,10 @@ static void gsr_capture_xcomposite_cuda_tick(gsr_capture *cap, AVCodecContext *v
return;
}
cap_xcomp->egl.glBindTexture(GL_TEXTURE_2D, window_texture_get_opengl_texture_id(&cap_xcomp->window_texture));
cap_xcomp->texture_size.x = 0;
cap_xcomp->texture_size.y = 0;
cap_xcomp->egl.glBindTexture(GL_TEXTURE_2D, window_texture_get_opengl_texture_id(&cap_xcomp->window_texture));
cap_xcomp->egl.glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_WIDTH, &cap_xcomp->texture_size.x);
cap_xcomp->egl.glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_HEIGHT, &cap_xcomp->texture_size.y);
cap_xcomp->egl.glBindTexture(GL_TEXTURE_2D, 0);
@ -315,37 +359,18 @@ static void gsr_capture_xcomposite_cuda_tick(gsr_capture *cap, AVCodecContext *v
cap_xcomp->texture_size.x = min_int(video_codec_context->width, max_int(2, cap_xcomp->texture_size.x & ~1));
cap_xcomp->texture_size.y = min_int(video_codec_context->height, max_int(2, cap_xcomp->texture_size.y & ~1));
if(!cap_xcomp->params.follow_focused) {
cap_xcomp->egl.glBindTexture(GL_TEXTURE_2D, cap_xcomp->target_texture_id);
cap_xcomp->egl.glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, cap_xcomp->texture_size.x, cap_xcomp->texture_size.y, 0, GL_RGB, GL_UNSIGNED_BYTE, NULL);
cap_xcomp->egl.glBindTexture(GL_TEXTURE_2D, 0);
CUcontext old_ctx;
CUresult res = cap_xcomp->cuda.cuCtxPushCurrent_v2(cap_xcomp->cuda.cu_ctx);
cap_xcomp->cuda.cuGraphicsUnmapResources(1, &cap_xcomp->cuda_graphics_resource, 0);
cap_xcomp->cuda.cuGraphicsUnregisterResource(cap_xcomp->cuda_graphics_resource);
res = cap_xcomp->cuda.cuGraphicsGLRegisterImage(&cap_xcomp->cuda_graphics_resource, cap_xcomp->target_texture_id, GL_TEXTURE_2D, CU_GRAPHICS_REGISTER_FLAGS_READ_ONLY);
if (res != CUDA_SUCCESS) {
const char *err_str = "unknown";
cap_xcomp->cuda.cuGetErrorString(res, &err_str);
fprintf(stderr, "gsr error: gsr_capture_xcomposite_cuda_tick: cuGraphicsGLRegisterImage failed, error %s, texture id: %u\n", err_str, cap_xcomp->target_texture_id);
cap_xcomp->should_stop = true;
cap_xcomp->stop_is_error = true;
res = cap_xcomp->cuda.cuCtxPopCurrent_v2(&old_ctx);
return;
}
res = cap_xcomp->cuda.cuGraphicsResourceSetMapFlags(cap_xcomp->cuda_graphics_resource, CU_GRAPHICS_MAP_RESOURCE_FLAGS_READ_ONLY);
res = cap_xcomp->cuda.cuGraphicsMapResources(1, &cap_xcomp->cuda_graphics_resource, 0);
res = cap_xcomp->cuda.cuGraphicsSubResourceGetMappedArray(&cap_xcomp->mapped_array, cap_xcomp->cuda_graphics_resource, 0, 0);
av_frame_free(frame);
*frame = av_frame_alloc();
if(!frame) {
fprintf(stderr, "gsr error: gsr_capture_xcomposite_cuda_tick: failed to allocate frame\n");
cap_xcomp->should_stop = true;
cap_xcomp->stop_is_error = true;
res = cap_xcomp->cuda.cuCtxPopCurrent_v2(&old_ctx);
return;
}
(*frame)->format = video_codec_context->pix_fmt;
@ -357,14 +382,12 @@ static void gsr_capture_xcomposite_cuda_tick(gsr_capture *cap, AVCodecContext *v
fprintf(stderr, "gsr error: gsr_capture_xcomposite_cuda_tick: av_hwframe_get_buffer failed\n");
cap_xcomp->should_stop = true;
cap_xcomp->stop_is_error = true;
res = cap_xcomp->cuda.cuCtxPopCurrent_v2(&old_ctx);
return;
}
// Make it completely black to clear unused parts
// TODO: cuMemsetD32?
res = cap_xcomp->cuda.cuMemsetD8_v2((CUdeviceptr)(*frame)->data[0], 0, (*frame)->width * (*frame)->height * 4);
res = cap_xcomp->cuda.cuCtxPopCurrent_v2(&old_ctx);
// Clear texture with black background because the source texture (window_texture_get_opengl_texture_id(&cap_xcomp->window_texture))
// might be smaller than cap_xcomp->target_texture_id
cap_xcomp->egl.glClearTexImage(cap_xcomp->target_texture_id, 0, GL_RGB, GL_UNSIGNED_BYTE, NULL);
}
}
@ -384,11 +407,11 @@ static bool gsr_capture_xcomposite_cuda_should_stop(gsr_capture *cap, bool *err)
static int gsr_capture_xcomposite_cuda_capture(gsr_capture *cap, AVFrame *frame) {
gsr_capture_xcomposite_cuda *cap_xcomp = cap->priv;
// TODO: Use a framebuffer instead. glCopyImageSubData requires opengl 4.2
vec2i source_pos = { 0, 0 };
vec2i source_size = cap_xcomp->texture_size;
// Requires opengl 4.2... TODO: Replace with earlier opengl if opengl < 4.2.
if(cap_xcomp->window_texture.texture_id != 0) {
/* TODO: Remove this copy, which is only possible by using nvenc directly and encoding window_pixmap.target_texture_id */
cap_xcomp->egl.glCopyImageSubData(
window_texture_get_opengl_texture_id(&cap_xcomp->window_texture), GL_TEXTURE_2D, 0, source_pos.x, source_pos.y, 0,
cap_xcomp->target_texture_id, GL_TEXTURE_2D, 0, 0, 0, 0,
@ -401,8 +424,8 @@ static int gsr_capture_xcomposite_cuda_capture(gsr_capture *cap, AVFrame *frame)
fprintf(stderr, "Error: glCopyImageSubData failed, gl error: %d\n", err);
}
}
}
cap_xcomp->egl.eglSwapBuffers(cap_xcomp->egl.egl_display, cap_xcomp->egl.egl_surface);
// TODO: Remove this copy, which is only possible by using nvenc directly and encoding window_pixmap.target_texture_id
frame->linesize[0] = frame->width * 4;
@ -426,8 +449,8 @@ static int gsr_capture_xcomposite_cuda_capture(gsr_capture *cap, AVFrame *frame)
}
static void gsr_capture_xcomposite_cuda_destroy(gsr_capture *cap, AVCodecContext *video_codec_context) {
gsr_capture_xcomposite_cuda_stop(cap, video_codec_context);
if(cap->priv) {
gsr_capture_xcomposite_cuda_stop(cap, video_codec_context);
free(cap->priv);
cap->priv = NULL;
}

View File

@ -121,8 +121,8 @@ static unsigned int gl_create_texture(gsr_capture_xcomposite_drm *cap_xcomp, int
cap_xcomp->egl.glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
cap_xcomp->egl.glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
cap_xcomp->egl.glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
cap_xcomp->egl.glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
cap_xcomp->egl.glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
cap_xcomp->egl.glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
cap_xcomp->egl.glBindTexture(GL_TEXTURE_2D, 0);
return texture_id;
@ -403,6 +403,18 @@ static int gsr_capture_xcomposite_drm_start(gsr_capture *cap, AVCodecContext *vi
return -1;
}
if(!cap_xcomp->egl.eglExportDMABUFImageQueryMESA) {
fprintf(stderr, "gsr error: gsr_capture_xcomposite_start: could not find eglExportDMABUFImageQueryMESA\n");
gsr_egl_unload(&cap_xcomp->egl);
return -1;
}
if(!cap_xcomp->egl.eglExportDMABUFImageMESA) {
fprintf(stderr, "gsr error: gsr_capture_xcomposite_start: could not find eglExportDMABUFImageMESA\n");
gsr_egl_unload(&cap_xcomp->egl);
return -1;
}
/* Disable vsync */
cap_xcomp->egl.eglSwapInterval(cap_xcomp->egl.egl_display, 0);
#if 0
@ -589,6 +601,8 @@ static void free_desc(void *opaque, uint8_t *data) {
static void gsr_capture_xcomposite_drm_tick(gsr_capture *cap, AVCodecContext *video_codec_context, AVFrame **frame) {
gsr_capture_xcomposite_drm *cap_xcomp = cap->priv;
cap_xcomp->egl.glClear(GL_COLOR_BUFFER_BIT);
if(!cap_xcomp->created_hw_frame) {
cap_xcomp->created_hw_frame = true;
@ -702,9 +716,11 @@ static void gsr_capture_xcomposite_drm_tick(gsr_capture *cap, AVCodecContext *vi
if(res < 0) {
fprintf(stderr, "av_hwframe_map failed: %d\n", res);
}
}
cap_xcomp->egl.glClear(GL_COLOR_BUFFER_BIT);
// Clear texture with black background because the source texture (window_texture_get_opengl_texture_id(&cap_xcomp->window_texture))
// might be smaller than cap_xcomp->target_texture_id
cap_xcomp->egl.glClearTexImage(cap_xcomp->target_texture_id, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL);
}
}
static bool gsr_capture_xcomposite_drm_should_stop(gsr_capture *cap, bool *err) {
@ -764,7 +780,7 @@ static int gsr_capture_xcomposite_drm_capture(gsr_capture *cap, AVFrame *frame)
vec2i source_size = cap_xcomp->texture_size;
#if 1
// Requires opengl 4.2... TODO: Replace with earlier opengl if opengl < 4.2.
/* TODO: Remove this copy, which is only possible by using nvenc directly and encoding window_pixmap.target_texture_id */
cap_xcomp->egl.glCopyImageSubData(
window_texture_get_opengl_texture_id(&cap_xcomp->window_texture), GL_TEXTURE_2D, 0, 0, 0, 0,
cap_xcomp->target_texture_id, GL_TEXTURE_2D, 0, 0, 0, 0,
@ -808,6 +824,7 @@ static int gsr_capture_xcomposite_drm_capture(gsr_capture *cap, AVFrame *frame)
}
static void gsr_capture_xcomposite_drm_destroy(gsr_capture *cap, AVCodecContext *video_codec_context) {
(void)video_codec_context;
if(cap->priv) {
free(cap->priv);
cap->priv = NULL;

110
src/egl.c
View File

@ -3,11 +3,12 @@
#include <string.h>
static bool gsr_egl_create_window(gsr_egl *self) {
EGLDisplay egl_display = NULL;
EGLConfig ecfg;
int32_t num_config;
EGLSurface egl_surface;
EGLContext egl_context;
int32_t num_config = 0;
EGLDisplay egl_display = NULL;
EGLSurface egl_surface = NULL;
EGLContext egl_context = NULL;
Window window = None;
int32_t attr[] = {
EGL_BUFFER_SIZE, 24,
@ -21,8 +22,7 @@ static bool gsr_egl_create_window(gsr_egl *self) {
EGL_NONE
};
// TODO: Is there a way to remove the need to create a window?
Window window = XCreateWindow(self->dpy, DefaultRootWindow(self->dpy), 0, 0, 1, 1, 0, CopyFromParent, InputOutput, CopyFromParent, 0, NULL);
window = XCreateWindow(self->dpy, DefaultRootWindow(self->dpy), 0, 0, 1, 1, 0, CopyFromParent, InputOutput, CopyFromParent, 0, NULL);
if(!window) {
fprintf(stderr, "gsr error: gsr_gl_create_window failed: failed to create gl window\n");
@ -32,40 +32,32 @@ static bool gsr_egl_create_window(gsr_egl *self) {
egl_display = self->eglGetDisplay(self->dpy);
if(!egl_display) {
fprintf(stderr, "gsr error: gsr_egl_create_window failed: eglGetDisplay failed\n");
return false;
goto fail;
}
if(!self->eglInitialize(egl_display, NULL, NULL)) {
fprintf(stderr, "gsr error: gsr_egl_create_window failed: eglInitialize failed\n");
return false;
goto fail;
}
// TODO: Cleanup ecfg?
if (!self->eglChooseConfig( egl_display, attr, &ecfg, 1, &num_config ) ) {
//cerr << "Failed to choose config (eglError: " << eglGetError() << ")" << endl;
return false;
if(!self->eglChooseConfig(egl_display, attr, &ecfg, 1, &num_config) || num_config != 1) {
fprintf(stderr, "gsr error: gsr_egl_create_window failed: failed to find a matching config\n");
goto fail;
}
if ( num_config != 1 ) {
//cerr << "Didn't get exactly one config, but " << num_config << endl;
return false;
egl_surface = self->eglCreateWindowSurface(egl_display, ecfg, (EGLNativeWindowType)window, NULL);
if(!egl_surface) {
fprintf(stderr, "gsr error: gsr_egl_create_window failed: failed to create window surface\n");
goto fail;
}
egl_surface = self->eglCreateWindowSurface ( egl_display, ecfg, (EGLNativeWindowType)window, NULL );
if ( !egl_surface ) {
//cerr << "Unable to create EGL surface (eglError: " << eglGetError() << ")" << endl;
return false;
egl_context = self->eglCreateContext(egl_display, ecfg, NULL, ctxattr);
if(!egl_context) {
fprintf(stderr, "gsr error: gsr_egl_create_window failed: failed to create egl context\n");
goto fail;
}
//// egl-contexts collect all state descriptions needed required for operation
egl_context = self->eglCreateContext ( egl_display, ecfg, NULL, ctxattr );
if ( !egl_context ) {
//cerr << "Unable to create EGL context (eglError: " << eglGetError() << ")" << endl;
return false;
}
//// associate the egl-context with the egl-surface
self->eglMakeCurrent( egl_display, egl_surface, egl_surface, egl_context );
self->eglMakeCurrent(egl_display, egl_surface, egl_surface, egl_context);
self->egl_display = egl_display;
self->egl_surface = egl_surface;
@ -74,31 +66,31 @@ static bool gsr_egl_create_window(gsr_egl *self) {
return true;
fail:
// TODO:
/*
if(egl_context)
self->eglDestroyContext(egl_display, egl_context);
if(egl_surface)
self->eglDestroySurface(egl_display, egl_surface);
if(egl_display)
self->eglTerminate(egl_display);
if(window)
XDestroyWindow(self->dpy, window);
if(colormap)
XFreeColormap(self->dpy, colormap);
if(gl_context)
self->glXDestroyContext(self->dpy, gl_context);
if(visual_info)
XFree(visual_info);
XFree(fbconfigs);
*/
return False;
return false;
}
static bool gsr_egl_load_egl(gsr_egl *self, void *library) {
dlsym_assign required_dlsym[] = {
{ (void**)&self->eglGetDisplay, "eglGetDisplay" },
{ (void**)&self->eglInitialize, "eglInitialize" },
{ (void**)&self->eglTerminate, "eglTerminate" },
{ (void**)&self->eglChooseConfig, "eglChooseConfig" },
{ (void**)&self->eglCreateWindowSurface, "eglCreateWindowSurface" },
{ (void**)&self->eglCreateContext, "eglCreateContext" },
{ (void**)&self->eglMakeCurrent, "eglMakeCurrent" },
{ (void**)&self->eglCreatePixmapSurface, "eglCreatePixmapSurface" },
{ (void**)&self->eglCreateImage, "eglCreateImage" }, // TODO: eglCreateImageKHR
{ (void**)&self->eglCreateImage, "eglCreateImage" }, /* TODO: use eglCreateImageKHR instead? */
{ (void**)&self->eglDestroyContext, "eglDestroyContext" },
{ (void**)&self->eglDestroySurface, "eglDestroySurface" },
{ (void**)&self->eglDestroyImage, "eglDestroyImage" },
{ (void**)&self->eglBindTexImage, "eglBindTexImage" },
{ (void**)&self->eglSwapInterval, "eglSwapInterval" },
{ (void**)&self->eglSwapBuffers, "eglSwapBuffers" },
@ -115,23 +107,13 @@ static bool gsr_egl_load_egl(gsr_egl *self, void *library) {
return true;
}
static bool gsr_egl_mesa_load_egl(gsr_egl *self) {
static bool gsr_egl_proc_load_egl(gsr_egl *self) {
self->eglExportDMABUFImageQueryMESA = self->eglGetProcAddress("eglExportDMABUFImageQueryMESA");
self->eglExportDMABUFImageMESA = self->eglGetProcAddress("eglExportDMABUFImageMESA");
self->glEGLImageTargetTexture2DOES = self->eglGetProcAddress("glEGLImageTargetTexture2DOES");
if(!self->eglExportDMABUFImageQueryMESA) {
fprintf(stderr, "could not find eglExportDMABUFImageQueryMESA\n");
return false;
}
if(!self->eglExportDMABUFImageMESA) {
fprintf(stderr, "could not find eglExportDMABUFImageMESA\n");
return false;
}
if(!self->glEGLImageTargetTexture2DOES) {
fprintf(stderr, "could not find glEGLImageTargetTexture2DOES\n");
fprintf(stderr, "gsr error: gsr_egl_load failed: could not find glEGLImageTargetTexture2DOES\n");
return false;
}
@ -151,6 +133,7 @@ static bool gsr_egl_load_gl(gsr_egl *self, void *library) {
{ (void**)&self->glGetTexLevelParameteriv, "glGetTexLevelParameteriv" },
{ (void**)&self->glTexImage2D, "glTexImage2D" },
{ (void**)&self->glCopyImageSubData, "glCopyImageSubData" },
{ (void**)&self->glClearTexImage, "glClearTexImage" },
{ (void**)&self->glGenFramebuffers, "glGenFramebuffers" },
{ (void**)&self->glBindFramebuffer, "glBindFramebuffer" },
{ (void**)&self->glViewport, "glViewport" },
@ -228,7 +211,7 @@ bool gsr_egl_load(gsr_egl *self, Display *dpy) {
return false;
}
if(!gsr_egl_mesa_load_egl(self)) {
if(!gsr_egl_proc_load_egl(self)) {
dlclose(egl_lib);
dlclose(gl_lib);
memset(self, 0, sizeof(gsr_egl));
@ -247,14 +230,21 @@ bool gsr_egl_load(gsr_egl *self, Display *dpy) {
return true;
}
bool gsr_egl_make_context_current(gsr_egl *self) {
// TODO:
return true;
//return self->glXMakeContextCurrent(self->dpy, self->window, self->window, self->gl_context);
}
void gsr_egl_unload(gsr_egl *self) {
// TODO: Cleanup of egl resources
if(self->egl_context) {
self->eglDestroyContext(self->egl_display, self->egl_context);
self->egl_context = NULL;
}
if(self->egl_surface) {
self->eglDestroySurface(self->egl_display, self->egl_surface);
self->egl_surface = NULL;
}
if(self->egl_display) {
self->eglTerminate(self->egl_display);
self->egl_display = NULL;
}
if(self->window) {
XDestroyWindow(self->dpy, self->window);

View File

@ -38,6 +38,12 @@ extern "C" {
#include <deque>
#include <future>
typedef enum {
GPU_VENDOR_AMD,
GPU_VENDOR_INTEL,
GPU_VENDOR_NVIDIA
} gpu_vendor;
// TODO: Remove LIBAVUTIL_VERSION_MAJOR checks in the future when ubuntu, pop os LTS etc update ffmpeg to >= 5.0
static const int VIDEO_STREAM_INDEX = 0;
@ -322,70 +328,51 @@ static AVCodecContext *create_video_codec_context(AVPixelFormat pix_fmt,
return codec_context;
}
#if 0
static const AVCodec* find_h264_encoder() {
const AVCodec *codec = avcodec_find_encoder_by_name("h264_vaapi");
if(!codec)
codec = avcodec_find_encoder_by_name("vaapi_h264");
return codec;
}
static const AVCodec* find_h265_encoder() {
const AVCodec *codec = avcodec_find_encoder_by_name("hevc_vaapi");
if(!codec)
codec = avcodec_find_encoder_by_name("vaapi_hevc");
return codec;
}
#else
static const AVCodec* find_h264_encoder() {
const AVCodec *codec = avcodec_find_encoder_by_name("h264_nvenc");
if(!codec)
codec = avcodec_find_encoder_by_name("nvenc_h264");
static bool checked = false;
if(!checked) {
checked = true;
static bool check_if_codec_valid_for_hardware(const AVCodec *codec) {
bool success = false;
// Do not use AV_PIX_FMT_CUDA because we dont want to do full check with hardware context
AVCodecContext *codec_context = create_video_codec_context(AV_PIX_FMT_YUV420P, VideoQuality::VERY_HIGH, 60, codec, false);
codec_context->width = 1920;
codec_context->height = 1080;
if(codec_context) {
if (avcodec_open2(codec_context, codec_context->codec, NULL) < 0) {
avcodec_free_context(&codec_context);
return nullptr;
}
success = avcodec_open2(codec_context, codec_context->codec, NULL) == 0;
avcodec_free_context(&codec_context);
}
}
return codec;
return success;
}
static const AVCodec* find_h265_encoder() {
const AVCodec *codec = avcodec_find_encoder_by_name("hevc_nvenc");
static const AVCodec* find_h264_encoder(gpu_vendor vendor) {
const AVCodec *codec = avcodec_find_encoder_by_name(vendor == GPU_VENDOR_NVIDIA ? "h264_nvenc" : "h264_vaapi");
if(!codec)
codec = avcodec_find_encoder_by_name("nvenc_hevc");
codec = avcodec_find_encoder_by_name(vendor == GPU_VENDOR_NVIDIA ? "nvenc_h264" : "vaapi_h264");
static bool checked = false;
static bool checked_success = true;
if(!checked) {
checked = true;
if(!check_if_codec_valid_for_hardware(codec))
checked_success = false;
}
return checked_success ? codec : nullptr;
}
static const AVCodec* find_h265_encoder(gpu_vendor vendor) {
const AVCodec *codec = avcodec_find_encoder_by_name(vendor == GPU_VENDOR_NVIDIA ? "hevc_nvenc" : "hevc_vaapi");
if(!codec)
codec = avcodec_find_encoder_by_name(vendor == GPU_VENDOR_NVIDIA ? "nvenc_hevc" : "vaapi_hevc");
if(!codec)
return nullptr;
static bool checked = false;
static bool checked_success = true;
if(!checked) {
checked = true;
// Do not use AV_PIX_FMT_CUDA because we dont want to do full check with hardware context
AVCodecContext *codec_context = create_video_codec_context(AV_PIX_FMT_YUV420P, VideoQuality::VERY_HIGH, 60, codec, false);
codec_context->width = 1920;
codec_context->height = 1080;
if(codec_context) {
if (avcodec_open2(codec_context, codec_context->codec, NULL) < 0) {
avcodec_free_context(&codec_context);
return nullptr;
if(!check_if_codec_valid_for_hardware(codec))
checked_success = false;
}
avcodec_free_context(&codec_context);
}
}
return codec;
return checked_success ? codec : nullptr;
}
#endif
static AVFrame* open_audio(AVCodecContext *audio_codec_context) {
int ret;
@ -499,11 +486,12 @@ static void open_video(AVCodecContext *codec_context, VideoQuality video_quality
}
static void usage() {
fprintf(stderr, "usage: gpu-screen-recorder -w <window_id> [-c <container_format>] -f <fps> [-a <audio_input>...] [-q <quality>] [-r <replay_buffer_size_sec>] [-o <output_file>]\n");
fprintf(stderr, "usage: gpu-screen-recorder -w <window_id|monitor|focused> [-c <container_format>] [-s WxH] -f <fps> [-a <audio_input>...] [-q <quality>] [-r <replay_buffer_size_sec>] [-o <output_file>]\n");
fprintf(stderr, "OPTIONS:\n");
fprintf(stderr, " -w Window to record or a display, \"screen\" or \"screen-direct\". The display is the display name in xrandr and if \"screen\" or \"screen-direct\" is selected then all displays are recorded and they are recorded in h265 (aka hevc)."
fprintf(stderr, " -w Window to record, a display, \"screen\", \"screen-direct\" or \"focused\". The display is the display (monitor) name in xrandr and if \"screen\" or \"screen-direct\" is selected then all displays are recorded. If this is \"focused\" then the currently focused window is recorded. When recording the focused window then the -s option has to be used as well.\n"
"\"screen-direct\" skips one texture copy for fullscreen applications so it may lead to better performance and it works with VRR monitors when recording fullscreen application but may break some applications, such as mpv in fullscreen mode. Recording a display requires a gpu with NvFBC support.\n");
fprintf(stderr, " -c Container format for output file, for example mp4, or flv. Only required if no output file is specified or if recording in replay buffer mode. If an output file is specified and -c is not used then the container format is determined from the output filename extension.\n");
fprintf(stderr, " -s The size (area) to record at in the format WxH, for example 1920x1080. This option is only supported (and required) when -w is \"focused\".\n");
fprintf(stderr, " -f Framerate to record at.\n");
fprintf(stderr, " -a Audio device to record from (pulse audio device). Can be specified multiple times. Each time this is specified a new audio track is added for the specified audio device. A name can be given to the audio input device by prefixing the audio input with <name>/, for example \"dummy/alsa_output.pci-0000_00_1b.0.analog-stereo.monitor\". Optional, no audio track is added by default.\n");
fprintf(stderr, " -q Video quality. Should be either 'medium', 'high', 'very_high' or 'ultra'. 'high' is the recommended option when live streaming or when you have a slower harddrive. Optional, set to 'very_high' be default.\n");
@ -732,12 +720,6 @@ static bool is_livestream_path(const char *str) {
return false;
}
typedef enum {
GPU_VENDOR_AMD,
GPU_VENDOR_INTEL,
GPU_VENDOR_NVIDIA
} gpu_vendor;
typedef struct {
gpu_vendor vendor;
int gpu_version; /* 0 if unknown */
@ -792,10 +774,9 @@ int main(int argc, char **argv) {
std::map<std::string, Arg> args = {
{ "-w", Arg { {}, false, false } },
//{ "-s", Arg { nullptr, true } },
{ "-c", Arg { {}, true, false } },
{ "-f", Arg { {}, false, false } },
//{ "-s", Arg { {}, true, false } },
{ "-s", Arg { {}, true, false } },
{ "-a", Arg { {}, true, true } },
{ "-q", Arg { {}, true, false } },
{ "-o", Arg { {}, true, false } },
@ -927,12 +908,73 @@ int main(int argc, char **argv) {
very_old_gpu = true;
}
// TODO: Remove once gpu screen recorder supports amd and intel properly
if(gpu_inf.vendor != GPU_VENDOR_NVIDIA) {
fprintf(stderr, "Error: gpu-screen-recorder does currently only support nvidia gpus\n");
return 2;
}
const char *screen_region = args["-s"].value();
const char *window_str = args["-w"].value();
if(screen_region && strcmp(window_str, "focused") != 0) {
fprintf(stderr, "Error: option -s is only available when using -w focused\n");
usage();
}
gsr_capture *capture = nullptr;
if(contains_non_hex_number(window_str)) {
if(strcmp(window_str, "focused") == 0) {
if(!screen_region) {
fprintf(stderr, "Error: option -s is required when using -w focused\n");
usage();
}
vec2i region_size = { 0, 0 };
if(sscanf(screen_region, "%dx%d", &region_size.x, &region_size.y) != 2) {
fprintf(stderr, "Error: invalid value for option -s '%s', expected a value in format WxH\n", screen_region);
usage();
}
if(region_size.x <= 0 || region_size.y <= 0) {
fprintf(stderr, "Error: invalud value for option -s '%s', expected width and height to be greater than 0\n", screen_region);
usage();
}
switch(gpu_inf.vendor) {
case GPU_VENDOR_AMD: {
gsr_capture_xcomposite_drm_params xcomposite_params;
xcomposite_params.window = 0;
xcomposite_params.follow_focused = true;
xcomposite_params.region_size = region_size;
capture = gsr_capture_xcomposite_drm_create(&xcomposite_params);
if(!capture)
return 1;
break;
}
case GPU_VENDOR_INTEL: {
gsr_capture_xcomposite_drm_params xcomposite_params;
xcomposite_params.window = 0;
xcomposite_params.follow_focused = true;
xcomposite_params.region_size = region_size;
capture = gsr_capture_xcomposite_drm_create(&xcomposite_params);
if(!capture)
return 1;
break;
}
case GPU_VENDOR_NVIDIA: {
gsr_capture_xcomposite_cuda_params xcomposite_params;
xcomposite_params.window = 0;
xcomposite_params.follow_focused = true;
xcomposite_params.region_size = region_size;
capture = gsr_capture_xcomposite_cuda_create(&xcomposite_params);
if(!capture)
return 1;
break;
}
}
} else if(contains_non_hex_number(window_str)) {
if(gpu_inf.vendor != GPU_VENDOR_NVIDIA) {
fprintf(stderr, "Error: recording a monitor is only supported on NVIDIA right now\n");
fprintf(stderr, "Error: recording a monitor is only supported on NVIDIA right now. Record \"focused\" instead for convenient fullscreen window recording\n");
return 2;
}
@ -978,6 +1020,8 @@ int main(int argc, char **argv) {
case GPU_VENDOR_AMD: {
gsr_capture_xcomposite_drm_params xcomposite_params;
xcomposite_params.window = src_window_id;
xcomposite_params.follow_focused = false;
xcomposite_params.region_size = { 0, 0 };
capture = gsr_capture_xcomposite_drm_create(&xcomposite_params);
if(!capture)
return 1;
@ -986,6 +1030,8 @@ int main(int argc, char **argv) {
case GPU_VENDOR_INTEL: {
gsr_capture_xcomposite_drm_params xcomposite_params;
xcomposite_params.window = src_window_id;
xcomposite_params.follow_focused = false;
xcomposite_params.region_size = { 0, 0 };
capture = gsr_capture_xcomposite_drm_create(&xcomposite_params);
if(!capture)
return 1;
@ -994,6 +1040,8 @@ int main(int argc, char **argv) {
case GPU_VENDOR_NVIDIA: {
gsr_capture_xcomposite_cuda_params xcomposite_params;
xcomposite_params.window = src_window_id;
xcomposite_params.follow_focused = false;
xcomposite_params.region_size = { 0, 0 };
capture = gsr_capture_xcomposite_cuda_create(&xcomposite_params);
if(!capture)
return 1;
@ -1052,7 +1100,7 @@ int main(int argc, char **argv) {
const double target_fps = 1.0 / (double)fps;
if(strcmp(codec_to_use, "auto") == 0) {
const AVCodec *h265_codec = find_h265_encoder();
const AVCodec *h265_codec = find_h265_encoder(gpu_inf.vendor);
// h265 generally allows recording at a higher resolution than h264 on nvidia cards. On a gtx 1080 4k is the max resolution for h264 but for h265 it's 8k.
// Another important info is that when recording at a higher fps than.. 60? h265 has very bad performance. For example when recording at 144 fps the fps drops to 1
@ -1081,10 +1129,10 @@ int main(int argc, char **argv) {
const AVCodec *video_codec_f = nullptr;
switch(video_codec) {
case VideoCodec::H264:
video_codec_f = find_h264_encoder();
video_codec_f = find_h264_encoder(gpu_inf.vendor);
break;
case VideoCodec::H265:
video_codec_f = find_h265_encoder();
video_codec_f = find_h265_encoder(gpu_inf.vendor);
break;
}
@ -1104,7 +1152,7 @@ int main(int argc, char **argv) {
AVStream *video_stream = nullptr;
std::vector<AudioTrack> audio_tracks;
AVCodecContext *video_codec_context = create_video_codec_context(AV_PIX_FMT_CUDA, quality, fps, video_codec_f, is_livestream);
AVCodecContext *video_codec_context = create_video_codec_context(gpu_inf.vendor == GPU_VENDOR_NVIDIA ? AV_PIX_FMT_CUDA : AV_PIX_FMT_VAAPI, quality, fps, video_codec_f, is_livestream);
if(replay_buffer_size_secs == -1)
video_stream = create_stream(av_format_context, video_codec_context);

View File

@ -1,6 +1,9 @@
#include "../include/window_texture.h"
#include <X11/extensions/Xcomposite.h>
#include <stdio.h>
#define EGL_TRUE 1
#define EGL_IMAGE_PRESERVED_KHR 0x30D2
#define EGL_NATIVE_PIXMAP_KHR 0x30B0
static int x11_supports_composite_named_window_pixmap(Display *display) {
int extension_major;
@ -18,9 +21,6 @@ int window_texture_init(WindowTexture *window_texture, Display *display, Window
window_texture->window = window;
window_texture->pixmap = None;
window_texture->texture_id = 0;
window_texture->target_texture_id = 0;
window_texture->texture_width = 0;
window_texture->texture_height = 0;
window_texture->redirected = 0;
window_texture->egl = egl;
@ -38,11 +38,6 @@ static void window_texture_cleanup(WindowTexture *self, int delete_texture) {
self->texture_id = 0;
}
if(delete_texture && self->target_texture_id) {
self->egl->glDeleteTextures(1, &self->target_texture_id);
self->target_texture_id = 0;
}
if(self->pixmap) {
XFreePixmap(self->display, self->pixmap);
self->pixmap = None;
@ -57,11 +52,6 @@ void window_texture_deinit(WindowTexture *self) {
window_texture_cleanup(self, 1);
}
#define EGL_TRUE 1
#define EGL_IMAGE_PRESERVED_KHR 0x30D2
#define EGL_NATIVE_PIXMAP_KHR 0x30B0
int window_texture_on_resize(WindowTexture *self) {
window_texture_cleanup(self, 0);
@ -84,7 +74,7 @@ int window_texture_on_resize(WindowTexture *self) {
if(self->texture_id == 0) {
self->egl->glGenTextures(1, &texture_id);
if(texture_id == 0) {
result = 4;
result = 3;
goto cleanup;
}
self->egl->glBindTexture(GL_TEXTURE_2D, texture_id);
@ -93,48 +83,39 @@ int window_texture_on_resize(WindowTexture *self) {
texture_id = self->texture_id;
}
image = self->egl->eglCreateImage(self->egl->egl_display, NULL, EGL_NATIVE_PIXMAP_KHR, (EGLClientBuffer)pixmap, pixmap_attrs);
if(!image) {
fprintf(stderr, "eglCreateImage failed\n");
return -1;
}
fprintf(stderr, "gl error: %d\n", self->egl->glGetError());
fprintf(stderr, "image: %p\n", image);
self->egl->glEGLImageTargetTexture2DOES(GL_TEXTURE_2D, image);
if(self->egl->glGetError() != 0) {
fprintf(stderr, "glEGLImageTargetTexture2DOES failed\n");
}
self->egl->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
self->egl->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
self->egl->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
self->egl->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
self->egl->glBindTexture(GL_TEXTURE_2D, 0);
self->pixmap = pixmap;
if(texture_id != 0) {
self->texture_id = texture_id;
self->egl->glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_WIDTH, &self->texture_width);
self->egl->glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_HEIGHT, &self->texture_height);
fprintf(stderr, "texture width: %d, height: %d\n", self->texture_width, self->texture_height);
self->egl->glGenTextures(1, &self->target_texture_id);
self->egl->glBindTexture(GL_TEXTURE_2D, self->target_texture_id);
self->egl->glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, self->texture_width, self->texture_height, 0, GL_RGB, GL_UNSIGNED_BYTE, NULL);
fprintf(stderr, "gl error: %d\n", self->egl->glGetError());
self->egl->glBindTexture(GL_TEXTURE_2D, 0);
image = self->egl->eglCreateImage(self->egl->egl_display, NULL, EGL_NATIVE_PIXMAP_KHR, (EGLClientBuffer)pixmap, pixmap_attrs);
if(!image) {
result = 4;
goto cleanup;
}
// TODO: destroyImage(image)
return 0;
self->egl->glEGLImageTargetTexture2DOES(GL_TEXTURE_2D, image);
if(self->egl->glGetError() != 0) {
result = 5;
goto cleanup;
}
self->pixmap = pixmap;
self->texture_id = texture_id;
cleanup:
if(texture_id != 0) self->egl->glDeleteTextures(1, &texture_id);
if(pixmap) XFreePixmap(self->display, pixmap);
self->egl->glBindTexture(GL_TEXTURE_2D, 0);
if(image)
self->egl->eglDestroyImage(self->egl->egl_display, image);
if(result != 0) {
if(texture_id != 0)
self->egl->glDeleteTextures(1, &texture_id);
if(pixmap)
XFreePixmap(self->display, pixmap);
}
return result;
}