Refactor nvfbc into abstract capture api

This commit is contained in:
dec05eba 2022-10-14 01:16:31 +02:00
parent 9d65552d05
commit 93d46b9767
11 changed files with 525 additions and 306 deletions

View File

@ -28,7 +28,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). 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 # Dependencies
`libgl (libglvnd), ffmpeg, libx11, libxcomposite, libpulse`. You need to additionally have `libcuda.so` installed when you run `gpu-screen-recorder`.\ `libgl (libglvnd), ffmpeg, libx11, libxcomposite, libxrandr, libpulse`. You need to additionally have `libcuda.so` installed when you run `gpu-screen-recorder`.\
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. 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 # How to use
@ -57,7 +57,6 @@ FFMPEG only uses the GPU with CUDA when doing transcoding from an input video to
# TODO # TODO
* Support AMD and Intel, using VAAPI. * Support AMD and Intel, using VAAPI.
libraries at compile-time. libraries at compile-time.
* Clean up the code!
* 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. * 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. * 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 composition issues and (VRR) without having to use NvFBC direct capture.

View File

@ -1,9 +1,11 @@
#!/bin/sh -e #!/bin/sh -e
dependencies="libavcodec libavformat libavutil x11 xcomposite libpulse libswresample" dependencies="libavcodec libavformat libavutil x11 xcomposite xrandr libpulse libswresample"
includes="$(pkg-config --cflags $dependencies)" includes="$(pkg-config --cflags $dependencies)"
libs="$(pkg-config --libs $dependencies) -ldl -pthread -lm" libs="$(pkg-config --libs $dependencies) -ldl -pthread -lm"
gcc -c src/capture/capture.c -O2 -g0 -DNDEBUG $includes
gcc -c src/capture/nvfbc.c -O2 -g0 -DNDEBUG $includes
g++ -c src/sound.cpp -O2 -g0 -DNDEBUG $includes g++ -c src/sound.cpp -O2 -g0 -DNDEBUG $includes
g++ -c src/main.cpp -O2 -g0 -DNDEBUG $includes g++ -c src/main.cpp -O2 -g0 -DNDEBUG $includes
g++ -o gpu-screen-recorder -O2 sound.o main.o -s $libs g++ -o gpu-screen-recorder -O2 capture.o nvfbc.o sound.o main.o -s $libs
echo "Successfully built gpu-screen-recorder" echo "Successfully built gpu-screen-recorder"

View File

@ -1,278 +0,0 @@
#pragma once
#include "../external/NvFBC.h"
#include <dlfcn.h>
#include <string.h>
#include <stdio.h>
#include <string.h>
class NvFBCLibrary {
public:
~NvFBCLibrary() {
if(fbc_handle_created) {
NVFBC_DESTROY_CAPTURE_SESSION_PARAMS destroy_capture_params;
memset(&destroy_capture_params, 0, sizeof(destroy_capture_params));
destroy_capture_params.dwVersion = NVFBC_DESTROY_CAPTURE_SESSION_PARAMS_VER;
nv_fbc_function_list.nvFBCDestroyCaptureSession(nv_fbc_handle, &destroy_capture_params);
NVFBC_DESTROY_HANDLE_PARAMS destroy_params;
memset(&destroy_params, 0, sizeof(destroy_params));
destroy_params.dwVersion = NVFBC_DESTROY_HANDLE_PARAMS_VER;
nv_fbc_function_list.nvFBCDestroyHandle(nv_fbc_handle, &destroy_params);
}
if(library)
dlclose(library);
}
bool load() {
if(library)
return true;
dlerror(); // clear
void *lib = dlopen("libnvidia-fbc.so.1", RTLD_LAZY);
if(!lib) {
fprintf(stderr, "Error: failed to load libnvidia-fbc.so.1, error: %s\n", dlerror());
return false;
}
nv_fbc_create_instance = (PNVFBCCREATEINSTANCE)dlsym(lib, "NvFBCCreateInstance");
if(!nv_fbc_create_instance) {
fprintf(stderr, "Error: unable to resolve symbol 'NvFBCCreateInstance'\n");
dlclose(lib);
return false;
}
memset(&nv_fbc_function_list, 0, sizeof(nv_fbc_function_list));
nv_fbc_function_list.dwVersion = NVFBC_VERSION;
NVFBCSTATUS status = nv_fbc_create_instance(&nv_fbc_function_list);
if(status != NVFBC_SUCCESS) {
fprintf(stderr, "Error: failed to create NvFBC instance (status: %d)\n", status);
dlclose(lib);
return false;
}
library = lib;
return true;
}
// If |display_to_capture| is "screen", then the entire x11 screen is captured (all displays).
bool create(const char *display_to_capture, uint32_t fps, /*out*/ uint32_t *display_width, /*out*/ uint32_t *display_height, uint32_t x = 0, uint32_t y = 0, uint32_t width = 0, uint32_t height = 0, bool direct_capture = false) {
if(!library || !display_to_capture || !display_width || !display_height || fbc_handle_created)
return false;
const bool capture_region = (x > 0 || y > 0 || width > 0 || height > 0);
NVFBCSTATUS status;
NVFBC_TRACKING_TYPE tracking_type;
bool capture_session_created = false;
uint32_t output_id = 0;
fbc_handle_created = false;
NVFBC_CREATE_HANDLE_PARAMS create_params;
memset(&create_params, 0, sizeof(create_params));
create_params.dwVersion = NVFBC_CREATE_HANDLE_PARAMS_VER;
status = nv_fbc_function_list.nvFBCCreateHandle(&nv_fbc_handle, &create_params);
if(status != NVFBC_SUCCESS) {
// Reverse engineering for interoperability
const uint8_t enable_key[] = { 0xac, 0x10, 0xc9, 0x2e, 0xa5, 0xe6, 0x87, 0x4f, 0x8f, 0x4b, 0xf4, 0x61, 0xf8, 0x56, 0x27, 0xe9 };
create_params.privateData = enable_key;
create_params.privateDataSize = 16;
status = nv_fbc_function_list.nvFBCCreateHandle(&nv_fbc_handle, &create_params);
if(status != NVFBC_SUCCESS) {
fprintf(stderr, "Error: %s\n", nv_fbc_function_list.nvFBCGetLastErrorStr(nv_fbc_handle));
return false;
}
}
fbc_handle_created = true;
NVFBC_GET_STATUS_PARAMS status_params;
memset(&status_params, 0, sizeof(status_params));
status_params.dwVersion = NVFBC_GET_STATUS_PARAMS_VER;
status = nv_fbc_function_list.nvFBCGetStatus(nv_fbc_handle, &status_params);
if(status != NVFBC_SUCCESS) {
fprintf(stderr, "Error: %s\n", nv_fbc_function_list.nvFBCGetLastErrorStr(nv_fbc_handle));
goto error_cleanup;
}
if(status_params.bCanCreateNow == NVFBC_FALSE) {
fprintf(stderr, "Error: it's not possible to create a capture session on this system\n");
goto error_cleanup;
}
tracking_type = strcmp(display_to_capture, "screen") == 0 ? NVFBC_TRACKING_SCREEN : NVFBC_TRACKING_OUTPUT;
if(tracking_type == NVFBC_TRACKING_OUTPUT) {
if(!status_params.bXRandRAvailable) {
fprintf(stderr, "Error: the xrandr extension is not available\n");
goto error_cleanup;
}
if(status_params.bInModeset) {
fprintf(stderr, "Error: the x server is in modeset, unable to record\n");
goto error_cleanup;
}
output_id = get_output_id_from_display_name(status_params.outputs, status_params.dwOutputNum, display_to_capture, display_width, display_height);
if(output_id == 0) {
fprintf(stderr, "Error: display '%s' not found\n", display_to_capture);
goto error_cleanup;
}
} else {
*display_width = status_params.screenSize.w;
*display_height = status_params.screenSize.h;
}
NVFBC_CREATE_CAPTURE_SESSION_PARAMS create_capture_params;
memset(&create_capture_params, 0, sizeof(create_capture_params));
create_capture_params.dwVersion = NVFBC_CREATE_CAPTURE_SESSION_PARAMS_VER;
create_capture_params.eCaptureType = NVFBC_CAPTURE_SHARED_CUDA;
create_capture_params.bWithCursor = (!direct_capture || driver_supports_direct_capture_cursor()) ? NVFBC_TRUE : NVFBC_FALSE;
if(capture_region) {
create_capture_params.captureBox = { x, y, width, height };
*display_width = width;
*display_height = height;
}
create_capture_params.eTrackingType = tracking_type;
create_capture_params.dwSamplingRateMs = 1000 / fps;
create_capture_params.bAllowDirectCapture = direct_capture ? NVFBC_TRUE : NVFBC_FALSE;
create_capture_params.bPushModel = direct_capture ? NVFBC_TRUE : NVFBC_FALSE;
if(tracking_type == NVFBC_TRACKING_OUTPUT)
create_capture_params.dwOutputId = output_id;
status = nv_fbc_function_list.nvFBCCreateCaptureSession(nv_fbc_handle, &create_capture_params);
if(status != NVFBC_SUCCESS) {
fprintf(stderr, "Error: %s\n", nv_fbc_function_list.nvFBCGetLastErrorStr(nv_fbc_handle));
goto error_cleanup;
}
capture_session_created = true;
NVFBC_TOCUDA_SETUP_PARAMS setup_params;
memset(&setup_params, 0, sizeof(setup_params));
setup_params.dwVersion = NVFBC_TOCUDA_SETUP_PARAMS_VER;
setup_params.eBufferFormat = NVFBC_BUFFER_FORMAT_BGRA;
status = nv_fbc_function_list.nvFBCToCudaSetUp(nv_fbc_handle, &setup_params);
if(status != NVFBC_SUCCESS) {
fprintf(stderr, "Error: %s\n", nv_fbc_function_list.nvFBCGetLastErrorStr(nv_fbc_handle));
goto error_cleanup;
}
return true;
error_cleanup:
if(fbc_handle_created) {
if(capture_session_created) {
NVFBC_DESTROY_CAPTURE_SESSION_PARAMS destroy_capture_params;
memset(&destroy_capture_params, 0, sizeof(destroy_capture_params));
destroy_capture_params.dwVersion = NVFBC_DESTROY_CAPTURE_SESSION_PARAMS_VER;
nv_fbc_function_list.nvFBCDestroyCaptureSession(nv_fbc_handle, &destroy_capture_params);
}
NVFBC_DESTROY_HANDLE_PARAMS destroy_params;
memset(&destroy_params, 0, sizeof(destroy_params));
destroy_params.dwVersion = NVFBC_DESTROY_HANDLE_PARAMS_VER;
nv_fbc_function_list.nvFBCDestroyHandle(nv_fbc_handle, &destroy_params);
fbc_handle_created = false;
}
output_id = 0;
return false;
}
bool capture(/*out*/ void *cu_device_ptr, uint32_t *byte_size) {
if(!library || !fbc_handle_created || !cu_device_ptr || !byte_size)
return false;
NVFBCSTATUS status;
NVFBC_FRAME_GRAB_INFO frame_info;
memset(&frame_info, 0, sizeof(frame_info));
NVFBC_TOCUDA_GRAB_FRAME_PARAMS grab_params;
memset(&grab_params, 0, sizeof(grab_params));
grab_params.dwVersion = NVFBC_TOCUDA_GRAB_FRAME_PARAMS_VER;
grab_params.dwFlags = NVFBC_TOCUDA_GRAB_FLAGS_NOWAIT | NVFBC_TOCUDA_GRAB_FLAGS_FORCE_REFRESH;
grab_params.pFrameGrabInfo = &frame_info;
grab_params.pCUDADeviceBuffer = cu_device_ptr;
status = nv_fbc_function_list.nvFBCToCudaGrabFrame(nv_fbc_handle, &grab_params);
if(status != NVFBC_SUCCESS) {
fprintf(stderr, "Error: %s\n", nv_fbc_function_list.nvFBCGetLastErrorStr(nv_fbc_handle));
return false;
}
*byte_size = frame_info.dwByteSize;
// TODO: Check bIsNewFrame
// TODO: Check dwWidth and dwHeight and update size in video output in ffmpeg. This can happen when xrandr is used to change monitor resolution
return true;
}
private:
static char to_upper(char c) {
if(c >= 'a' && c <= 'z')
return c - 32;
else
return c;
}
static bool strcase_equals(const char *str1, const char *str2) {
for(;;) {
char c1 = to_upper(*str1);
char c2 = to_upper(*str2);
if(c1 != c2)
return false;
if(c1 == '\0' || c2 == '\0')
return true;
++str1;
++str2;
}
}
// Returns 0 on failure
static uint32_t get_output_id_from_display_name(NVFBC_RANDR_OUTPUT_INFO *outputs, uint32_t num_outputs, const char *display_name, uint32_t *display_width, uint32_t *display_height) {
if(!outputs)
return 0;
for(uint32_t i = 0; i < num_outputs; ++i) {
if(strcase_equals(outputs[i].name, display_name)) {
*display_width = outputs[i].trackedBox.w;
*display_height = outputs[i].trackedBox.h;
return outputs[i].dwId;
}
}
return 0;
}
// TODO: Test with optimus and open kernel modules
static bool driver_supports_direct_capture_cursor() {
FILE *f = fopen("/proc/driver/nvidia/version", "rb");
if(!f)
return false;
char buffer[2048];
size_t bytes_read = fread(buffer, 1, sizeof(buffer) - 1, f);
buffer[bytes_read] = '\0';
bool supports_cursor = false;
const char *p = strstr(buffer, "Kernel Module");
if(p) {
p += 13;
int driver_major_version = 0, driver_minor_version = 0;
if(sscanf(p, "%d.%d", &driver_major_version, &driver_minor_version) == 2) {
if(driver_major_version > 515 || (driver_major_version == 515 && driver_minor_version >= 57))
supports_cursor = true;
}
}
fclose(f);
return supports_cursor;
}
private:
void *library = nullptr;
PNVFBCCREATEINSTANCE nv_fbc_create_instance = nullptr;
NVFBC_API_FUNCTION_LIST nv_fbc_function_list;
NVFBC_SESSION_HANDLE nv_fbc_handle;
bool fbc_handle_created = false;
};

25
include/capture/capture.h Normal file
View File

@ -0,0 +1,25 @@
#ifndef GSR_CAPTURE_CAPTURE_H
#define GSR_CAPTURE_CAPTURE_H
#include <stdbool.h>
typedef struct AVFrame AVFrame;
typedef struct gsr_capture gsr_capture;
struct gsr_capture {
int (*start)(gsr_capture *cap);
void (*stop)(gsr_capture *cap);
int (*capture)(gsr_capture *cap, AVFrame *frame);
void (*destroy)(gsr_capture *cap);
void *priv;
};
int gsr_capture_start(gsr_capture *cap);
void gsr_capture_stop(gsr_capture *cap);
int gsr_capture_capture(gsr_capture *cap, AVFrame *frame);
/* Calls |gsr_capture_stop| as well */
void gsr_capture_destroy(gsr_capture *cap);
#endif /* GSR_CAPTURE_CAPTURE_H */

17
include/capture/nvfbc.h Normal file
View File

@ -0,0 +1,17 @@
#ifndef GSR_CAPTURE_NVFBC_H
#define GSR_CAPTURE_NVFBC_H
#include "capture.h"
#include "../vec2.h"
typedef struct {
const char *display_to_capture; /* if this is "screen", then the entire x11 screen is captured (all displays) */
int fps;
vec2i pos;
vec2i size;
bool direct_capture; /* temporary disabled */
} gsr_capture_nvfbc_params;
gsr_capture* gsr_capture_nvfbc_create(const gsr_capture_nvfbc_params *params);
#endif /* GSR_CAPTURE_NVFBC_H */

8
include/vec2.h Normal file
View File

@ -0,0 +1,8 @@
#ifndef VEC2_H
#define VEC2_H
typedef struct {
int x, y;
} vec2i;
#endif /* VEC2_H */

View File

@ -8,7 +8,7 @@ cd "$script_dir"
set -e set -e
apt-get -y install build-essential\ apt-get -y install build-essential\
libswresample-dev libavformat-dev libavcodec-dev libavutil-dev\ libswresample-dev libavformat-dev libavcodec-dev libavutil-dev\
libgl-dev libx11-dev libxcomposite-dev\ libgl-dev libx11-dev libxcomposite-dev libxrandr-dev\
libpulse-dev libpulse-dev
./install.sh ./install.sh

View File

@ -10,5 +10,6 @@ libavformat = ">=58"
libavutil = ">=56.2" libavutil = ">=56.2"
x11 = ">=1" x11 = ">=1"
xcomposite = ">=0.2" xcomposite = ">=0.2"
xrandr = ">=1"
libpulse = ">=13" libpulse = ">=13"
libswresample = ">=3" libswresample = ">=3"

17
src/capture/capture.c Normal file
View File

@ -0,0 +1,17 @@
#include "../../include/capture/capture.h"
int gsr_capture_start(gsr_capture *cap) {
return cap->start(cap);
}
void gsr_capture_stop(gsr_capture *cap) {
cap->stop(cap);
}
int gsr_capture_capture(gsr_capture *cap, AVFrame *frame) {
return cap->capture(cap, frame);
}
void gsr_capture_destroy(gsr_capture *cap) {
cap->destroy(cap);
}

332
src/capture/nvfbc.c Normal file
View File

@ -0,0 +1,332 @@
#include "../../include/capture/nvfbc.h"
#include "../../external/NvFBC.h"
#include <dlfcn.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <libavutil/frame.h>
typedef struct {
gsr_capture_nvfbc_params params;
void *library;
NVFBC_SESSION_HANDLE nv_fbc_handle;
PNVFBCCREATEINSTANCE nv_fbc_create_instance;
NVFBC_API_FUNCTION_LIST nv_fbc_function_list;
bool fbc_handle_created;
} gsr_capture_nvfbc;
#if defined(_WIN64) || defined(__LP64__)
typedef unsigned long long CUdeviceptr_v2;
#else
typedef unsigned int CUdeviceptr_v2;
#endif
typedef CUdeviceptr_v2 CUdeviceptr;
static int max_int(int a, int b) {
return a > b ? a : b;
}
/* Returns 0 on failure */
static uint32_t get_output_id_from_display_name(NVFBC_RANDR_OUTPUT_INFO *outputs, uint32_t num_outputs, const char *display_name) {
if(!outputs)
return 0;
for(uint32_t i = 0; i < num_outputs; ++i) {
if(strcmp(outputs[i].name, display_name) == 0)
return outputs[i].dwId;
}
return 0;
}
/* TODO: Test with optimus and open kernel modules */
static bool driver_supports_direct_capture_cursor() {
FILE *f = fopen("/proc/driver/nvidia/version", "rb");
if(!f)
return false;
char buffer[2048];
size_t bytes_read = fread(buffer, 1, sizeof(buffer) - 1, f);
buffer[bytes_read] = '\0';
bool supports_cursor = false;
const char *p = strstr(buffer, "Kernel Module");
if(p) {
p += 13;
int driver_major_version = 0, driver_minor_version = 0;
if(sscanf(p, "%d.%d", &driver_major_version, &driver_minor_version) == 2) {
if(driver_major_version > 515 || (driver_major_version == 515 && driver_minor_version >= 57))
supports_cursor = true;
}
}
fclose(f);
return supports_cursor;
}
static bool gsr_capture_nvfbc_load_library(gsr_capture *cap) {
gsr_capture_nvfbc *cap_nvfbc = cap->priv;
dlerror(); /* clear */
void *lib = dlopen("libnvidia-fbc.so.1", RTLD_LAZY);
if(!lib) {
fprintf(stderr, "gsr error: failed to load libnvidia-fbc.so.1, error: %s\n", dlerror());
return false;
}
cap_nvfbc->nv_fbc_create_instance = (PNVFBCCREATEINSTANCE)dlsym(lib, "NvFBCCreateInstance");
if(!cap_nvfbc->nv_fbc_create_instance) {
fprintf(stderr, "gsr error: unable to resolve symbol 'NvFBCCreateInstance'\n");
dlclose(lib);
return false;
}
memset(&cap_nvfbc->nv_fbc_function_list, 0, sizeof(cap_nvfbc->nv_fbc_function_list));
cap_nvfbc->nv_fbc_function_list.dwVersion = NVFBC_VERSION;
NVFBCSTATUS status = cap_nvfbc->nv_fbc_create_instance(&cap_nvfbc->nv_fbc_function_list);
if(status != NVFBC_SUCCESS) {
fprintf(stderr, "gsr error: failed to create NvFBC instance (status: %d)\n", status);
dlclose(lib);
return false;
}
cap_nvfbc->library = lib;
return true;
}
static int gsr_capture_nvfbc_start(gsr_capture *cap) {
gsr_capture_nvfbc *cap_nvfbc = cap->priv;
const uint32_t x = max_int(cap_nvfbc->params.pos.x, 0);
const uint32_t y = max_int(cap_nvfbc->params.pos.y, 0);
const uint32_t width = max_int(cap_nvfbc->params.size.x, 0);
const uint32_t height = max_int(cap_nvfbc->params.size.y, 0);
if(!cap_nvfbc->library || !cap_nvfbc->params.display_to_capture || cap_nvfbc->fbc_handle_created)
return -1;
const bool capture_region = (x > 0 || y > 0 || width > 0 || height > 0);
NVFBCSTATUS status;
NVFBC_TRACKING_TYPE tracking_type;
bool capture_session_created = false;
uint32_t output_id = 0;
cap_nvfbc->fbc_handle_created = false;
NVFBC_CREATE_HANDLE_PARAMS create_params;
memset(&create_params, 0, sizeof(create_params));
create_params.dwVersion = NVFBC_CREATE_HANDLE_PARAMS_VER;
status = cap_nvfbc->nv_fbc_function_list.nvFBCCreateHandle(&cap_nvfbc->nv_fbc_handle, &create_params);
if(status != NVFBC_SUCCESS) {
// Reverse engineering for interoperability
const uint8_t enable_key[] = { 0xac, 0x10, 0xc9, 0x2e, 0xa5, 0xe6, 0x87, 0x4f, 0x8f, 0x4b, 0xf4, 0x61, 0xf8, 0x56, 0x27, 0xe9 };
create_params.privateData = enable_key;
create_params.privateDataSize = 16;
status = cap_nvfbc->nv_fbc_function_list.nvFBCCreateHandle(&cap_nvfbc->nv_fbc_handle, &create_params);
if(status != NVFBC_SUCCESS) {
fprintf(stderr, "gsr error: gsr_capture_nvfbc_start failed: %s\n", cap_nvfbc->nv_fbc_function_list.nvFBCGetLastErrorStr(cap_nvfbc->nv_fbc_handle));
return -1;
}
}
cap_nvfbc->fbc_handle_created = true;
NVFBC_GET_STATUS_PARAMS status_params;
memset(&status_params, 0, sizeof(status_params));
status_params.dwVersion = NVFBC_GET_STATUS_PARAMS_VER;
status = cap_nvfbc->nv_fbc_function_list.nvFBCGetStatus(cap_nvfbc->nv_fbc_handle, &status_params);
if(status != NVFBC_SUCCESS) {
fprintf(stderr, "gsr error: gsr_capture_nvfbc_start failed: %s\n", cap_nvfbc->nv_fbc_function_list.nvFBCGetLastErrorStr(cap_nvfbc->nv_fbc_handle));
goto error_cleanup;
}
if(status_params.bCanCreateNow == NVFBC_FALSE) {
fprintf(stderr, "gsr error: gsr_capture_nvfbc_start failed: it's not possible to create a capture session on this system\n");
goto error_cleanup;
}
tracking_type = strcmp(cap_nvfbc->params.display_to_capture, "screen") == 0 ? NVFBC_TRACKING_SCREEN : NVFBC_TRACKING_OUTPUT;
if(tracking_type == NVFBC_TRACKING_OUTPUT) {
if(!status_params.bXRandRAvailable) {
fprintf(stderr, "gsr error: gsr_capture_nvfbc_start failed: the xrandr extension is not available\n");
goto error_cleanup;
}
if(status_params.bInModeset) {
fprintf(stderr, "gsr error: gsr_capture_nvfbc_start failed: the x server is in modeset, unable to record\n");
goto error_cleanup;
}
output_id = get_output_id_from_display_name(status_params.outputs, status_params.dwOutputNum, cap_nvfbc->params.display_to_capture);
if(output_id == 0) {
fprintf(stderr, "gsr error: gsr_capture_nvfbc_start failed: display '%s' not found\n", cap_nvfbc->params.display_to_capture);
goto error_cleanup;
}
}
NVFBC_CREATE_CAPTURE_SESSION_PARAMS create_capture_params;
memset(&create_capture_params, 0, sizeof(create_capture_params));
create_capture_params.dwVersion = NVFBC_CREATE_CAPTURE_SESSION_PARAMS_VER;
create_capture_params.eCaptureType = NVFBC_CAPTURE_SHARED_CUDA;
create_capture_params.bWithCursor = (!cap_nvfbc->params.direct_capture || driver_supports_direct_capture_cursor()) ? NVFBC_TRUE : NVFBC_FALSE;
if(capture_region)
create_capture_params.captureBox = (NVFBC_BOX){ x, y, width, height };
create_capture_params.eTrackingType = tracking_type;
create_capture_params.dwSamplingRateMs = 1000u / (uint32_t)cap_nvfbc->params.fps;
create_capture_params.bAllowDirectCapture = cap_nvfbc->params.direct_capture ? NVFBC_TRUE : NVFBC_FALSE;
create_capture_params.bPushModel = cap_nvfbc->params.direct_capture ? NVFBC_TRUE : NVFBC_FALSE;
if(tracking_type == NVFBC_TRACKING_OUTPUT)
create_capture_params.dwOutputId = output_id;
status = cap_nvfbc->nv_fbc_function_list.nvFBCCreateCaptureSession(cap_nvfbc->nv_fbc_handle, &create_capture_params);
if(status != NVFBC_SUCCESS) {
fprintf(stderr, "gsr error: gsr_capture_nvfbc_start failed: %s\n", cap_nvfbc->nv_fbc_function_list.nvFBCGetLastErrorStr(cap_nvfbc->nv_fbc_handle));
goto error_cleanup;
}
capture_session_created = true;
NVFBC_TOCUDA_SETUP_PARAMS setup_params;
memset(&setup_params, 0, sizeof(setup_params));
setup_params.dwVersion = NVFBC_TOCUDA_SETUP_PARAMS_VER;
setup_params.eBufferFormat = NVFBC_BUFFER_FORMAT_BGRA;
status = cap_nvfbc->nv_fbc_function_list.nvFBCToCudaSetUp(cap_nvfbc->nv_fbc_handle, &setup_params);
if(status != NVFBC_SUCCESS) {
fprintf(stderr, "gsr error: gsr_capture_nvfbc_start failed: %s\n", cap_nvfbc->nv_fbc_function_list.nvFBCGetLastErrorStr(cap_nvfbc->nv_fbc_handle));
goto error_cleanup;
}
return 0;
error_cleanup:
if(cap_nvfbc->fbc_handle_created) {
if(capture_session_created) {
NVFBC_DESTROY_CAPTURE_SESSION_PARAMS destroy_capture_params;
memset(&destroy_capture_params, 0, sizeof(destroy_capture_params));
destroy_capture_params.dwVersion = NVFBC_DESTROY_CAPTURE_SESSION_PARAMS_VER;
cap_nvfbc->nv_fbc_function_list.nvFBCDestroyCaptureSession(cap_nvfbc->nv_fbc_handle, &destroy_capture_params);
}
NVFBC_DESTROY_HANDLE_PARAMS destroy_params;
memset(&destroy_params, 0, sizeof(destroy_params));
destroy_params.dwVersion = NVFBC_DESTROY_HANDLE_PARAMS_VER;
cap_nvfbc->nv_fbc_function_list.nvFBCDestroyHandle(cap_nvfbc->nv_fbc_handle, &destroy_params);
cap_nvfbc->fbc_handle_created = false;
}
output_id = 0;
return -1;
}
static void gsr_capture_nvfbc_stop(gsr_capture *cap) {
gsr_capture_nvfbc *cap_nvfbc = cap->priv;
/* Intentionally ignore failure on destroy */
if(!cap_nvfbc->nv_fbc_handle)
return;
NVFBC_DESTROY_CAPTURE_SESSION_PARAMS destroy_capture_params;
memset(&destroy_capture_params, 0, sizeof(destroy_capture_params));
destroy_capture_params.dwVersion = NVFBC_DESTROY_CAPTURE_SESSION_PARAMS_VER;
cap_nvfbc->nv_fbc_function_list.nvFBCDestroyCaptureSession(cap_nvfbc->nv_fbc_handle, &destroy_capture_params);
NVFBC_DESTROY_HANDLE_PARAMS destroy_params;
memset(&destroy_params, 0, sizeof(destroy_params));
destroy_params.dwVersion = NVFBC_DESTROY_HANDLE_PARAMS_VER;
cap_nvfbc->nv_fbc_function_list.nvFBCDestroyHandle(cap_nvfbc->nv_fbc_handle, &destroy_params);
cap_nvfbc->nv_fbc_handle = 0;
}
static int gsr_capture_nvfbc_capture(gsr_capture *cap, AVFrame *frame) {
gsr_capture_nvfbc *cap_nvfbc = cap->priv;
if(!cap_nvfbc->library || !cap_nvfbc->fbc_handle_created)
return -1;
CUdeviceptr cu_device_ptr = 0;
NVFBC_FRAME_GRAB_INFO frame_info;
memset(&frame_info, 0, sizeof(frame_info));
NVFBC_TOCUDA_GRAB_FRAME_PARAMS grab_params;
memset(&grab_params, 0, sizeof(grab_params));
grab_params.dwVersion = NVFBC_TOCUDA_GRAB_FRAME_PARAMS_VER;
grab_params.dwFlags = NVFBC_TOCUDA_GRAB_FLAGS_NOWAIT;/* | NVFBC_TOCUDA_GRAB_FLAGS_FORCE_REFRESH;*/
grab_params.pFrameGrabInfo = &frame_info;
grab_params.pCUDADeviceBuffer = &cu_device_ptr;
NVFBCSTATUS status = cap_nvfbc->nv_fbc_function_list.nvFBCToCudaGrabFrame(cap_nvfbc->nv_fbc_handle, &grab_params);
if(status != NVFBC_SUCCESS) {
fprintf(stderr, "gsr error: gsr_capture_nvfbc_capture failed: %s\n", cap_nvfbc->nv_fbc_function_list.nvFBCGetLastErrorStr(cap_nvfbc->nv_fbc_handle));
return -1;
}
/*
*byte_size = frame_info.dwByteSize;
TODO: Check bIsNewFrame
TODO: Check dwWidth and dwHeight and update size in video output in ffmpeg. This can happen when xrandr is used to change monitor resolution
*/
frame->data[0] = (uint8_t*)cu_device_ptr;
frame->linesize[0] = frame->width * 4;
return 0;
}
static void gsr_capture_nvfbc_destroy(gsr_capture *cap) {
if(cap) {
gsr_capture_nvfbc *cap_nvfbc = cap->priv;
gsr_capture_nvfbc_stop(cap);
if(cap_nvfbc) {
dlclose(cap_nvfbc->library);
free((void*)cap_nvfbc->params.display_to_capture);
free(cap->priv);
cap->priv = NULL;
}
free(cap);
}
}
gsr_capture* gsr_capture_nvfbc_create(const gsr_capture_nvfbc_params *params) {
if(!params) {
fprintf(stderr, "gsr error: gsr_capture_nvfbc_create params is NULL\n");
return NULL;
}
gsr_capture *cap = calloc(1, sizeof(gsr_capture));
if(!cap)
return NULL;
gsr_capture_nvfbc *cap_nvfbc = calloc(1, sizeof(gsr_capture_nvfbc));
if(!cap_nvfbc) {
free(cap);
return NULL;
}
const char *display_to_capture = strdup(params->display_to_capture);
if(!display_to_capture) {
free(cap);
free(cap_nvfbc);
return NULL;
}
cap_nvfbc->params = *params;
cap_nvfbc->params.display_to_capture = display_to_capture;
cap_nvfbc->params.fps = max_int(cap_nvfbc->params.fps, 1);
*cap = (gsr_capture) {
.start = gsr_capture_nvfbc_start,
.stop = gsr_capture_nvfbc_stop,
.capture = gsr_capture_nvfbc_capture,
.destroy = gsr_capture_nvfbc_destroy,
.priv = cap_nvfbc
};
if(!gsr_capture_nvfbc_load_library(cap)) {
gsr_capture_nvfbc_destroy(cap);
return NULL;
}
return cap;
}

View File

@ -15,6 +15,10 @@
along with this program. If not, see <https://www.gnu.org/licenses/>. along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
extern "C" {
#include "../include/capture/nvfbc.h"
}
#include <assert.h> #include <assert.h>
#include <stdio.h> #include <stdio.h>
#include <stdlib.h> #include <stdlib.h>
@ -31,11 +35,11 @@
#include <fcntl.h> #include <fcntl.h>
#include "../include/sound.hpp" #include "../include/sound.hpp"
#include "../include/NvFBCLibrary.hpp"
#include "../include/CudaLibrary.hpp" #include "../include/CudaLibrary.hpp"
#include "../include/GlLibrary.hpp" #include "../include/GlLibrary.hpp"
#include <X11/extensions/Xcomposite.h> #include <X11/extensions/Xcomposite.h>
#include <X11/extensions/Xrandr.h>
//#include <X11/Xatom.h> //#include <X11/Xatom.h>
extern "C" { extern "C" {
@ -66,6 +70,75 @@ static thread_local char av_error_buffer[AV_ERROR_MAX_STRING_SIZE];
static Cuda cuda; static Cuda cuda;
static GlLibrary gl; static GlLibrary gl;
static const XRRModeInfo* get_mode_info(const XRRScreenResources *sr, RRMode id) {
for(int i = 0; i < sr->nmode; ++i) {
if(sr->modes[i].id == id)
return &sr->modes[i];
}
return nullptr;
}
typedef void (*active_monitor_callback)(const XRROutputInfo *output_info, const XRRCrtcInfo *crt_info, const XRRModeInfo *mode_info, void *userdata);
static void for_each_active_monitor_output(Display *display, active_monitor_callback callback, void *userdata) {
XRRScreenResources *screen_res = XRRGetScreenResources(display, DefaultRootWindow(display));
if(!screen_res)
return;
for(int i = 0; i < screen_res->noutput; ++i) {
XRROutputInfo *out_info = XRRGetOutputInfo(display, screen_res, screen_res->outputs[i]);
if(out_info && out_info->crtc && out_info->connection == RR_Connected) {
XRRCrtcInfo *crt_info = XRRGetCrtcInfo(display, screen_res, out_info->crtc);
if(crt_info && crt_info->mode) {
const XRRModeInfo *mode_info = get_mode_info(screen_res, crt_info->mode);
if(mode_info)
callback(out_info, crt_info, mode_info, userdata);
}
if(crt_info)
XRRFreeCrtcInfo(crt_info);
}
if(out_info)
XRRFreeOutputInfo(out_info);
}
XRRFreeScreenResources(screen_res);
}
typedef struct {
vec2i pos;
vec2i size;
} gsr_monitor;
typedef struct {
const char *name;
int name_len;
gsr_monitor *monitor;
bool found_monitor;
} get_monitor_by_name_userdata;
static void get_monitor_by_name_callback(const XRROutputInfo *output_info, const XRRCrtcInfo *crt_info, const XRRModeInfo *mode_info, void *userdata) {
get_monitor_by_name_userdata *data = (get_monitor_by_name_userdata*)userdata;
if(!data->found_monitor && data->name_len == output_info->nameLen && memcmp(data->name, output_info->name, data->name_len) == 0) {
data->monitor->pos = { crt_info->x, crt_info->y };
data->monitor->size = { (int)crt_info->width, (int)crt_info->height };
data->found_monitor = true;
}
}
static bool get_monitor_by_name(Display *display, const char *name, gsr_monitor *monitor) {
get_monitor_by_name_userdata userdata;
userdata.name = name;
userdata.name_len = strlen(name);
userdata.monitor = monitor;
userdata.found_monitor = false;
for_each_active_monitor_output(display, get_monitor_by_name_callback, &userdata);
return userdata.found_monitor;
}
static void monitor_output_callback_print(const XRROutputInfo *output_info, const XRRCrtcInfo *crt_info, const XRRModeInfo *mode_info, void *userdata) {
fprintf(stderr, " \"%.*s\" (%dx%d+%d+%d)\n", output_info->nameLen, output_info->name, (int)crt_info->width, (int)crt_info->height, crt_info->x, crt_info->y);
}
static char* av_error_to_string(int err) { static char* av_error_to_string(int err) {
if(av_strerror(err, av_error_buffer, sizeof(av_error_buffer)) < 0) if(av_strerror(err, av_error_buffer, sizeof(av_error_buffer)) < 0)
strcpy(av_error_buffer, "Unknown error"); strcpy(av_error_buffer, "Unknown error");
@ -1216,7 +1289,7 @@ int main(int argc, char **argv) {
} }
if(!match) { if(!match) {
fprintf(stderr, "Error: Audio input device '%s' is not a valid audio device. Expected one of:\n", request_audio_input.name.c_str()); fprintf(stderr, "Error: Audio input device '%s' is not a valid audio device, expected one of:\n", request_audio_input.name.c_str());
for(const auto &existing_audio_input : audio_inputs) { for(const auto &existing_audio_input : audio_inputs) {
fprintf(stderr, " %s\n", existing_audio_input.name.c_str()); fprintf(stderr, " %s\n", existing_audio_input.name.c_str());
} }
@ -1318,6 +1391,15 @@ int main(int argc, char **argv) {
return 1; return 1;
} }
Display *dpy = XOpenDisplay(nullptr);
if (!dpy) {
fprintf(stderr, "Error: Failed to open display\n");
return 1;
}
XSetErrorHandler(x11_error_handler);
XSetIOErrorHandler(x11_io_error_handler);
const char *record_area = args["-s"].value(); const char *record_area = args["-s"].value();
uint32_t window_width = 0; uint32_t window_width = 0;
@ -1325,7 +1407,7 @@ int main(int argc, char **argv) {
int window_x = 0; int window_x = 0;
int window_y = 0; int window_y = 0;
NvFBCLibrary nv_fbc_library; gsr_capture *capture = nullptr;
const char *window_str = args["-w"].value(); const char *window_str = args["-w"].value();
Window src_window_id = None; Window src_window_id = None;
@ -1335,9 +1417,6 @@ int main(int argc, char **argv) {
usage(); usage();
} }
if(!nv_fbc_library.load())
return 1;
const char *capture_target = window_str; const char *capture_target = window_str;
bool direct_capture = strcmp(window_str, "screen-direct") == 0; bool direct_capture = strcmp(window_str, "screen-direct") == 0;
if(direct_capture) { if(direct_capture) {
@ -1347,7 +1426,36 @@ int main(int argc, char **argv) {
fprintf(stderr, "Warning: screen-direct has temporary been disabled as it causes stuttering. This is likely a NvFBC bug. Falling back to \"screen\".\n"); fprintf(stderr, "Warning: screen-direct has temporary been disabled as it causes stuttering. This is likely a NvFBC bug. Falling back to \"screen\".\n");
} }
if(!nv_fbc_library.create(capture_target, fps, &window_width, &window_height, region_x, region_y, region_width, region_height, direct_capture)) gsr_capture_nvfbc_params nvfbc_params;
nvfbc_params.display_to_capture = capture_target;
nvfbc_params.fps = fps;
nvfbc_params.pos = { (int)region_x, (int)region_y };
nvfbc_params.size = { (int)region_width, (int)region_height };
nvfbc_params.direct_capture = direct_capture;
capture = gsr_capture_nvfbc_create(&nvfbc_params);
if(!capture)
return 1;
// TODO: Set window_width and window_height to the nvfbc blalba
if(strcmp(capture_target, "screen") == 0) {
window_width = WidthOfScreen(DefaultScreenOfDisplay(dpy));
window_height = HeightOfScreen(DefaultScreenOfDisplay(dpy));
} else {
gsr_monitor gmon;
if(!get_monitor_by_name(dpy, capture_target, &gmon)) {
fprintf(stderr, "gsr error: display \"%s\" not found, expected one of:\n", capture_target);
fprintf(stderr, " \"screen\" (%dx%d+%d+%d)\n", WidthOfScreen(DefaultScreenOfDisplay(dpy)), HeightOfScreen(DefaultScreenOfDisplay(dpy)), 0, 0);
fprintf(stderr, " \"screen-direct\" (%dx%d+%d+%d)\n", WidthOfScreen(DefaultScreenOfDisplay(dpy)), HeightOfScreen(DefaultScreenOfDisplay(dpy)), 0, 0);
for_each_active_monitor_output(dpy, monitor_output_callback_print, NULL);
return 1;
}
window_width = gmon.size.x;
window_height = gmon.size.y;
}
// TODO: Move down
if(gsr_capture_start(capture) != 0)
return 1; return 1;
} else { } else {
errno = 0; errno = 0;
@ -1387,15 +1495,6 @@ int main(int argc, char **argv) {
const double target_fps = 1.0 / (double)fps; const double target_fps = 1.0 / (double)fps;
Display *dpy = XOpenDisplay(nullptr);
if (!dpy) {
fprintf(stderr, "Error: Failed to open display\n");
return 1;
}
XSetErrorHandler(x11_error_handler);
XSetIOErrorHandler(x11_io_error_handler);
WindowPixmap window_pixmap; WindowPixmap window_pixmap;
Window window = None; Window window = None;
if(src_window_id) { if(src_window_id) {
@ -1993,13 +2092,7 @@ int main(int argc, char **argv) {
frame_captured = true; frame_captured = true;
} else { } else {
// TODO: Check when src_cu_device_ptr changes and re-register resource gsr_capture_capture(capture, frame);
frame->linesize[0] = frame->width * 4;
uint32_t byte_size = 0;
CUdeviceptr src_cu_device_ptr = 0;
frame_captured = nv_fbc_library.capture(&src_cu_device_ptr, &byte_size);
frame->data[0] = (uint8_t*)src_cu_device_ptr;
} }
// res = cuda.cuCtxPopCurrent_v2(&old_ctx); // res = cuda.cuCtxPopCurrent_v2(&old_ctx);
} }
@ -2059,6 +2152,9 @@ int main(int argc, char **argv) {
if(replay_buffer_size_secs == -1 && !(output_format->flags & AVFMT_NOFILE)) if(replay_buffer_size_secs == -1 && !(output_format->flags & AVFMT_NOFILE))
avio_close(av_format_context->pb); avio_close(av_format_context->pb);
if(capture)
gsr_capture_destroy(capture);
if(dpy) if(dpy)
XCloseDisplay(dpy); XCloseDisplay(dpy);