Add option to record a display or all displays. This requires nvfbc
This commit is contained in:
parent
7b39bd81c0
commit
4617e2c45a
@ -12,7 +12,8 @@ When recording a 4k game, fps drops from 30 to 7 when using OBS Studio, however
|
|||||||
the fps remains at 30.
|
the fps remains at 30.
|
||||||
|
|
||||||
# Installation
|
# Installation
|
||||||
gpu screen recorder can be built using [sibs](https://git.dec05eba.com/sibs) or if you are running Arch Linux, then you can find it on aur under the name gpu-screen-recorder-git (`yay -S gpu-screen-recorder-git`).
|
gpu screen recorder can be built using [sibs](https://git.dec05eba.com/sibs) or if you are running Arch Linux, then you can find it on aur under the name gpu-screen-recorder-git (`yay -S gpu-screen-recorder-git`).\
|
||||||
|
Recording displays requires a gpu with NvFBC support. Normally only tesla and quadro gpus support this, but by using https://github.com/keylase/nvidia-patch 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
|
||||||
Run `interactive.sh` or run gpu-screen-recorder directly, for example: `gpu-screen-recorder -w 0x1c00001 -c mp4 -f 60 -a bluez_sink.00_18_09_8A_07_93.a2dp_sink.monitor > test_video.mp4`\
|
Run `interactive.sh` or run gpu-screen-recorder directly, for example: `gpu-screen-recorder -w 0x1c00001 -c mp4 -f 60 -a bluez_sink.00_18_09_8A_07_93.a2dp_sink.monitor > test_video.mp4`\
|
||||||
|
7
TODO
Normal file
7
TODO
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
Check for reparent.
|
||||||
|
Only add window to list if its the window is a topmost window.
|
||||||
|
Use nvEncoder api directly? maybe with this we could copy the window opengl texture directly to the gpu which doesn't work right now for some reason.
|
||||||
|
Right now we are required to copy the opengl texture to another opengl texture first.
|
||||||
|
Load cuda at runtime with dlopen.
|
||||||
|
Track window damages and only update then. That is better for output file size.
|
||||||
|
Remove cuda to cuda copy when using nvFBC if possible. ffmpeg is getting in the way.
|
2006
include/NvFBC.h
Normal file
2006
include/NvFBC.h
Normal file
File diff suppressed because it is too large
Load Diff
245
include/NvFBCLibrary.hpp
Normal file
245
include/NvFBCLibrary.hpp
Normal file
@ -0,0 +1,245 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "NvFBC.h"
|
||||||
|
#include <cuda.h>
|
||||||
|
|
||||||
|
#include <dlfcn.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <stdio.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_NOW);
|
||||||
|
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) {
|
||||||
|
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) {
|
||||||
|
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 = NVFBC_TRUE; // This will need to be disabled when using bAllowDirectCapture
|
||||||
|
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;
|
||||||
|
if(tracking_type == NVFBC_TRACKING_OUTPUT)
|
||||||
|
create_capture_params.dwOutputId = output_id;
|
||||||
|
|
||||||
|
// TODO: Use create_capture_params.bAllowDirectCapture and create_capture_params.bPushModel
|
||||||
|
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*/ CUdeviceptr *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;
|
||||||
|
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
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
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;
|
||||||
|
};
|
517
src/main.cpp
517
src/main.cpp
@ -52,6 +52,8 @@ extern "C" {
|
|||||||
#include <libavutil/hwcontext.h>
|
#include <libavutil/hwcontext.h>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#include "../include/NvFBCLibrary.hpp"
|
||||||
|
|
||||||
#include <deque>
|
#include <deque>
|
||||||
|
|
||||||
//#include <CL/cl.h>
|
//#include <CL/cl.h>
|
||||||
@ -397,12 +399,13 @@ static AVStream *add_audio_stream(AVFormatContext *av_format_context, AVCodec **
|
|||||||
|
|
||||||
static AVStream *add_video_stream(AVFormatContext *av_format_context, AVCodec **codec,
|
static AVStream *add_video_stream(AVFormatContext *av_format_context, AVCodec **codec,
|
||||||
VideoQuality video_quality,
|
VideoQuality video_quality,
|
||||||
const WindowPixmap &window_pixmap,
|
int texture_width, int texture_height,
|
||||||
int fps) {
|
int fps) {
|
||||||
//*codec = avcodec_find_encoder(codec_id);
|
//*codec = avcodec_find_encoder(codec_id);
|
||||||
*codec = avcodec_find_encoder_by_name("h264_nvenc");
|
bool using_hevc = true;
|
||||||
|
*codec = avcodec_find_encoder_by_name("hevc_nvenc");
|
||||||
if (!*codec) {
|
if (!*codec) {
|
||||||
*codec = avcodec_find_encoder_by_name("nvenc_h264");
|
*codec = avcodec_find_encoder_by_name("nvenc_hevc");
|
||||||
}
|
}
|
||||||
if (!*codec) {
|
if (!*codec) {
|
||||||
fprintf(
|
fprintf(
|
||||||
@ -425,8 +428,8 @@ static AVStream *add_video_stream(AVFormatContext *av_format_context, AVCodec **
|
|||||||
assert((*codec)->type == AVMEDIA_TYPE_VIDEO);
|
assert((*codec)->type == AVMEDIA_TYPE_VIDEO);
|
||||||
codec_context->codec_id = (*codec)->id;
|
codec_context->codec_id = (*codec)->id;
|
||||||
fprintf(stderr, "codec id: %d\n", (*codec)->id);
|
fprintf(stderr, "codec id: %d\n", (*codec)->id);
|
||||||
codec_context->width = window_pixmap.texture_width & ~1;
|
codec_context->width = texture_width & ~1;
|
||||||
codec_context->height = window_pixmap.texture_height & ~1;
|
codec_context->height = texture_height & ~1;
|
||||||
codec_context->bit_rate = 7500000 + (codec_context->width * codec_context->height) / 2;
|
codec_context->bit_rate = 7500000 + (codec_context->width * codec_context->height) / 2;
|
||||||
// Timebase: This is the fundamental unit of time (in seconds) in terms
|
// Timebase: This is the fundamental unit of time (in seconds) in terms
|
||||||
// of which frame timestamps are represented. For fixed-fps content,
|
// of which frame timestamps are represented. For fixed-fps content,
|
||||||
@ -439,7 +442,7 @@ static AVStream *add_video_stream(AVFormatContext *av_format_context, AVCodec **
|
|||||||
codec_context->sample_aspect_ratio.num = 0;
|
codec_context->sample_aspect_ratio.num = 0;
|
||||||
codec_context->sample_aspect_ratio.den = 0;
|
codec_context->sample_aspect_ratio.den = 0;
|
||||||
codec_context->gop_size = fps * 2;
|
codec_context->gop_size = fps * 2;
|
||||||
codec_context->max_b_frames = 2;
|
codec_context->max_b_frames = using_hevc ? 0 : 2;
|
||||||
codec_context->pix_fmt = AV_PIX_FMT_CUDA;
|
codec_context->pix_fmt = AV_PIX_FMT_CUDA;
|
||||||
codec_context->color_range = AVCOL_RANGE_JPEG;
|
codec_context->color_range = AVCOL_RANGE_JPEG;
|
||||||
switch(video_quality) {
|
switch(video_quality) {
|
||||||
@ -577,24 +580,26 @@ static void open_video(AVCodec *codec, AVStream *stream,
|
|||||||
exit(1);
|
exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
CUresult res;
|
if(window_pixmap.target_texture_id != 0) {
|
||||||
CUcontext old_ctx;
|
CUresult res;
|
||||||
res = cuCtxPopCurrent(&old_ctx);
|
CUcontext old_ctx;
|
||||||
res = cuCtxPushCurrent(*cuda_context);
|
res = cuCtxPopCurrent(&old_ctx);
|
||||||
res = cuGraphicsGLRegisterImage(
|
res = cuCtxPushCurrent(*cuda_context);
|
||||||
cuda_graphics_resource, window_pixmap.target_texture_id, GL_TEXTURE_2D,
|
res = cuGraphicsGLRegisterImage(
|
||||||
CU_GRAPHICS_REGISTER_FLAGS_READ_ONLY);
|
cuda_graphics_resource, window_pixmap.target_texture_id, GL_TEXTURE_2D,
|
||||||
// cuGraphicsUnregisterResource(*cuda_graphics_resource);
|
CU_GRAPHICS_REGISTER_FLAGS_READ_ONLY);
|
||||||
if (res != CUDA_SUCCESS) {
|
// cuGraphicsUnregisterResource(*cuda_graphics_resource);
|
||||||
const char *err_str;
|
if (res != CUDA_SUCCESS) {
|
||||||
cuGetErrorString(res, &err_str);
|
const char *err_str;
|
||||||
fprintf(stderr,
|
cuGetErrorString(res, &err_str);
|
||||||
"Error: cuGraphicsGLRegisterImage failed, error %s, texture "
|
fprintf(stderr,
|
||||||
"id: %u\n",
|
"Error: cuGraphicsGLRegisterImage failed, error %s, texture "
|
||||||
err_str, window_pixmap.target_texture_id);
|
"id: %u\n",
|
||||||
exit(1);
|
err_str, window_pixmap.target_texture_id);
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
res = cuCtxPopCurrent(&old_ctx);
|
||||||
}
|
}
|
||||||
res = cuCtxPopCurrent(&old_ctx);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void close_video(AVStream *video_stream, AVFrame *frame) {
|
static void close_video(AVStream *video_stream, AVFrame *frame) {
|
||||||
@ -605,9 +610,10 @@ static void close_video(AVStream *video_stream, AVFrame *frame) {
|
|||||||
static void usage() {
|
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> -c <container_format> -f <fps> [-a <audio_input>] [-q <quality>] [-r <replay_buffer_size_sec>] [-o <output_file>]\n");
|
||||||
fprintf(stderr, "OPTIONS:\n");
|
fprintf(stderr, "OPTIONS:\n");
|
||||||
fprintf(stderr, " -w Window to record.\n");
|
fprintf(stderr, " -w Window to record or a display or \"screen\". The display is the display name in xrandr and if \"screen\" is selected then all displays are recorded and they are recorded in h265 (aka hevc). Recording a display requires a gpu with NvFBC support.\n");
|
||||||
|
//fprintf(stderr, " -s The screen region to capture in format WxH+X+Y. This is only applicable when -w is a display or \"screen\". Optional, the entire window/display/screen is recorded by default.\n");
|
||||||
fprintf(stderr, " -c Container format for output file, for example mp4, or flv.\n");
|
fprintf(stderr, " -c Container format for output file, for example mp4, or flv.\n");
|
||||||
fprintf(stderr, " -f Framerate to record at.\n");
|
fprintf(stderr, " -f Framerate to record at. Clamped to [1,500].\n");
|
||||||
fprintf(stderr, " -a Audio device to record from (pulse audio device). Optional, disabled by default.\n");
|
fprintf(stderr, " -a Audio device to record from (pulse audio device). Optional, disabled by default.\n");
|
||||||
fprintf(stderr, " -q Video quality. Should either be 'medium', 'high' or 'ultra'. Optional, set to 'medium' be default.\n");
|
fprintf(stderr, " -q Video quality. Should either be 'medium', 'high' or 'ultra'. Optional, set to 'medium' be default.\n");
|
||||||
fprintf(stderr, " -r Replay buffer size in seconds. If this is set, then only the last seconds as set by this option will be stored"
|
fprintf(stderr, " -r Replay buffer size in seconds. If this is set, then only the last seconds as set by this option will be stored"
|
||||||
@ -628,17 +634,39 @@ struct Arg {
|
|||||||
bool optional;
|
bool optional;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
static bool is_hex_num(char c) {
|
||||||
|
return (c >= 'A' && c <= 'F') || (c >= 'a' && c <= 'f') || (c >= '0' && c <= '9');
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool contains_non_hex_number(const char *str) {
|
||||||
|
size_t len = strlen(str);
|
||||||
|
if(len >= 2 && memcmp(str, "0x", 2) == 0) {
|
||||||
|
str += 2;
|
||||||
|
len -= 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
for(size_t i = 0; i < len; ++i) {
|
||||||
|
char c = str[i];
|
||||||
|
if(c == '\0')
|
||||||
|
return false;
|
||||||
|
if(!is_hex_num(c))
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
int main(int argc, char **argv) {
|
int main(int argc, char **argv) {
|
||||||
signal(SIGINT, int_handler);
|
signal(SIGINT, int_handler);
|
||||||
|
|
||||||
std::map<std::string, Arg> args = {
|
std::map<std::string, Arg> args = {
|
||||||
{ "-w", Arg { nullptr, false } },
|
{ "-w", Arg { nullptr, false } },
|
||||||
|
//{ "-s", Arg { nullptr, true } },
|
||||||
{ "-c", Arg { nullptr, false } },
|
{ "-c", Arg { nullptr, false } },
|
||||||
{ "-f", Arg { nullptr, false } },
|
{ "-f", Arg { nullptr, false } },
|
||||||
{ "-a", Arg { nullptr, true } },
|
{ "-a", Arg { nullptr, true } },
|
||||||
{ "-q", Arg { nullptr, true } },
|
{ "-q", Arg { nullptr, true } },
|
||||||
{ "-o", Arg { nullptr, true } },
|
{ "-o", Arg { nullptr, true } },
|
||||||
{ "-r", Arg { nullptr, true} }
|
{ "-r", Arg { nullptr, true } }
|
||||||
};
|
};
|
||||||
|
|
||||||
for(int i = 1; i < argc - 1; i += 2) {
|
for(int i = 1; i < argc - 1; i += 2) {
|
||||||
@ -657,13 +685,30 @@ int main(int argc, char **argv) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Window src_window_id = strtol(args["-w"].value, nullptr, 0);
|
uint32_t region_x = 0;
|
||||||
|
uint32_t region_y = 0;
|
||||||
|
uint32_t region_width = 0;
|
||||||
|
uint32_t region_height = 0;
|
||||||
|
|
||||||
|
/*
|
||||||
|
TODO: Fix this. Doesn't work for some reason
|
||||||
|
const char *screen_region = args["-s"].value;
|
||||||
|
if(screen_region) {
|
||||||
|
if(sscanf(screen_region, "%ux%u+%u+%u", ®ion_x, ®ion_y, ®ion_width, ®ion_height) != 4) {
|
||||||
|
fprintf(stderr, "Invalid value for -s '%s', expected a value in format WxH+X+Y\n", screen_region);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
const char *container_format = args["-c"].value;
|
const char *container_format = args["-c"].value;
|
||||||
int fps = atoi(args["-f"].value);
|
int fps = atoi(args["-f"].value);
|
||||||
if(fps <= 0 || fps > 255) {
|
if(fps == 0) {
|
||||||
fprintf(stderr, "invalid fps argument: %s\n", args["-f"].value);
|
fprintf(stderr, "Invalid fps argument: %s\n", args["-f"].value);
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
if(fps > 500)
|
||||||
|
fps = 500;
|
||||||
|
|
||||||
const char *quality_str = args["-q"].value;
|
const char *quality_str = args["-q"].value;
|
||||||
if(!quality_str)
|
if(!quality_str)
|
||||||
@ -681,12 +726,6 @@ int main(int argc, char **argv) {
|
|||||||
usage();
|
usage();
|
||||||
}
|
}
|
||||||
|
|
||||||
const char *filename = args["-o"].value;
|
|
||||||
if(!filename)
|
|
||||||
filename = "/dev/stdout";
|
|
||||||
|
|
||||||
const double target_fps = 1.0 / (double)fps;
|
|
||||||
|
|
||||||
int replay_buffer_size_secs = -1;
|
int replay_buffer_size_secs = -1;
|
||||||
const char *replay_buffer_size_secs_str = args["-r"].value;
|
const char *replay_buffer_size_secs_str = args["-r"].value;
|
||||||
if(replay_buffer_size_secs_str) {
|
if(replay_buffer_size_secs_str) {
|
||||||
@ -698,70 +737,133 @@ int main(int argc, char **argv) {
|
|||||||
replay_buffer_size_secs += 5; // Add a few seconds to account of lost packets because of non-keyframe packets skipped
|
replay_buffer_size_secs += 5; // Add a few seconds to account of lost packets because of non-keyframe packets skipped
|
||||||
}
|
}
|
||||||
|
|
||||||
Display *dpy = XOpenDisplay(nullptr);
|
CUresult res;
|
||||||
if (!dpy) {
|
|
||||||
fprintf(stderr, "Error: Failed to open display\n");
|
res = cuInit(0);
|
||||||
|
if(res != CUDA_SUCCESS) {
|
||||||
|
fprintf(stderr, "Error: cuInit failed (result: %d)\n", res);
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
CUdevice cu_dev;
|
||||||
|
res = cuDeviceGet(&cu_dev, 0);
|
||||||
|
if(res != CUDA_SUCCESS) {
|
||||||
|
fprintf(stderr, "Unable to get CUDA device (result: %d)\n", res);
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool has_name_pixmap = x11_supports_composite_named_window_pixmap(dpy);
|
CUcontext cu_ctx;
|
||||||
if (!has_name_pixmap) {
|
res = cuCtxCreate_v2(&cu_ctx, CU_CTX_SCHED_AUTO, cu_dev);
|
||||||
fprintf(stderr, "Error: XCompositeNameWindowPixmap is not supported by "
|
if(res != CUDA_SUCCESS) {
|
||||||
"your X11 server\n");
|
fprintf(stderr, "Unable to create CUDA context (result: %d)\n", res);
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
XWindowAttributes attr;
|
uint32_t window_width = 0;
|
||||||
if (!XGetWindowAttributes(dpy, src_window_id, &attr)) {
|
uint32_t window_height = 0;
|
||||||
fprintf(stderr, "Error: Invalid window id: %lu\n", src_window_id);
|
|
||||||
return 1;
|
NvFBCLibrary nv_fbc_library;
|
||||||
|
|
||||||
|
const char *window_str = args["-w"].value;
|
||||||
|
Window src_window_id = None;
|
||||||
|
if(contains_non_hex_number(window_str)) {
|
||||||
|
if(!nv_fbc_library.load())
|
||||||
|
return 1;
|
||||||
|
|
||||||
|
if(!nv_fbc_library.create(window_str, fps, &window_width, &window_height, region_x, region_y, region_width, region_height))
|
||||||
|
return 1;
|
||||||
|
} else {
|
||||||
|
src_window_id = strtol(window_str, nullptr, 0);
|
||||||
|
if(src_window_id == None && errno == EINVAL) {
|
||||||
|
fprintf(stderr, "Invalid window number %s\n", window_str);
|
||||||
|
usage();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
XCompositeRedirectWindow(dpy, src_window_id, CompositeRedirectAutomatic);
|
const char *filename = args["-o"].value;
|
||||||
|
if(!filename)
|
||||||
|
filename = "/dev/stdout";
|
||||||
|
|
||||||
// glXMakeContextCurrent(Display *dpy, GLXDrawable draw, GLXDrawable read,
|
const double target_fps = 1.0 / (double)fps;
|
||||||
// GLXContext ctx)
|
|
||||||
if (!glfwInit()) {
|
|
||||||
fprintf(stderr, "Error: Failed to initialize glfw\n");
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 4);
|
|
||||||
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 2);
|
|
||||||
glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE);
|
|
||||||
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
|
|
||||||
glfwWindowHint(GLFW_RESIZABLE, GL_FALSE);
|
|
||||||
|
|
||||||
GLFWwindow *window = glfwCreateWindow(1, 1, "gpu-screen-recorder", nullptr, nullptr);
|
|
||||||
if (!window) {
|
|
||||||
fprintf(stderr, "Error: Failed to create glfw window\n");
|
|
||||||
glfwTerminate();
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
glfwMakeContextCurrent(window);
|
|
||||||
glfwSwapInterval(0);
|
|
||||||
glfwHideWindow(window);
|
|
||||||
|
|
||||||
//#if defined(DEBUG)
|
|
||||||
XSetErrorHandler(x11_error_handler);
|
|
||||||
XSetIOErrorHandler(x11_io_error_handler);
|
|
||||||
//#endif
|
|
||||||
|
|
||||||
glewExperimental = GL_TRUE;
|
|
||||||
GLenum nGlewError = glewInit();
|
|
||||||
if (nGlewError != GLEW_OK) {
|
|
||||||
fprintf(stderr, "%s - Error initializing GLEW! %s\n", __FUNCTION__,
|
|
||||||
glewGetErrorString(nGlewError));
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
glGetError(); // to clear the error caused deep in GLEW
|
|
||||||
|
|
||||||
WindowPixmap window_pixmap;
|
WindowPixmap window_pixmap;
|
||||||
if (!recreate_window_pixmap(dpy, src_window_id, window_pixmap)) {
|
Display *dpy = nullptr;
|
||||||
fprintf(stderr, "Error: Failed to create glx pixmap for window: %lu\n",
|
GLFWwindow *window = nullptr;
|
||||||
src_window_id);
|
if(src_window_id) {
|
||||||
return 1;
|
dpy = XOpenDisplay(nullptr);
|
||||||
|
if (!dpy) {
|
||||||
|
fprintf(stderr, "Error: Failed to open display\n");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool has_name_pixmap = x11_supports_composite_named_window_pixmap(dpy);
|
||||||
|
if (!has_name_pixmap) {
|
||||||
|
fprintf(stderr, "Error: XCompositeNameWindowPixmap is not supported by "
|
||||||
|
"your X11 server\n");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
XWindowAttributes attr;
|
||||||
|
if (!XGetWindowAttributes(dpy, src_window_id, &attr)) {
|
||||||
|
fprintf(stderr, "Error: Invalid window id: %lu\n", src_window_id);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
XCompositeRedirectWindow(dpy, src_window_id, CompositeRedirectAutomatic);
|
||||||
|
|
||||||
|
// glXMakeContextCurrent(Display *dpy, GLXDrawable draw, GLXDrawable read,
|
||||||
|
// GLXContext ctx)
|
||||||
|
if (!glfwInit()) {
|
||||||
|
fprintf(stderr, "Error: Failed to initialize glfw\n");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 4);
|
||||||
|
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 2);
|
||||||
|
glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE);
|
||||||
|
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
|
||||||
|
glfwWindowHint(GLFW_RESIZABLE, GL_FALSE);
|
||||||
|
|
||||||
|
window = glfwCreateWindow(1, 1, "gpu-screen-recorder", nullptr, nullptr);
|
||||||
|
if (!window) {
|
||||||
|
fprintf(stderr, "Error: Failed to create glfw window\n");
|
||||||
|
glfwTerminate();
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
glfwMakeContextCurrent(window);
|
||||||
|
glfwSwapInterval(0);
|
||||||
|
glfwHideWindow(window);
|
||||||
|
|
||||||
|
//#if defined(DEBUG)
|
||||||
|
XSetErrorHandler(x11_error_handler);
|
||||||
|
XSetIOErrorHandler(x11_io_error_handler);
|
||||||
|
//#endif
|
||||||
|
|
||||||
|
glewExperimental = GL_TRUE;
|
||||||
|
GLenum nGlewError = glewInit();
|
||||||
|
if (nGlewError != GLEW_OK) {
|
||||||
|
fprintf(stderr, "%s - Error initializing GLEW! %s\n", __FUNCTION__,
|
||||||
|
glewGetErrorString(nGlewError));
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
glGetError(); // to clear the error caused deep in GLEW
|
||||||
|
|
||||||
|
if (!recreate_window_pixmap(dpy, src_window_id, window_pixmap)) {
|
||||||
|
fprintf(stderr, "Error: Failed to create glx pixmap for window: %lu\n",
|
||||||
|
src_window_id);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
window_pixmap.texture_id = 0;
|
||||||
|
window_pixmap.target_texture_id = 0;
|
||||||
|
window_pixmap.texture_width = window_width;
|
||||||
|
window_pixmap.texture_height = window_height;
|
||||||
|
|
||||||
|
if (!glfwInit()) {
|
||||||
|
fprintf(stderr, "Error: Failed to initialize glfw\n");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Video start
|
// Video start
|
||||||
@ -780,8 +882,7 @@ int main(int argc, char **argv) {
|
|||||||
|
|
||||||
AVCodec *video_codec;
|
AVCodec *video_codec;
|
||||||
AVStream *video_stream =
|
AVStream *video_stream =
|
||||||
add_video_stream(av_format_context, &video_codec, quality,
|
add_video_stream(av_format_context, &video_codec, quality, window_pixmap.texture_width, window_pixmap.texture_height, fps);
|
||||||
window_pixmap, fps);
|
|
||||||
if (!video_stream) {
|
if (!video_stream) {
|
||||||
fprintf(stderr, "Error: Failed to create video stream\n");
|
fprintf(stderr, "Error: Failed to create video stream\n");
|
||||||
return 1;
|
return 1;
|
||||||
@ -795,11 +896,6 @@ int main(int argc, char **argv) {
|
|||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (cuInit(0) < 0) {
|
|
||||||
fprintf(stderr, "Error: cuInit failed\n");
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
|
|
||||||
AVBufferRef *device_ctx;
|
AVBufferRef *device_ctx;
|
||||||
CUgraphicsResource cuda_graphics_resource;
|
CUgraphicsResource cuda_graphics_resource;
|
||||||
open_video(video_codec, video_stream, window_pixmap, &device_ctx,
|
open_video(video_codec, video_stream, window_pixmap, &device_ctx,
|
||||||
@ -841,7 +937,8 @@ int main(int argc, char **argv) {
|
|||||||
// av_frame_free(&rgb_frame);
|
// av_frame_free(&rgb_frame);
|
||||||
// avcodec_close(av_codec_context);
|
// avcodec_close(av_codec_context);
|
||||||
|
|
||||||
XSelectInput(dpy, src_window_id, StructureNotifyMask);
|
if(dpy)
|
||||||
|
XSelectInput(dpy, src_window_id, StructureNotifyMask);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
int damage_event;
|
int damage_event;
|
||||||
@ -857,20 +954,21 @@ int main(int argc, char **argv) {
|
|||||||
|
|
||||||
int frame_count = 0;
|
int frame_count = 0;
|
||||||
|
|
||||||
CUresult res;
|
|
||||||
CUcontext old_ctx;
|
CUcontext old_ctx;
|
||||||
res = cuCtxPopCurrent(&old_ctx);
|
|
||||||
res = cuCtxPushCurrent(*cuda_context);
|
|
||||||
|
|
||||||
// Get texture
|
|
||||||
res = cuGraphicsResourceSetMapFlags(
|
|
||||||
cuda_graphics_resource, CU_GRAPHICS_MAP_RESOURCE_FLAGS_READ_ONLY);
|
|
||||||
res = cuGraphicsMapResources(1, &cuda_graphics_resource, 0);
|
|
||||||
|
|
||||||
// Map texture to cuda array
|
|
||||||
CUarray mapped_array;
|
CUarray mapped_array;
|
||||||
res = cuGraphicsSubResourceGetMappedArray(&mapped_array,
|
if(src_window_id) {
|
||||||
cuda_graphics_resource, 0, 0);
|
res = cuCtxPopCurrent(&old_ctx);
|
||||||
|
res = cuCtxPushCurrent(*cuda_context);
|
||||||
|
|
||||||
|
// Get texture
|
||||||
|
res = cuGraphicsResourceSetMapFlags(
|
||||||
|
cuda_graphics_resource, CU_GRAPHICS_MAP_RESOURCE_FLAGS_READ_ONLY);
|
||||||
|
res = cuGraphicsMapResources(1, &cuda_graphics_resource, 0);
|
||||||
|
|
||||||
|
// Map texture to cuda array
|
||||||
|
res = cuGraphicsSubResourceGetMappedArray(&mapped_array,
|
||||||
|
cuda_graphics_resource, 0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
// Release texture
|
// Release texture
|
||||||
// res = cuGraphicsUnmapResources(1, &cuda_graphics_resource, 0);
|
// res = cuGraphicsUnmapResources(1, &cuda_graphics_resource, 0);
|
||||||
@ -896,11 +994,12 @@ int main(int argc, char **argv) {
|
|||||||
exit(1);
|
exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
XWindowAttributes xwa;
|
if(dpy) {
|
||||||
XGetWindowAttributes(dpy, src_window_id, &xwa);
|
XWindowAttributes xwa;
|
||||||
int window_width = xwa.width;
|
XGetWindowAttributes(dpy, src_window_id, &xwa);
|
||||||
int window_height = xwa.height;
|
window_width = xwa.width;
|
||||||
|
window_height = xwa.height;
|
||||||
|
}
|
||||||
int original_window_width = window_width;
|
int original_window_width = window_width;
|
||||||
int original_window_height = window_height;
|
int original_window_height = window_height;
|
||||||
|
|
||||||
@ -999,67 +1098,70 @@ int main(int argc, char **argv) {
|
|||||||
while (running) {
|
while (running) {
|
||||||
double frame_start = glfwGetTime();
|
double frame_start = glfwGetTime();
|
||||||
glfwPollEvents();
|
glfwPollEvents();
|
||||||
|
if(window)
|
||||||
glClear(GL_COLOR_BUFFER_BIT);
|
glClear(GL_COLOR_BUFFER_BIT);
|
||||||
if (XCheckTypedWindowEvent(dpy, src_window_id, ConfigureNotify, &e) && e.xconfigure.window == src_window_id) {
|
|
||||||
// Window resize
|
|
||||||
if(e.xconfigure.width != window_width || e.xconfigure.height != window_height) {
|
|
||||||
window_width = e.xconfigure.width;
|
|
||||||
window_height = e.xconfigure.height;
|
|
||||||
window_resize_timer = glfwGetTime();
|
|
||||||
window_resized = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
redraw = true;
|
redraw = true;
|
||||||
|
|
||||||
const double window_resize_timeout = 1.0; // 1 second
|
if(src_window_id) {
|
||||||
if(window_resized && glfwGetTime() - window_resize_timer >= window_resize_timeout) {
|
if (XCheckTypedWindowEvent(dpy, src_window_id, ConfigureNotify, &e) && e.xconfigure.window == src_window_id) {
|
||||||
window_resized = false;
|
// Window resize
|
||||||
fprintf(stderr, "Resize window!\n");
|
if(e.xconfigure.width != window_width || e.xconfigure.height != window_height) {
|
||||||
recreate_window_pixmap(dpy, src_window_id, window_pixmap);
|
window_width = e.xconfigure.width;
|
||||||
// Resolution must be a multiple of two
|
window_height = e.xconfigure.height;
|
||||||
//video_stream->codec->width = window_pixmap.texture_width & ~1;
|
window_resize_timer = glfwGetTime();
|
||||||
//video_stream->codec->height = window_pixmap.texture_height & ~1;
|
window_resized = true;
|
||||||
|
}
|
||||||
cuGraphicsUnregisterResource(cuda_graphics_resource);
|
|
||||||
res = cuGraphicsGLRegisterImage(
|
|
||||||
&cuda_graphics_resource, window_pixmap.target_texture_id, GL_TEXTURE_2D,
|
|
||||||
CU_GRAPHICS_REGISTER_FLAGS_READ_ONLY);
|
|
||||||
if (res != CUDA_SUCCESS) {
|
|
||||||
const char *err_str;
|
|
||||||
cuGetErrorString(res, &err_str);
|
|
||||||
fprintf(stderr,
|
|
||||||
"Error: cuGraphicsGLRegisterImage failed, error %s, texture "
|
|
||||||
"id: %u\n",
|
|
||||||
err_str, window_pixmap.target_texture_id);
|
|
||||||
running = false;
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
res = cuGraphicsResourceSetMapFlags(
|
const double window_resize_timeout = 1.0; // 1 second
|
||||||
cuda_graphics_resource, CU_GRAPHICS_MAP_RESOURCE_FLAGS_READ_ONLY);
|
if(window_resized && glfwGetTime() - window_resize_timer >= window_resize_timeout) {
|
||||||
res = cuGraphicsMapResources(1, &cuda_graphics_resource, 0);
|
window_resized = false;
|
||||||
res = cuGraphicsSubResourceGetMappedArray(&mapped_array, cuda_graphics_resource, 0, 0);
|
fprintf(stderr, "Resize window!\n");
|
||||||
|
recreate_window_pixmap(dpy, src_window_id, window_pixmap);
|
||||||
|
// Resolution must be a multiple of two
|
||||||
|
//video_stream->codec->width = window_pixmap.texture_width & ~1;
|
||||||
|
//video_stream->codec->height = window_pixmap.texture_height & ~1;
|
||||||
|
|
||||||
av_frame_unref(frame);
|
cuGraphicsUnregisterResource(cuda_graphics_resource);
|
||||||
if (av_hwframe_get_buffer(video_stream->codec->hw_frames_ctx, frame, 0) < 0) {
|
res = cuGraphicsGLRegisterImage(
|
||||||
fprintf(stderr, "Error: av_hwframe_get_buffer failed\n");
|
&cuda_graphics_resource, window_pixmap.target_texture_id, GL_TEXTURE_2D,
|
||||||
running = false;
|
CU_GRAPHICS_REGISTER_FLAGS_READ_ONLY);
|
||||||
break;
|
if (res != CUDA_SUCCESS) {
|
||||||
|
const char *err_str;
|
||||||
|
cuGetErrorString(res, &err_str);
|
||||||
|
fprintf(stderr,
|
||||||
|
"Error: cuGraphicsGLRegisterImage failed, error %s, texture "
|
||||||
|
"id: %u\n",
|
||||||
|
err_str, window_pixmap.target_texture_id);
|
||||||
|
running = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
res = cuGraphicsResourceSetMapFlags(
|
||||||
|
cuda_graphics_resource, CU_GRAPHICS_MAP_RESOURCE_FLAGS_READ_ONLY);
|
||||||
|
res = cuGraphicsMapResources(1, &cuda_graphics_resource, 0);
|
||||||
|
res = cuGraphicsSubResourceGetMappedArray(&mapped_array, cuda_graphics_resource, 0, 0);
|
||||||
|
|
||||||
|
av_frame_unref(frame);
|
||||||
|
if (av_hwframe_get_buffer(video_stream->codec->hw_frames_ctx, frame, 0) < 0) {
|
||||||
|
fprintf(stderr, "Error: av_hwframe_get_buffer failed\n");
|
||||||
|
running = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
frame->pts = frame_count;
|
||||||
|
|
||||||
|
if(window_width < original_window_width)
|
||||||
|
frame->width = window_pixmap.texture_width & ~1;
|
||||||
|
else
|
||||||
|
frame->width = original_window_width;
|
||||||
|
|
||||||
|
if(window_height < original_window_height)
|
||||||
|
frame->height = window_pixmap.texture_height & ~1;
|
||||||
|
else
|
||||||
|
frame->height = original_window_height;
|
||||||
}
|
}
|
||||||
|
|
||||||
frame->pts = frame_count;
|
|
||||||
|
|
||||||
if(window_width < original_window_width)
|
|
||||||
frame->width = window_pixmap.texture_width & ~1;
|
|
||||||
else
|
|
||||||
frame->width = original_window_width;
|
|
||||||
|
|
||||||
if(window_height < original_window_height)
|
|
||||||
frame->height = window_pixmap.texture_height & ~1;
|
|
||||||
else
|
|
||||||
frame->height = original_window_height;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
++fps_counter;
|
++fps_counter;
|
||||||
@ -1078,33 +1180,64 @@ int main(int argc, char **argv) {
|
|||||||
if (frame_time_overflow >= 0.0) {
|
if (frame_time_overflow >= 0.0) {
|
||||||
frame_timer_start = time_now - frame_time_overflow;
|
frame_timer_start = time_now - frame_time_overflow;
|
||||||
|
|
||||||
|
bool frame_captured = true;
|
||||||
if(redraw) {
|
if(redraw) {
|
||||||
redraw = false;
|
redraw = false;
|
||||||
// TODO: Use a framebuffer instead. glCopyImageSubData requires
|
if(src_window_id) {
|
||||||
// opengl 4.2
|
// TODO: Use a framebuffer instead. glCopyImageSubData requires
|
||||||
glCopyImageSubData(
|
// opengl 4.2
|
||||||
window_pixmap.texture_id, GL_TEXTURE_2D, 0, 0, 0, 0,
|
glCopyImageSubData(
|
||||||
window_pixmap.target_texture_id, GL_TEXTURE_2D, 0, 0, 0, 0,
|
window_pixmap.texture_id, GL_TEXTURE_2D, 0, 0, 0, 0,
|
||||||
window_pixmap.texture_width, window_pixmap.texture_height, 1);
|
window_pixmap.target_texture_id, GL_TEXTURE_2D, 0, 0, 0, 0,
|
||||||
glfwSwapBuffers(window);
|
window_pixmap.texture_width, window_pixmap.texture_height, 1);
|
||||||
// int err = glGetError();
|
glfwSwapBuffers(window);
|
||||||
// fprintf(stderr, "error: %d\n", err);
|
// int err = glGetError();
|
||||||
|
// fprintf(stderr, "error: %d\n", err);
|
||||||
|
|
||||||
CUDA_MEMCPY2D memcpy_struct;
|
CUDA_MEMCPY2D memcpy_struct;
|
||||||
memcpy_struct.srcXInBytes = 0;
|
memcpy_struct.srcXInBytes = 0;
|
||||||
memcpy_struct.srcY = 0;
|
memcpy_struct.srcY = 0;
|
||||||
memcpy_struct.srcMemoryType = CUmemorytype::CU_MEMORYTYPE_ARRAY;
|
memcpy_struct.srcMemoryType = CUmemorytype::CU_MEMORYTYPE_ARRAY;
|
||||||
|
|
||||||
memcpy_struct.dstXInBytes = 0;
|
memcpy_struct.dstXInBytes = 0;
|
||||||
memcpy_struct.dstY = 0;
|
memcpy_struct.dstY = 0;
|
||||||
memcpy_struct.dstMemoryType = CUmemorytype::CU_MEMORYTYPE_DEVICE;
|
memcpy_struct.dstMemoryType = CUmemorytype::CU_MEMORYTYPE_DEVICE;
|
||||||
|
|
||||||
memcpy_struct.srcArray = mapped_array;
|
memcpy_struct.srcArray = mapped_array;
|
||||||
memcpy_struct.dstDevice = (CUdeviceptr)frame->data[0];
|
memcpy_struct.dstDevice = (CUdeviceptr)frame->data[0];
|
||||||
memcpy_struct.dstPitch = frame->linesize[0];
|
memcpy_struct.dstPitch = frame->linesize[0];
|
||||||
memcpy_struct.WidthInBytes = frame->width * 4;
|
memcpy_struct.WidthInBytes = frame->width * 4;
|
||||||
memcpy_struct.Height = frame->height;
|
memcpy_struct.Height = frame->height;
|
||||||
cuMemcpy2D(&memcpy_struct);
|
cuMemcpy2D(&memcpy_struct);
|
||||||
|
|
||||||
|
frame_captured = true;
|
||||||
|
} else {
|
||||||
|
uint32_t byte_size;
|
||||||
|
CUdeviceptr src_cu_device_ptr;
|
||||||
|
frame_captured = nv_fbc_library.capture(&src_cu_device_ptr, &byte_size);
|
||||||
|
if(frame_captured) {
|
||||||
|
// TODO: Is it possible to bypass this copy?
|
||||||
|
/*
|
||||||
|
CUDA_MEMCPY2D memcpy_struct;
|
||||||
|
memcpy_struct.srcXInBytes = 0;
|
||||||
|
memcpy_struct.srcY = 0;
|
||||||
|
memcpy_struct.srcMemoryType = CUmemorytype::CU_MEMORYTYPE_DEVICE;
|
||||||
|
|
||||||
|
memcpy_struct.dstXInBytes = 0;
|
||||||
|
memcpy_struct.dstY = 0;
|
||||||
|
memcpy_struct.dstMemoryType = CUmemorytype::CU_MEMORYTYPE_DEVICE;
|
||||||
|
|
||||||
|
memcpy_struct.srcDevice = src_cu_device_ptr;
|
||||||
|
memcpy_struct.dstDevice = (CUdeviceptr)frame->data[0];
|
||||||
|
memcpy_struct.dstPitch = frame->linesize[0];
|
||||||
|
memcpy_struct.WidthInBytes = frame->width * 4;
|
||||||
|
memcpy_struct.Height = frame->height;
|
||||||
|
cuMemcpy2D(&memcpy_struct);
|
||||||
|
*/
|
||||||
|
cuMemcpyDtoD((CUdeviceptr)frame->data[0], src_cu_device_ptr, byte_size);
|
||||||
|
//frame->data[0] = (uint8_t*)src_cu_device_ptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
// res = cuCtxPopCurrent(&old_ctx);
|
// res = cuCtxPopCurrent(&old_ctx);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1196,6 +1329,8 @@ int main(int argc, char **argv) {
|
|||||||
// avformat_free_context(av_format_context);
|
// avformat_free_context(av_format_context);
|
||||||
|
|
||||||
// cleanup_window_pixmap(dpy, window_pixmap);
|
// cleanup_window_pixmap(dpy, window_pixmap);
|
||||||
XCompositeUnredirectWindow(dpy, src_window_id, CompositeRedirectAutomatic);
|
if(dpy) {
|
||||||
XCloseDisplay(dpy);
|
XCompositeUnredirectWindow(dpy, src_window_id, CompositeRedirectAutomatic);
|
||||||
|
XCloseDisplay(dpy);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user