aboutsummaryrefslogtreecommitdiff
path: root/src/main.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/main.cpp')
-rw-r--r--src/main.cpp2493
1 files changed, 1666 insertions, 827 deletions
diff --git a/src/main.cpp b/src/main.cpp
index d044f7e..badcd94 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -2,11 +2,20 @@ extern "C" {
#include "../include/capture/nvfbc.h"
#include "../include/capture/xcomposite.h"
#include "../include/capture/kms.h"
+#ifdef GSR_PORTAL
+#include "../include/capture/portal.h"
+#include "../include/dbus.h"
+#endif
#include "../include/encoder/video/cuda.h"
#include "../include/encoder/video/vaapi.h"
+#include "../include/encoder/video/vulkan.h"
#include "../include/encoder/video/software.h"
+#include "../include/codec_query/nvenc.h"
+#include "../include/codec_query/vaapi.h"
+#include "../include/codec_query/vulkan.h"
#include "../include/egl.h"
#include "../include/utils.h"
+#include "../include/damage.h"
#include "../include/color_conversion.h"
}
@@ -23,6 +32,7 @@ extern "C" {
#include <sys/stat.h>
#include <unistd.h>
#include <sys/wait.h>
+#include <inttypes.h>
#include <libgen.h>
#include "../include/sound.hpp"
@@ -35,6 +45,7 @@ extern "C" {
#include <libswresample/swresample.h>
#include <libavutil/avutil.h>
#include <libavutil/time.h>
+#include <libavutil/mastering_display_metadata.h>
#include <libavfilter/avfilter.h>
#include <libavfilter/buffersink.h>
#include <libavfilter/buffersrc.h>
@@ -43,6 +54,10 @@ extern "C" {
#include <deque>
#include <future>
+#ifndef GSR_VERSION
+#define GSR_VERSION "unknown"
+#endif
+
// TODO: If options are not supported then they are returned (allocated) in the options. This should be free'd.
// TODO: Remove LIBAVUTIL_VERSION_MAJOR checks in the future when ubuntu, pop os LTS etc update ffmpeg to >= 5.0
@@ -85,8 +100,14 @@ enum class VideoCodec {
H264,
HEVC,
HEVC_HDR,
+ HEVC_10BIT,
AV1,
- AV1_HDR
+ AV1_HDR,
+ AV1_10BIT,
+ VP8,
+ VP9,
+ H264_VULKAN,
+ HEVC_VULKAN
};
enum class AudioCodec {
@@ -106,6 +127,12 @@ enum class FramerateMode {
CONTENT
};
+enum class BitrateMode {
+ QP,
+ VBR,
+ CBR
+};
+
static int x11_error_handler(Display*, XErrorEvent*) {
return 0;
}
@@ -115,9 +142,69 @@ static int x11_io_error_handler(Display*) {
}
static bool video_codec_is_hdr(VideoCodec video_codec) {
+ // TODO: Vulkan
+ switch(video_codec) {
+ case VideoCodec::HEVC_HDR:
+ case VideoCodec::AV1_HDR:
+ return true;
+ default:
+ return false;
+ }
+}
+
+static VideoCodec hdr_video_codec_to_sdr_video_codec(VideoCodec video_codec) {
+ // TODO: Vulkan
+ switch(video_codec) {
+ case VideoCodec::HEVC_HDR:
+ return VideoCodec::HEVC;
+ case VideoCodec::AV1_HDR:
+ return VideoCodec::AV1;
+ default:
+ return video_codec;
+ }
+}
+
+static gsr_color_depth video_codec_to_bit_depth(VideoCodec video_codec) {
+ // TODO: Vulkan
switch(video_codec) {
case VideoCodec::HEVC_HDR:
+ case VideoCodec::HEVC_10BIT:
+ case VideoCodec::AV1_HDR:
+ case VideoCodec::AV1_10BIT:
+ return GSR_COLOR_DEPTH_10_BITS;
+ default:
+ return GSR_COLOR_DEPTH_8_BITS;
+ }
+}
+
+// static bool video_codec_is_hevc(VideoCodec video_codec) {
+// TODO: Vulkan
+// switch(video_codec) {
+// case VideoCodec::HEVC:
+// case VideoCodec::HEVC_HDR:
+// case VideoCodec::HEVC_10BIT:
+// return true;
+// default:
+// return false;
+// }
+// }
+
+static bool video_codec_is_av1(VideoCodec video_codec) {
+ // TODO: Vulkan
+ switch(video_codec) {
+ case VideoCodec::AV1:
case VideoCodec::AV1_HDR:
+ case VideoCodec::AV1_10BIT:
+ return true;
+ default:
+ return false;
+ }
+}
+
+static bool video_codec_is_vulkan(VideoCodec video_codec) {
+ switch(video_codec) {
+ case VideoCodec::H264_VULKAN:
+ case VideoCodec::HEVC_VULKAN:
return true;
default:
return false;
@@ -223,7 +310,8 @@ static AVCodecID audio_codec_get_id(AudioCodec audio_codec) {
return AV_CODEC_ID_AAC;
}
-static AVSampleFormat audio_codec_get_sample_format(AudioCodec audio_codec, const AVCodec *codec, bool mix_audio) {
+static AVSampleFormat audio_codec_get_sample_format(AVCodecContext *audio_codec_context, AudioCodec audio_codec, const AVCodec *codec, bool mix_audio) {
+ (void)audio_codec_context;
switch(audio_codec) {
case AudioCodec::AAC: {
return AV_SAMPLE_FMT_FLTP;
@@ -232,13 +320,32 @@ static AVSampleFormat audio_codec_get_sample_format(AudioCodec audio_codec, cons
bool supports_s16 = false;
bool supports_flt = false;
- for(size_t i = 0; codec->sample_fmts && codec->sample_fmts[i] != -1; ++i) {
+ #if LIBAVCODEC_VERSION_INT < AV_VERSION_INT(61, 15, 0)
+ for(size_t i = 0; codec->sample_fmts && codec->sample_fmts[i] != AV_SAMPLE_FMT_NONE; ++i) {
if(codec->sample_fmts[i] == AV_SAMPLE_FMT_S16) {
supports_s16 = true;
} else if(codec->sample_fmts[i] == AV_SAMPLE_FMT_FLT) {
supports_flt = true;
}
}
+ #else
+ const enum AVSampleFormat *sample_fmts = NULL;
+ if(avcodec_get_supported_config(audio_codec_context, codec, AV_CODEC_CONFIG_SAMPLE_FORMAT, 0, (const void**)&sample_fmts, NULL) >= 0) {
+ if(sample_fmts) {
+ for(size_t i = 0; sample_fmts[i] != AV_SAMPLE_FMT_NONE; ++i) {
+ if(sample_fmts[i] == AV_SAMPLE_FMT_S16) {
+ supports_s16 = true;
+ } else if(sample_fmts[i] == AV_SAMPLE_FMT_FLT) {
+ supports_flt = true;
+ }
+ }
+ } else {
+ // What a dumb API. It returns NULL if all formats are supported
+ supports_s16 = true;
+ supports_flt = true;
+ }
+ }
+ #endif
// Amix only works with float audio
if(mix_audio)
@@ -295,7 +402,7 @@ static AVSampleFormat audio_format_to_sample_format(const AudioFormat audio_form
return AV_SAMPLE_FMT_S16;
}
-static AVCodecContext* create_audio_codec_context(int fps, AudioCodec audio_codec, bool mix_audio, int audio_bitrate) {
+static AVCodecContext* create_audio_codec_context(int fps, AudioCodec audio_codec, bool mix_audio, int64_t audio_bitrate) {
(void)fps;
const AVCodec *codec = avcodec_find_encoder(audio_codec_get_id(audio_codec));
if (!codec) {
@@ -307,7 +414,7 @@ static AVCodecContext* create_audio_codec_context(int fps, AudioCodec audio_code
assert(codec->type == AVMEDIA_TYPE_AUDIO);
codec_context->codec_id = codec->id;
- codec_context->sample_fmt = audio_codec_get_sample_format(audio_codec, codec, mix_audio);
+ codec_context->sample_fmt = audio_codec_get_sample_format(codec_context, audio_codec, codec, mix_audio);
codec_context->bit_rate = audio_bitrate == 0 ? audio_codec_get_get_bitrate(audio_codec) : audio_bitrate;
codec_context->sample_rate = AUDIO_SAMPLE_RATE;
if(audio_codec == AudioCodec::AAC)
@@ -327,10 +434,62 @@ static AVCodecContext* create_audio_codec_context(int fps, AudioCodec audio_code
return codec_context;
}
+static int vbr_get_quality_parameter(AVCodecContext *codec_context, VideoQuality video_quality, bool hdr) {
+ // 8 bit / 10 bit = 80%
+ const float qp_multiply = hdr ? 8.0f/10.0f : 1.0f;
+ if(codec_context->codec_id == AV_CODEC_ID_AV1) {
+ switch(video_quality) {
+ case VideoQuality::MEDIUM:
+ return 160 * qp_multiply;
+ case VideoQuality::HIGH:
+ return 130 * qp_multiply;
+ case VideoQuality::VERY_HIGH:
+ return 110 * qp_multiply;
+ case VideoQuality::ULTRA:
+ return 90 * qp_multiply;
+ }
+ } else if(codec_context->codec_id == AV_CODEC_ID_H264) {
+ switch(video_quality) {
+ case VideoQuality::MEDIUM:
+ return 35 * qp_multiply;
+ case VideoQuality::HIGH:
+ return 30 * qp_multiply;
+ case VideoQuality::VERY_HIGH:
+ return 25 * qp_multiply;
+ case VideoQuality::ULTRA:
+ return 22 * qp_multiply;
+ }
+ } else if(codec_context->codec_id == AV_CODEC_ID_HEVC) {
+ switch(video_quality) {
+ case VideoQuality::MEDIUM:
+ return 35 * qp_multiply;
+ case VideoQuality::HIGH:
+ return 30 * qp_multiply;
+ case VideoQuality::VERY_HIGH:
+ return 25 * qp_multiply;
+ case VideoQuality::ULTRA:
+ return 22 * qp_multiply;
+ }
+ } else if(codec_context->codec_id == AV_CODEC_ID_VP8 || codec_context->codec_id == AV_CODEC_ID_VP9) {
+ switch(video_quality) {
+ case VideoQuality::MEDIUM:
+ return 35 * qp_multiply;
+ case VideoQuality::HIGH:
+ return 30 * qp_multiply;
+ case VideoQuality::VERY_HIGH:
+ return 25 * qp_multiply;
+ case VideoQuality::ULTRA:
+ return 22 * qp_multiply;
+ }
+ }
+ assert(false);
+ return 22 * qp_multiply;
+}
+
static AVCodecContext *create_video_codec_context(AVPixelFormat pix_fmt,
VideoQuality video_quality,
int fps, const AVCodec *codec, bool low_latency, gsr_gpu_vendor vendor, FramerateMode framerate_mode,
- bool hdr, gsr_color_range color_range, float keyint) {
+ bool hdr, gsr_color_range color_range, float keyint, bool use_software_video_encoder, BitrateMode bitrate_mode, VideoCodec video_codec, int64_t bitrate) {
AVCodecContext *codec_context = avcodec_alloc_context3(codec);
@@ -372,69 +531,97 @@ static AVCodecContext *create_video_codec_context(AVPixelFormat pix_fmt,
}
//codec_context->chroma_sample_location = AVCHROMA_LOC_CENTER;
if(codec->id == AV_CODEC_ID_HEVC)
- codec_context->codec_tag = MKTAG('h', 'v', 'c', '1');
- switch(video_quality) {
- case VideoQuality::MEDIUM:
- //codec_context->qmin = 35;
- //codec_context->qmax = 35;
- codec_context->bit_rate = 100000;//4500000 + (codec_context->width * codec_context->height)*0.75;
- break;
- case VideoQuality::HIGH:
- //codec_context->qmin = 34;
- //codec_context->qmax = 34;
- codec_context->bit_rate = 100000;//10000000-9000000 + (codec_context->width * codec_context->height)*0.75;
- break;
- case VideoQuality::VERY_HIGH:
- //codec_context->qmin = 28;
- //codec_context->qmax = 28;
- codec_context->bit_rate = 100000;//10000000-9000000 + (codec_context->width * codec_context->height)*0.75;
- break;
- case VideoQuality::ULTRA:
- //codec_context->qmin = 22;
- //codec_context->qmax = 22;
- codec_context->bit_rate = 100000;//10000000-9000000 + (codec_context->width * codec_context->height)*0.75;
- break;
- }
- //codec_context->profile = FF_PROFILE_H264_MAIN;
- if (codec_context->codec_id == AV_CODEC_ID_MPEG1VIDEO)
- codec_context->mb_decision = 2;
-
- // stream->time_base = codec_context->time_base;
- // codec_context->ticks_per_frame = 30;
- //av_opt_set(codec_context->priv_data, "tune", "hq", 0);
- // TODO: Do this for better file size? also allows setting qmin, qmax per frame? which can then be used to dynamically set bitrate to reduce quality
- // if live streaming is slow or if the users harddrive is cant handle writing megabytes of data per second.
- #if 0
- char qmin_str[32];
- snprintf(qmin_str, sizeof(qmin_str), "%d", codec_context->qmin);
-
- char qmax_str[32];
- snprintf(qmax_str, sizeof(qmax_str), "%d", codec_context->qmax);
-
- av_opt_set(codec_context->priv_data, "cq", qmax_str, 0);
- av_opt_set(codec_context->priv_data, "rc", "vbr", 0);
- av_opt_set(codec_context->priv_data, "qmin", qmin_str, 0);
- av_opt_set(codec_context->priv_data, "qmax", qmax_str, 0);
- codec_context->bit_rate = 0;
- #endif
-
- // 8 bit / 10 bit = 80%, and increase it even more
- const float quality_multiply = hdr ? (8.0f/10.0f * 0.7f) : 1.0f;
- if(vendor != GSR_GPU_VENDOR_NVIDIA) {
+ codec_context->codec_tag = MKTAG('h', 'v', 'c', '1'); // QuickTime on MacOS requires this or the video wont be playable
+
+ if(bitrate_mode == BitrateMode::CBR) {
+ codec_context->bit_rate = bitrate;
+ codec_context->rc_max_rate = codec_context->bit_rate;
+ codec_context->rc_min_rate = codec_context->bit_rate;
+ codec_context->rc_buffer_size = codec_context->bit_rate;//codec_context->bit_rate / 10;
+ codec_context->rc_initial_buffer_occupancy = codec_context->bit_rate;//codec_context->bit_rate * 1000;
+ } else if(bitrate_mode == BitrateMode::VBR) {
+ const int quality = vbr_get_quality_parameter(codec_context, video_quality, hdr);
switch(video_quality) {
case VideoQuality::MEDIUM:
- codec_context->global_quality = 180 * quality_multiply;
+ codec_context->qmin = quality;
+ codec_context->qmax = quality;
+ codec_context->bit_rate = 100000;//4500000 + (codec_context->width * codec_context->height)*0.75;
break;
case VideoQuality::HIGH:
- codec_context->global_quality = 140 * quality_multiply;
+ codec_context->qmin = quality;
+ codec_context->qmax = quality;
+ codec_context->bit_rate = 100000;//10000000-9000000 + (codec_context->width * codec_context->height)*0.75;
break;
case VideoQuality::VERY_HIGH:
- codec_context->global_quality = 120 * quality_multiply;
+ codec_context->qmin = quality;
+ codec_context->qmax = quality;
+ codec_context->bit_rate = 100000;//10000000-9000000 + (codec_context->width * codec_context->height)*0.75;
break;
case VideoQuality::ULTRA:
- codec_context->global_quality = 100 * quality_multiply;
+ codec_context->qmin = quality;
+ codec_context->qmax = quality;
+ codec_context->bit_rate = 100000;//10000000-9000000 + (codec_context->width * codec_context->height)*0.75;
break;
}
+
+ codec_context->rc_max_rate = codec_context->bit_rate;
+ codec_context->rc_min_rate = codec_context->bit_rate;
+ codec_context->rc_buffer_size = codec_context->bit_rate;//codec_context->bit_rate / 10;
+ codec_context->rc_initial_buffer_occupancy = 100000;//codec_context->bit_rate * 1000;
+ }
+ //codec_context->profile = FF_PROFILE_H264_MAIN;
+ if (codec_context->codec_id == AV_CODEC_ID_MPEG1VIDEO)
+ codec_context->mb_decision = 2;
+
+ if(!use_software_video_encoder && vendor != GSR_GPU_VENDOR_NVIDIA) {
+ // 8 bit / 10 bit = 80%, and increase it even more
+ const float quality_multiply = hdr ? (8.0f/10.0f * 0.7f) : 1.0f;
+ if(codec_context->codec_id == AV_CODEC_ID_AV1 || codec_context->codec_id == AV_CODEC_ID_H264 || codec_context->codec_id == AV_CODEC_ID_HEVC) {
+ switch(video_quality) {
+ case VideoQuality::MEDIUM:
+ codec_context->global_quality = 150 * quality_multiply;
+ break;
+ case VideoQuality::HIGH:
+ codec_context->global_quality = 120 * quality_multiply;
+ break;
+ case VideoQuality::VERY_HIGH:
+ codec_context->global_quality = 100 * quality_multiply;
+ break;
+ case VideoQuality::ULTRA:
+ codec_context->global_quality = 90 * quality_multiply;
+ break;
+ }
+ } else if(codec_context->codec_id == AV_CODEC_ID_VP8) {
+ switch(video_quality) {
+ case VideoQuality::MEDIUM:
+ codec_context->global_quality = 35 * quality_multiply;
+ break;
+ case VideoQuality::HIGH:
+ codec_context->global_quality = 30 * quality_multiply;
+ break;
+ case VideoQuality::VERY_HIGH:
+ codec_context->global_quality = 20 * quality_multiply;
+ break;
+ case VideoQuality::ULTRA:
+ codec_context->global_quality = 10 * quality_multiply;
+ break;
+ }
+ } else if(codec_context->codec_id == AV_CODEC_ID_VP9) {
+ switch(video_quality) {
+ case VideoQuality::MEDIUM:
+ codec_context->global_quality = 35 * quality_multiply;
+ break;
+ case VideoQuality::HIGH:
+ codec_context->global_quality = 30 * quality_multiply;
+ break;
+ case VideoQuality::VERY_HIGH:
+ codec_context->global_quality = 20 * quality_multiply;
+ break;
+ case VideoQuality::ULTRA:
+ codec_context->global_quality = 10 * quality_multiply;
+ break;
+ }
+ }
}
av_opt_set_int(codec_context->priv_data, "b_ref_mode", 0, 0);
@@ -443,158 +630,46 @@ static AVCodecContext *create_video_codec_context(AVPixelFormat pix_fmt,
if(vendor != GSR_GPU_VENDOR_NVIDIA) {
// TODO: More options, better options
//codec_context->bit_rate = codec_context->width * codec_context->height;
- av_opt_set(codec_context->priv_data, "rc_mode", "CQP", 0);
+ switch(bitrate_mode) {
+ case BitrateMode::QP: {
+ if(video_codec_is_vulkan(video_codec))
+ av_opt_set(codec_context->priv_data, "rc_mode", "cqp", 0);
+ else if(vendor == GSR_GPU_VENDOR_NVIDIA)
+ av_opt_set(codec_context->priv_data, "rc", "constqp", 0);
+ else
+ av_opt_set(codec_context->priv_data, "rc_mode", "CQP", 0);
+ break;
+ }
+ case BitrateMode::VBR: {
+ if(video_codec_is_vulkan(video_codec))
+ av_opt_set(codec_context->priv_data, "rc_mode", "vbr", 0);
+ else if(vendor == GSR_GPU_VENDOR_NVIDIA)
+ av_opt_set(codec_context->priv_data, "rc", "vbr", 0);
+ else
+ av_opt_set(codec_context->priv_data, "rc_mode", "VBR", 0);
+ break;
+ }
+ case BitrateMode::CBR: {
+ if(video_codec_is_vulkan(video_codec))
+ av_opt_set(codec_context->priv_data, "rc_mode", "cbr", 0);
+ else if(vendor == GSR_GPU_VENDOR_NVIDIA)
+ av_opt_set(codec_context->priv_data, "rc", "cbr", 0);
+ else
+ av_opt_set(codec_context->priv_data, "rc_mode", "CBR", 0);
+ break;
+ }
+ }
//codec_context->global_quality = 4;
//codec_context->compression_level = 2;
}
//av_opt_set(codec_context->priv_data, "bsf", "hevc_metadata=colour_primaries=9:transfer_characteristics=16:matrix_coefficients=9", 0);
- //codec_context->rc_max_rate = codec_context->bit_rate;
- //codec_context->rc_min_rate = codec_context->bit_rate;
- //codec_context->rc_buffer_size = codec_context->bit_rate / 10;
- // TODO: Do this when not using cqp
- //codec_context->rc_initial_buffer_occupancy = codec_context->bit_rate * 1000;
-
codec_context->flags |= AV_CODEC_FLAG_GLOBAL_HEADER;
return codec_context;
}
-static bool vaapi_create_codec_context(AVCodecContext *video_codec_context, const char *card_path) {
- char render_path[128];
- if(!gsr_card_path_get_render_path(card_path, render_path)) {
- fprintf(stderr, "gsr error: failed to get /dev/dri/renderDXXX file from %s\n", card_path);
- return false;
- }
-
- AVBufferRef *device_ctx;
- if(av_hwdevice_ctx_create(&device_ctx, AV_HWDEVICE_TYPE_VAAPI, render_path, NULL, 0) < 0) {
- fprintf(stderr, "Error: Failed to create hardware device context\n");
- return false;
- }
-
- AVBufferRef *frame_context = av_hwframe_ctx_alloc(device_ctx);
- if(!frame_context) {
- fprintf(stderr, "Error: Failed to create hwframe context\n");
- av_buffer_unref(&device_ctx);
- return false;
- }
-
- AVHWFramesContext *hw_frame_context =
- (AVHWFramesContext *)frame_context->data;
- hw_frame_context->width = video_codec_context->width;
- hw_frame_context->height = video_codec_context->height;
- hw_frame_context->sw_format = AV_PIX_FMT_NV12;
- hw_frame_context->format = video_codec_context->pix_fmt;
- hw_frame_context->device_ref = device_ctx;
- hw_frame_context->device_ctx = (AVHWDeviceContext*)device_ctx->data;
-
- //hw_frame_context->initial_pool_size = 1;
-
- if (av_hwframe_ctx_init(frame_context) < 0) {
- fprintf(stderr, "Error: Failed to initialize hardware frame context "
- "(note: ffmpeg version needs to be > 4.0)\n");
- av_buffer_unref(&device_ctx);
- //av_buffer_unref(&frame_context);
- return false;
- }
-
- video_codec_context->hw_device_ctx = av_buffer_ref(device_ctx);
- video_codec_context->hw_frames_ctx = av_buffer_ref(frame_context);
- return true;
-}
-
-static bool check_if_codec_valid_for_hardware(const AVCodec *codec, gsr_gpu_vendor vendor, const char *card_path) {
- // 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(vendor == GSR_GPU_VENDOR_NVIDIA ? AV_PIX_FMT_YUV420P : AV_PIX_FMT_VAAPI, VideoQuality::VERY_HIGH, 60, codec, false, vendor, FramerateMode::CONSTANT, false, GSR_COLOR_RANGE_LIMITED, 2);
- if(!codec_context)
- return false;
-
- codec_context->width = 512;
- codec_context->height = 512;
-
- if(vendor != GSR_GPU_VENDOR_NVIDIA) {
- if(!vaapi_create_codec_context(codec_context, card_path)) {
- avcodec_free_context(&codec_context);
- return false;
- }
- }
-
- bool success = false;
- success = avcodec_open2(codec_context, codec_context->codec, NULL) == 0;
- if(codec_context->hw_device_ctx)
- av_buffer_unref(&codec_context->hw_device_ctx);
- if(codec_context->hw_frames_ctx)
- av_buffer_unref(&codec_context->hw_frames_ctx);
- avcodec_free_context(&codec_context);
- return success;
-}
-
-static const AVCodec* find_h264_software_encoder() {
- return avcodec_find_encoder_by_name("libx264");
-}
-
-static const AVCodec* find_h264_encoder(gsr_gpu_vendor vendor, const char *card_path) {
- const AVCodec *codec = avcodec_find_encoder_by_name(vendor == GSR_GPU_VENDOR_NVIDIA ? "h264_nvenc" : "h264_vaapi");
- if(!codec)
- codec = avcodec_find_encoder_by_name(vendor == GSR_GPU_VENDOR_NVIDIA ? "nvenc_h264" : "vaapi_h264");
-
- if(!codec)
- return nullptr;
-
- static bool checked = false;
- static bool checked_success = true;
- if(!checked) {
- checked = true;
- if(!check_if_codec_valid_for_hardware(codec, vendor, card_path))
- checked_success = false;
- }
- return checked_success ? codec : nullptr;
-}
-
-static const AVCodec* find_hevc_encoder(gsr_gpu_vendor vendor, const char *card_path) {
- const AVCodec *codec = avcodec_find_encoder_by_name(vendor == GSR_GPU_VENDOR_NVIDIA ? "hevc_nvenc" : "hevc_vaapi");
- if(!codec)
- codec = avcodec_find_encoder_by_name(vendor == GSR_GPU_VENDOR_NVIDIA ? "nvenc_hevc" : "vaapi_hevc");
-
- if(!codec)
- return nullptr;
-
- static bool checked = false;
- static bool checked_success = true;
- if(!checked) {
- checked = true;
- if(!check_if_codec_valid_for_hardware(codec, vendor, card_path))
- checked_success = false;
- }
- return checked_success ? codec : nullptr;
-}
-
-static const AVCodec* find_av1_encoder(gsr_gpu_vendor vendor, const char *card_path) {
- // Workaround bug with av1 nvidia in older ffmpeg versions that causes the whole application to crash
- // when avcodec_open2 is opened with av1_nvenc
- if(vendor == GSR_GPU_VENDOR_NVIDIA && LIBAVCODEC_BUILD < AV_VERSION_INT(60, 30, 100)) {
- return nullptr;
- }
-
- const AVCodec *codec = avcodec_find_encoder_by_name(vendor == GSR_GPU_VENDOR_NVIDIA ? "av1_nvenc" : "av1_vaapi");
- if(!codec)
- codec = avcodec_find_encoder_by_name(vendor == GSR_GPU_VENDOR_NVIDIA ? "nvenc_av1" : "vaapi_av1");
-
- if(!codec)
- return nullptr;
-
- static bool checked = false;
- static bool checked_success = true;
- if(!checked) {
- checked = true;
- if(!check_if_codec_valid_for_hardware(codec, vendor, card_path))
- checked_success = false;
- }
- return checked_success ? codec : nullptr;
-}
-
static void open_audio(AVCodecContext *audio_codec_context) {
AVDictionary *options = nullptr;
av_dict_set(&options, "strict", "experimental", 0);
@@ -633,64 +708,110 @@ static AVFrame* create_audio_frame(AVCodecContext *audio_codec_context) {
return frame;
}
-static void open_video_software(AVCodecContext *codec_context, VideoQuality video_quality, PixelFormat pixel_format, bool hdr) {
- (void)pixel_format; // TODO:
- AVDictionary *options = nullptr;
+static void dict_set_profile(AVCodecContext *codec_context, gsr_gpu_vendor vendor, gsr_color_depth color_depth, AVDictionary **options) {
+ #if LIBAVCODEC_VERSION_INT < AV_VERSION_INT(61, 17, 100)
+ if(codec_context->codec_id == AV_CODEC_ID_H264) {
+ // TODO: Only for vaapi
+ //if(color_depth == GSR_COLOR_DEPTH_10_BITS)
+ // av_dict_set(options, "profile", "high10", 0);
+ //else
+ av_dict_set(options, "profile", "high", 0);
+ } else if(codec_context->codec_id == AV_CODEC_ID_AV1) {
+ if(vendor == GSR_GPU_VENDOR_NVIDIA) {
+ if(color_depth == GSR_COLOR_DEPTH_10_BITS)
+ av_dict_set_int(options, "highbitdepth", 1, 0);
+ } else {
+ av_dict_set(options, "profile", "main", 0); // TODO: use professional instead?
+ }
+ } else if(codec_context->codec_id == AV_CODEC_ID_HEVC) {
+ if(color_depth == GSR_COLOR_DEPTH_10_BITS)
+ av_dict_set(options, "profile", "main10", 0);
+ else
+ av_dict_set(options, "profile", "main", 0);
+ }
+ #else
+ if(codec_context->codec_id == AV_CODEC_ID_H264) {
+ // TODO: Only for vaapi
+ //if(color_depth == GSR_COLOR_DEPTH_10_BITS)
+ // av_dict_set_int(options, "profile", AV_PROFILE_H264_HIGH_10, 0);
+ //else
+ av_dict_set_int(options, "profile", AV_PROFILE_H264_HIGH, 0);
+ } else if(codec_context->codec_id == AV_CODEC_ID_AV1) {
+ if(vendor == GSR_GPU_VENDOR_NVIDIA) {
+ if(color_depth == GSR_COLOR_DEPTH_10_BITS)
+ av_dict_set_int(options, "highbitdepth", 1, 0);
+ } else {
+ av_dict_set_int(options, "profile", AV_PROFILE_AV1_MAIN, 0); // TODO: use professional instead?
+ }
+ } else if(codec_context->codec_id == AV_CODEC_ID_HEVC) {
+ if(color_depth == GSR_COLOR_DEPTH_10_BITS)
+ av_dict_set_int(options, "profile", AV_PROFILE_HEVC_MAIN_10, 0);
+ else
+ av_dict_set_int(options, "profile", AV_PROFILE_HEVC_MAIN, 0);
+ }
+ #endif
+}
+static void video_software_set_qp(AVCodecContext *codec_context, VideoQuality video_quality, bool hdr, AVDictionary **options) {
+ // 8 bit / 10 bit = 80%
const float qp_multiply = hdr ? 8.0f/10.0f : 1.0f;
if(codec_context->codec_id == AV_CODEC_ID_AV1) {
switch(video_quality) {
case VideoQuality::MEDIUM:
- av_dict_set_int(&options, "qp", 37 * qp_multiply, 0);
+ av_dict_set_int(options, "qp", 35 * qp_multiply, 0);
break;
case VideoQuality::HIGH:
- av_dict_set_int(&options, "qp", 32 * qp_multiply, 0);
+ av_dict_set_int(options, "qp", 30 * qp_multiply, 0);
break;
case VideoQuality::VERY_HIGH:
- av_dict_set_int(&options, "qp", 28 * qp_multiply, 0);
+ av_dict_set_int(options, "qp", 25 * qp_multiply, 0);
break;
case VideoQuality::ULTRA:
- av_dict_set_int(&options, "qp", 24 * qp_multiply, 0);
+ av_dict_set_int(options, "qp", 22 * qp_multiply, 0);
break;
}
} else if(codec_context->codec_id == AV_CODEC_ID_H264) {
switch(video_quality) {
case VideoQuality::MEDIUM:
- av_dict_set_int(&options, "qp", 34 * qp_multiply, 0);
+ av_dict_set_int(options, "qp", 34 * qp_multiply, 0);
break;
case VideoQuality::HIGH:
- av_dict_set_int(&options, "qp", 30 * qp_multiply, 0);
+ av_dict_set_int(options, "qp", 30 * qp_multiply, 0);
break;
case VideoQuality::VERY_HIGH:
- av_dict_set_int(&options, "qp", 26 * qp_multiply, 0);
+ av_dict_set_int(options, "qp", 23 * qp_multiply, 0);
break;
case VideoQuality::ULTRA:
- av_dict_set_int(&options, "qp", 22 * qp_multiply, 0);
+ av_dict_set_int(options, "qp", 20 * qp_multiply, 0);
break;
}
} else {
switch(video_quality) {
case VideoQuality::MEDIUM:
- av_dict_set_int(&options, "qp", 37 * qp_multiply, 0);
+ av_dict_set_int(options, "qp", 35 * qp_multiply, 0);
break;
case VideoQuality::HIGH:
- av_dict_set_int(&options, "qp", 32 * qp_multiply, 0);
+ av_dict_set_int(options, "qp", 30 * qp_multiply, 0);
break;
case VideoQuality::VERY_HIGH:
- av_dict_set_int(&options, "qp", 28 * qp_multiply, 0);
+ av_dict_set_int(options, "qp", 25 * qp_multiply, 0);
break;
case VideoQuality::ULTRA:
- av_dict_set_int(&options, "qp", 24 * qp_multiply, 0);
+ av_dict_set_int(options, "qp", 22 * qp_multiply, 0);
break;
}
}
+}
+
+static void open_video_software(AVCodecContext *codec_context, VideoQuality video_quality, PixelFormat pixel_format, bool hdr, gsr_color_depth color_depth, BitrateMode bitrate_mode) {
+ (void)pixel_format; // TODO:
+ AVDictionary *options = nullptr;
+
+ if(bitrate_mode == BitrateMode::QP)
+ video_software_set_qp(codec_context, video_quality, hdr, &options);
av_dict_set(&options, "preset", "medium", 0);
- if(hdr) {
- av_dict_set(&options, "profile", "high10", 0);
- } else {
- av_dict_set(&options, "profile", "high", 0);
- }
+ dict_set_profile(codec_context, GSR_GPU_VENDOR_INTEL, color_depth, &options);
// TODO: If streaming or piping output set this to zerolatency
av_dict_set(&options, "tune", "fastdecode", 0);
@@ -707,131 +828,103 @@ static void open_video_software(AVCodecContext *codec_context, VideoQuality vide
}
}
-static void open_video_hardware(AVCodecContext *codec_context, VideoQuality video_quality, bool very_old_gpu, gsr_gpu_vendor vendor, PixelFormat pixel_format, bool hdr) {
- (void)very_old_gpu;
- AVDictionary *options = nullptr;
+static void video_set_rc(VideoCodec video_codec, gsr_gpu_vendor vendor, BitrateMode bitrate_mode, AVDictionary **options) {
+ switch(bitrate_mode) {
+ case BitrateMode::QP: {
+ if(video_codec_is_vulkan(video_codec))
+ av_dict_set(options, "rc_mode", "cqp", 0);
+ else if(vendor == GSR_GPU_VENDOR_NVIDIA)
+ av_dict_set(options, "rc", "constqp", 0);
+ else
+ av_dict_set(options, "rc_mode", "CQP", 0);
+ break;
+ }
+ case BitrateMode::VBR: {
+ if(video_codec_is_vulkan(video_codec))
+ av_dict_set(options, "rc_mode", "vbr", 0);
+ else if(vendor == GSR_GPU_VENDOR_NVIDIA)
+ av_dict_set(options, "rc", "vbr", 0);
+ else
+ av_dict_set(options, "rc_mode", "VBR", 0);
+ break;
+ }
+ case BitrateMode::CBR: {
+ if(video_codec_is_vulkan(video_codec))
+ av_dict_set(options, "rc_mode", "cbr", 0);
+ else if(vendor == GSR_GPU_VENDOR_NVIDIA)
+ av_dict_set(options, "rc", "cbr", 0);
+ else
+ av_dict_set(options, "rc_mode", "CBR", 0);
+ break;
+ }
+ }
+}
+
+static void video_hardware_set_qp(AVCodecContext *codec_context, VideoQuality video_quality, gsr_gpu_vendor vendor, bool hdr, AVDictionary **options) {
// 8 bit / 10 bit = 80%
const float qp_multiply = hdr ? 8.0f/10.0f : 1.0f;
if(vendor == GSR_GPU_VENDOR_NVIDIA) {
- // Disable setting preset since some nvidia gpus cant handle it nicely and greatly reduce encoding performance (from more than 60 fps to less than 45 fps) (such as Nvidia RTX A2000)
- #if 0
- bool supports_p4 = false;
- bool supports_p5 = false;
-
- const AVOption *opt = nullptr;
- while((opt = av_opt_next(codec_context->priv_data, opt))) {
- if(opt->type == AV_OPT_TYPE_CONST) {
- if(strcmp(opt->name, "p4") == 0)
- supports_p4 = true;
- else if(strcmp(opt->name, "p5") == 0)
- supports_p5 = true;
- }
- }
- #endif
-
+ // TODO: Test if these should be in the same range as vaapi
if(codec_context->codec_id == AV_CODEC_ID_AV1) {
switch(video_quality) {
case VideoQuality::MEDIUM:
- av_dict_set_int(&options, "qp", 37 * qp_multiply, 0);
+ av_dict_set_int(options, "qp", 35 * qp_multiply, 0);
break;
case VideoQuality::HIGH:
- av_dict_set_int(&options, "qp", 32 * qp_multiply, 0);
+ av_dict_set_int(options, "qp", 30 * qp_multiply, 0);
break;
case VideoQuality::VERY_HIGH:
- av_dict_set_int(&options, "qp", 28 * qp_multiply, 0);
+ av_dict_set_int(options, "qp", 25 * qp_multiply, 0);
break;
case VideoQuality::ULTRA:
- av_dict_set_int(&options, "qp", 24 * qp_multiply, 0);
+ av_dict_set_int(options, "qp", 22 * qp_multiply, 0);
break;
}
} else if(codec_context->codec_id == AV_CODEC_ID_H264) {
switch(video_quality) {
case VideoQuality::MEDIUM:
- av_dict_set_int(&options, "qp", 34 * qp_multiply, 0);
+ av_dict_set_int(options, "qp", 34 * qp_multiply, 0);
break;
case VideoQuality::HIGH:
- av_dict_set_int(&options, "qp", 30 * qp_multiply, 0);
+ av_dict_set_int(options, "qp", 30 * qp_multiply, 0);
break;
case VideoQuality::VERY_HIGH:
- av_dict_set_int(&options, "qp", 26 * qp_multiply, 0);
+ av_dict_set_int(options, "qp", 23 * qp_multiply, 0);
break;
case VideoQuality::ULTRA:
- av_dict_set_int(&options, "qp", 22 * qp_multiply, 0);
+ av_dict_set_int(options, "qp", 20 * qp_multiply, 0);
break;
}
- } else {
+ } else if(codec_context->codec_id == AV_CODEC_ID_HEVC) {
switch(video_quality) {
case VideoQuality::MEDIUM:
- av_dict_set_int(&options, "qp", 37 * qp_multiply, 0);
+ av_dict_set_int(options, "qp", 35 * qp_multiply, 0);
break;
case VideoQuality::HIGH:
- av_dict_set_int(&options, "qp", 32 * qp_multiply, 0);
+ av_dict_set_int(options, "qp", 30 * qp_multiply, 0);
break;
case VideoQuality::VERY_HIGH:
- av_dict_set_int(&options, "qp", 28 * qp_multiply, 0);
+ av_dict_set_int(options, "qp", 25 * qp_multiply, 0);
break;
case VideoQuality::ULTRA:
- av_dict_set_int(&options, "qp", 24 * qp_multiply, 0);
+ av_dict_set_int(options, "qp", 22 * qp_multiply, 0);
break;
}
- }
-
- #if 0
- if(!supports_p4 && !supports_p5)
- fprintf(stderr, "Info: your ffmpeg version is outdated. It's recommended that you use the flatpak version of gpu-screen-recorder version instead, which you can find at https://flathub.org/apps/details/com.dec05eba.gpu_screen_recorder\n");
-
- //if(is_livestream) {
- // av_dict_set_int(&options, "zerolatency", 1, 0);
- // //av_dict_set(&options, "preset", "llhq", 0);
- //}
-
- // I want to use a good preset for the gpu but all gpus prefer different
- // presets. Nvidia and ffmpeg used to support "hq" preset that chose the best preset for the gpu
- // with pretty good performance but you now have to choose p1-p7, which are gpu agnostic and on
- // older gpus p5-p7 slow the gpu down to a crawl...
- // "hq" is now just an alias for p7 in ffmpeg :(
- // TODO: Temporary disable because of stuttering?
-
- // TODO: Preset is set to p5 for now but it should ideally be p6 or p7.
- // This change is needed because for certain sizes of a window (or monitor?) such as 971x780 causes encoding to freeze
- // when using h264 codec. This is a new(?) nvidia driver bug.
- if(very_old_gpu)
- av_dict_set(&options, "preset", supports_p4 ? "p4" : "medium", 0);
- else
- av_dict_set(&options, "preset", supports_p5 ? "p5" : "slow", 0);
- #endif
-
- av_dict_set(&options, "tune", "hq", 0);
- av_dict_set(&options, "rc", "constqp", 0);
-
- // TODO: Enable multipass
-
- if(codec_context->codec_id == AV_CODEC_ID_H264) {
- switch(pixel_format) {
- case PixelFormat::YUV420:
- av_dict_set(&options, "profile", "high", 0);
+ } else if(codec_context->codec_id == AV_CODEC_ID_VP8 || codec_context->codec_id == AV_CODEC_ID_VP9) {
+ switch(video_quality) {
+ case VideoQuality::MEDIUM:
+ av_dict_set_int(options, "qp", 35 * qp_multiply, 0);
break;
- case PixelFormat::YUV444:
- av_dict_set(&options, "profile", "high444p", 0);
+ case VideoQuality::HIGH:
+ av_dict_set_int(options, "qp", 30 * qp_multiply, 0);
break;
- }
- } else if(codec_context->codec_id == AV_CODEC_ID_AV1) {
- switch(pixel_format) {
- case PixelFormat::YUV420:
- av_dict_set(&options, "rgb_mode", "yuv420", 0);
+ case VideoQuality::VERY_HIGH:
+ av_dict_set_int(options, "qp", 25 * qp_multiply, 0);
break;
- case PixelFormat::YUV444:
- av_dict_set(&options, "rgb_mode", "yuv444", 0);
+ case VideoQuality::ULTRA:
+ av_dict_set_int(options, "qp", 22 * qp_multiply, 0);
break;
}
- } else {
- //av_dict_set(&options, "profile", "main10", 0);
- //av_dict_set(&options, "pix_fmt", "yuv420p16le", 0);
- if(hdr) {
- av_dict_set(&options, "profile", "main10", 0);
- } else {
- av_dict_set(&options, "profile", "main", 0);
- }
}
} else {
if(codec_context->codec_id == AV_CODEC_ID_AV1) {
@@ -839,54 +932,109 @@ static void open_video_hardware(AVCodecContext *codec_context, VideoQuality vide
} else if(codec_context->codec_id == AV_CODEC_ID_H264) {
switch(video_quality) {
case VideoQuality::MEDIUM:
- av_dict_set_int(&options, "qp", 34 * qp_multiply, 0);
+ av_dict_set_int(options, "qp", 34 * qp_multiply, 0);
break;
case VideoQuality::HIGH:
- av_dict_set_int(&options, "qp", 30 * qp_multiply, 0);
+ av_dict_set_int(options, "qp", 30 * qp_multiply, 0);
break;
case VideoQuality::VERY_HIGH:
- av_dict_set_int(&options, "qp", 26 * qp_multiply, 0);
+ av_dict_set_int(options, "qp", 23 * qp_multiply, 0);
break;
case VideoQuality::ULTRA:
- av_dict_set_int(&options, "qp", 22 * qp_multiply, 0);
+ av_dict_set_int(options, "qp", 20 * qp_multiply, 0);
break;
}
- } else {
+ } else if(codec_context->codec_id == AV_CODEC_ID_HEVC) {
+ switch(video_quality) {
+ case VideoQuality::MEDIUM:
+ av_dict_set_int(options, "qp", 35 * qp_multiply, 0);
+ break;
+ case VideoQuality::HIGH:
+ av_dict_set_int(options, "qp", 30 * qp_multiply, 0);
+ break;
+ case VideoQuality::VERY_HIGH:
+ av_dict_set_int(options, "qp", 25 * qp_multiply, 0);
+ break;
+ case VideoQuality::ULTRA:
+ av_dict_set_int(options, "qp", 22 * qp_multiply, 0);
+ break;
+ }
+ } else if(codec_context->codec_id == AV_CODEC_ID_VP8 || codec_context->codec_id == AV_CODEC_ID_VP9) {
switch(video_quality) {
case VideoQuality::MEDIUM:
- av_dict_set_int(&options, "qp", 37 * qp_multiply, 0);
+ av_dict_set_int(options, "qp", 35 * qp_multiply, 0);
break;
case VideoQuality::HIGH:
- av_dict_set_int(&options, "qp", 32 * qp_multiply, 0);
+ av_dict_set_int(options, "qp", 30 * qp_multiply, 0);
break;
case VideoQuality::VERY_HIGH:
- av_dict_set_int(&options, "qp", 28 * qp_multiply, 0);
+ av_dict_set_int(options, "qp", 25 * qp_multiply, 0);
break;
case VideoQuality::ULTRA:
- av_dict_set_int(&options, "qp", 24 * qp_multiply, 0);
+ av_dict_set_int(options, "qp", 22 * qp_multiply, 0);
break;
}
}
+ }
+}
+
+static void open_video_hardware(AVCodecContext *codec_context, VideoQuality video_quality, bool very_old_gpu, gsr_gpu_vendor vendor, PixelFormat pixel_format, bool hdr, gsr_color_depth color_depth, BitrateMode bitrate_mode, VideoCodec video_codec, bool low_power) {
+ (void)very_old_gpu;
+ AVDictionary *options = nullptr;
+
+ if(bitrate_mode == BitrateMode::QP)
+ video_hardware_set_qp(codec_context, video_quality, vendor, hdr, &options);
+
+ video_set_rc(video_codec, vendor, bitrate_mode, &options);
+
+ // TODO: Enable multipass
+
+ if(vendor == GSR_GPU_VENDOR_NVIDIA) {
+ av_dict_set(&options, "tune", "hq", 0);
+
+ dict_set_profile(codec_context, vendor, color_depth, &options);
+ if(codec_context->codec_id == AV_CODEC_ID_H264) {
+ // TODO: h264 10bit?
+ // TODO:
+ // switch(pixel_format) {
+ // case PixelFormat::YUV420:
+ // av_dict_set_int(&options, "profile", AV_PROFILE_H264_HIGH, 0);
+ // break;
+ // case PixelFormat::YUV444:
+ // av_dict_set_int(&options, "profile", AV_PROFILE_H264_HIGH_444, 0);
+ // break;
+ // }
+ } else if(codec_context->codec_id == AV_CODEC_ID_AV1) {
+ switch(pixel_format) {
+ case PixelFormat::YUV420:
+ av_dict_set(&options, "rgb_mode", "yuv420", 0);
+ break;
+ case PixelFormat::YUV444:
+ av_dict_set(&options, "rgb_mode", "yuv444", 0);
+ break;
+ }
+ } else if(codec_context->codec_id == AV_CODEC_ID_HEVC) {
+ //av_dict_set(&options, "pix_fmt", "yuv420p16le", 0);
+ }
+ } else {
// TODO: More quality options
- av_dict_set(&options, "rc_mode", "CQP", 0);
- //av_dict_set_int(&options, "low_power", 1, 0);
+ if(low_power)
+ av_dict_set_int(&options, "low_power", 1, 0);
+ // Improves performance but increases vram
+ //av_dict_set_int(&options, "async_depth", 8, 0);
if(codec_context->codec_id == AV_CODEC_ID_H264) {
- av_dict_set(&options, "profile", "high", 0);
// Removed because it causes stutter in games for some people
//av_dict_set_int(&options, "quality", 5, 0); // quality preset
} else if(codec_context->codec_id == AV_CODEC_ID_AV1) {
- av_dict_set(&options, "profile", "main", 0); // TODO: use professional instead?
av_dict_set(&options, "tier", "main", 0);
- } else {
- if(hdr) {
- av_dict_set(&options, "profile", "main10", 0);
+ } else if(codec_context->codec_id == AV_CODEC_ID_HEVC) {
+ if(hdr)
av_dict_set(&options, "sei", "hdr", 0);
- } else {
- av_dict_set(&options, "profile", "main", 0);
- }
}
+
+ // TODO: vp8/vp9 10bit
}
if(codec_context->codec_id == AV_CODEC_ID_H264) {
@@ -905,24 +1053,27 @@ static void open_video_hardware(AVCodecContext *codec_context, VideoQuality vide
static void usage_header() {
const bool inside_flatpak = getenv("FLATPAK_ID") != NULL;
const char *program_name = inside_flatpak ? "flatpak run --command=gpu-screen-recorder com.dec05eba.gpu_screen_recorder" : "gpu-screen-recorder";
- fprintf(stderr, "usage: %s -w <window_id|monitor|focused> [-c <container_format>] [-s WxH] -f <fps> [-a <audio_input>] [-q <quality>] [-r <replay_buffer_size_sec>] [-k h264|hevc|hevc_hdr|av1|av1_hdr] [-ac aac|opus|flac] [-ab <bitrate>] [-oc yes|no] [-fm cfr|vfr|content] [-cr limited|full] [-mf yes|no] [-sc <script_path>] [-cursor yes|no] [-keyint <value>] [-encoder gpu|cpu] [-o <output_file>] [-v yes|no] [-h|--help]\n", program_name);
+ fprintf(stderr, "usage: %s -w <window_id|monitor|focused|portal> [-c <container_format>] [-s WxH] -f <fps> [-a <audio_input>] [-q <quality>] [-vb <bitrate>] [-r <replay_buffer_size_sec>] [-k h264|hevc|av1|vp8|vp9|hevc_hdr|av1_hdr|hevc_10bit|av1_10bit] [-ac aac|opus|flac] [-ab <bitrate>] [-oc yes|no] [-fm cfr|vfr|content] [-bm auto|qp|vbr|cbr] [-cr limited|full] [-df yes|no] [-sc <script_path>] [-cursor yes|no] [-keyint <value>] [-restore-portal-session yes|no] [-portal-session-token-filepath filepath] [-encoder gpu|cpu] [-o <output_file>] [-v yes|no] [--version] [-h|--help]\n", program_name);
}
+// TODO: Update with portal info
static void usage_full() {
const bool inside_flatpak = getenv("FLATPAK_ID") != NULL;
const char *program_name = inside_flatpak ? "flatpak run --command=gpu-screen-recorder com.dec05eba.gpu_screen_recorder" : "gpu-screen-recorder";
usage_header();
fprintf(stderr, "\n");
fprintf(stderr, "OPTIONS:\n");
- fprintf(stderr, " -w Window id to record, a display (monitor name), \"screen\", \"screen-direct-force\" or \"focused\".\n");
- fprintf(stderr, " If this is \"screen\" or \"screen-direct-force\" then all monitors are recorded.\n");
+ fprintf(stderr, " -w Window id to record, a display (monitor name), \"screen\", \"screen-direct-force\", \"focused\" or \"portal\".\n");
+ fprintf(stderr, " If this is \"portal\" then xdg desktop screencast portal with pipewire will be used. Portal option is only available on Wayland.\n");
+ fprintf(stderr, " If you select to save the session (token) in the desktop portal capture popup then the session will be saved for the next time you use \"portal\",\n");
+ fprintf(stderr, " but the session will be ignored unless you run GPU Screen Recorder with the '-restore-portal-session yes' option.\n");
+ fprintf(stderr, " If this is \"screen\" or \"screen-direct-force\" then all monitors are recorded on Nvidia X11. On AMD/Intel or wayland \"screen\" will record the first monitor found.\n");
fprintf(stderr, " \"screen-direct-force\" is not recommended unless you use a VRR (G-SYNC) monitor on Nvidia X11 and you are aware that using this option can cause games to freeze/crash or other issues because of Nvidia driver issues.\n");
fprintf(stderr, " \"screen-direct-force\" option is only available on Nvidia X11. VRR works without this option on other systems.\n");
fprintf(stderr, "\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.\n");
fprintf(stderr, " 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, " Only containers that support h264, hevc or av1 are supported, which means that only mp4, mkv, flv (and some others) are supported.\n");
- fprintf(stderr, " WebM is not supported yet (most hardware doesn't support WebM video encoding).\n");
+ fprintf(stderr, " Only containers that support h264, hevc, av1, vp8 or vp9 are supported, which means that only mp4, mkv, flv, webm (and some others) are supported.\n");
fprintf(stderr, "\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, "\n");
@@ -934,63 +1085,99 @@ static void usage_full() {
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.\n");
fprintf(stderr, " 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\".\n");
fprintf(stderr, " Multiple audio devices can be merged into one audio track by using \"|\" as a separator into one -a argument, for example: -a \"alsa_output1|alsa_output2\".\n");
+ fprintf(stderr, " The audio device can also be \"default_output\" in which case the default output device is used, or \"default_input\" in which case the default input device is used.\n");
fprintf(stderr, " If the audio device is an empty string then the audio device is ignored.\n");
fprintf(stderr, " Optional, no audio track is added by default.\n");
fprintf(stderr, "\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.\n");
fprintf(stderr, " Optional, set to 'very_high' be default.\n");
+ fprintf(stderr, " Note: this option is only used when using '-bm qp' (the default option) or '-bm vbr' options. When using '-bm cbr' option then '-vb' should be used instead.\n");
+ fprintf(stderr, "\n");
+ fprintf(stderr, " -vb Video bitrate in kbps. This should be an integer value that specifies the bitrate (quality) of the video. This option is required when using the '-bm cbr' option.\n");
+ fprintf(stderr, " Note: this option should only be used when using '-bm cbr' option. When using '-bm qp' or '-bm vbr' options then '-q' option should be used instead.\n");
fprintf(stderr, "\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\n");
fprintf(stderr, " and the video will only be saved when the gpu-screen-recorder is closed. This feature is similar to Nvidia's instant replay feature.\n");
fprintf(stderr, " This option has be between 5 and 1200. Note that the replay buffer size will not always be precise, because of keyframes. Optional, disabled by default.\n");
fprintf(stderr, "\n");
- fprintf(stderr, " -k Video codec to use. Should be either 'auto', 'h264', 'hevc', 'av1', 'hevc_hdr' or 'av1_hdr'. Optional, defaults to 'auto' which defaults to 'h264'.\n");
- fprintf(stderr, " Forcefully set to 'h264' if the file container type is 'flv'.\n");
- fprintf(stderr, " 'hevc_hdr' and 'av1_hdr' option is not available on X11.\n");
- fprintf(stderr, " Note: hdr metadata is not included in the video when recording with 'hevc_hdr'/'av1_hdr' because of bugs in AMD, Intel and NVIDIA drivers (amazin', they are all bugged).\n");
+ fprintf(stderr, " -k Video codec to use. Should be either 'auto', 'h264', 'hevc', 'av1', 'vp8', 'vp9', 'hevc_hdr', 'av1_hdr', 'hevc_10bit' or 'av1_10bit'.\n");
+ fprintf(stderr, " Optional, set to 'auto' by default which defaults to 'h264'. Forcefully set to 'h264' if the file container type is 'flv'.\n");
+ fprintf(stderr, " 'hevc_hdr' and 'av1_hdr' option is not available on X11 nor when using the portal capture option.\n");
+ fprintf(stderr, " 'hevc_10bit' and 'av1_10bit' options allow you to select 10 bit color depth which can reduce banding and improve quality in darker areas, but not all video players support 10 bit color depth\n");
+ fprintf(stderr, " and if you upload the video to a website the website might reduce 10 bit to 8 bit.\n");
+ fprintf(stderr, " Note that when using 'hevc_hdr' or 'av1_hdr' the color depth is also 10 bits.\n");
fprintf(stderr, "\n");
- fprintf(stderr, " -ac Audio codec to use. Should be either 'aac', 'opus' or 'flac'. Defaults to 'opus' for .mp4/.mkv files, otherwise defaults to 'aac'.\n");
+ fprintf(stderr, " -ac Audio codec to use. Should be either 'aac', 'opus' or 'flac'. Optional, set to 'opus' for .mp4/.mkv files, otherwise set to 'aac'.\n");
fprintf(stderr, " 'opus' and 'flac' is only supported by .mp4/.mkv files. 'opus' is recommended for best performance and smallest audio size.\n");
fprintf(stderr, " Flac audio codec is option is disable at the moment because of a temporary issue.\n");
fprintf(stderr, "\n");
- fprintf(stderr, " -ab Audio bitrate to use. If this is set to 0 then it's the same as if it's absent, in which case the bitrate is determined automatically depending on the audio codec.\n");
- fprintf(stderr, " Optional, by default the bitrate is 128000 for opus and flac and 160000 for aac.\n");
+ fprintf(stderr, " -ab Audio bitrate in kbps. If this is set to 0 then it's the same as if it's absent, in which case the bitrate is determined automatically depending on the audio codec.\n");
+ fprintf(stderr, " Optional, by default the bitrate is 128kbps for opus and flac and 160kbps for aac.\n");
fprintf(stderr, "\n");
fprintf(stderr, " -oc Overclock memory transfer rate to the maximum performance level. This only applies to NVIDIA on X11 and exists to overcome a bug in NVIDIA driver where performance level\n");
fprintf(stderr, " is dropped when you record a game. Only needed if you are recording a game that is bottlenecked by GPU. The same issue exists on Wayland but overclocking is not possible on Wayland.\n");
fprintf(stderr, " Works only if your have \"Coolbits\" set to \"12\" in NVIDIA X settings, see README for more information. Note! use at your own risk! Optional, disabled by default.\n");
fprintf(stderr, "\n");
- fprintf(stderr, " -fm Framerate mode. Should be either 'cfr' (constant frame rate), 'vfr' (variable frame rate) or 'content'. Defaults to 'vfr'.\n");
+ fprintf(stderr, " -fm Framerate mode. Should be either 'cfr' (constant frame rate), 'vfr' (variable frame rate) or 'content'. Optional, set to 'vfr' by default.\n");
fprintf(stderr, " 'vfr' is recommended for recording for less issue with very high system load but some applications such as video editors may not support it properly.\n");
- fprintf(stderr, " 'content' is currently only supported when recording a single window, on X11. The 'content' option matches the recording frame rate to the captured content.\n");
+ fprintf(stderr, " 'content' is currently only supported on X11 or when using portal capture option. The 'content' option matches the recording frame rate to the captured content.\n");
fprintf(stderr, "\n");
- fprintf(stderr, " -cr Color range. Should be either 'limited' (aka mpeg) or 'full' (aka jpeg). Defaults to 'limited'.\n");
+ fprintf(stderr, " -bm Bitrate mode. Should be either 'auto', 'qp' (constant quality), 'vbr' (variable bitrate) or 'cbr' (constant bitrate). Optional, set to 'auto' by default which defaults to 'qp' on all devices\n");
+ fprintf(stderr, " except steam deck that has broken drivers and doesn't support qp.\n");
+ fprintf(stderr, " Note: 'vbr' option is not supported when using '-encoder cpu' option.\n");
+ fprintf(stderr, "\n");
+ fprintf(stderr, " -cr Color range. Should be either 'limited' (aka mpeg) or 'full' (aka jpeg). Optional, set to 'limited' by default.\n");
fprintf(stderr, " Limited color range means that colors are in range 16-235 (4112-60395 for hdr) while full color range means that colors are in range 0-255 (0-65535 for hdr).\n");
- fprintf(stderr, " Note that some buggy video players (such as vlc) are unable to correctly display videos in full color range.\n");
+ fprintf(stderr, " Note that some buggy video players (such as vlc) are unable to correctly display videos in full color range and when upload the video to websites the website\n");
+ fprintf(stderr, " might re-encoder the video to make the video limited color range.\n");
fprintf(stderr, "\n");
- fprintf(stderr, " -mf Organise replays in folders based on the current date.\n");
+ fprintf(stderr, " -df Organise replays in folders based on the current date.\n");
fprintf(stderr, "\n");
- fprintf(stderr, " -sc Run a script on the saved video file (non-blocking). The first argument to the script is the filepath to the saved video file and the second argument is the recording type (either \"regular\" or \"replay\").\n");
+ fprintf(stderr, " -sc Run a script on the saved video file (asynchronously). The first argument to the script is the filepath to the saved video file and the second argument is the recording type (either \"regular\" or \"replay\").\n");
fprintf(stderr, " Not applicable for live streams.\n");
fprintf(stderr, "\n");
fprintf(stderr, " -cursor\n");
- fprintf(stderr, " Record cursor. Defaults to 'yes'.\n");
+ fprintf(stderr, " Record cursor. Optional, set to 'yes' by default.\n");
+ fprintf(stderr, "\n");
fprintf(stderr, " -keyint\n");
fprintf(stderr, " Specifies the keyframe interval in seconds, the max amount of time to wait to generate a keyframe. Keyframes can be generated more often than this.\n");
fprintf(stderr, " This also affects seeking in the video and may affect how the replay video is cut. If this is set to 10 for example then you can only seek in 10-second chunks in the video.\n");
fprintf(stderr, " Setting this to a higher value reduces the video file size if you are ok with the previously described downside. This option is expected to be a floating point number.\n");
fprintf(stderr, " By default this value is set to 2.0.\n");
+ fprintf(stderr, "\n");
+ fprintf(stderr, " -restore-portal-session\n");
+ fprintf(stderr, " If GPU Screen Recorder should use the same capture option as the last time. Using this option removes the popup asking what you want to record the next time you record with '-w portal' if you selected the option to save session (token) in the desktop portal screencast popup.\n");
+ fprintf(stderr, " This option may not have any effect on your Wayland compositor and your systems desktop portal needs to support ScreenCast version 5 or later. Optional, set to 'no' by default.\n");
+ fprintf(stderr, "\n");
+ fprintf(stderr, " -portal-session-token-filepath\n");
+ fprintf(stderr, " This option is used together with -restore-portal-session option to specify the file path to save/restore the portal session token to/from.\n");
+ fprintf(stderr, " This can be used to remember different portal capture options depending on different recording option (such as recording/replay).\n");
+ fprintf(stderr, " Optional, set to \"$XDG_CONFIG_HOME/gpu-screen-recorder/restore_token\" by default ($XDG_CONFIG_HOME defaults to \"$HOME/.config\").\n");
+ fprintf(stderr, " Note: the directory to the portal session token file is created automatically if it doesn't exist.\n");
+ fprintf(stderr, "\n");
fprintf(stderr, " -encoder\n");
fprintf(stderr, " Which device should be used for video encoding. Should either be 'gpu' or 'cpu'. Does currently only work with h264 codec option (-k).\n");
fprintf(stderr, " Optional, set to 'gpu' by default.\n");
fprintf(stderr, "\n");
- fprintf(stderr, " --list-supported-video-codecs\n");
- fprintf(stderr, " List supported video codecs and exits. Prints h264, hevc, hevc_hdr, av1 and av1_hdr (if supported).\n");
+ fprintf(stderr, " --info\n");
+ fprintf(stderr, " List info about the system (for use by GPU Screen Recorder UI). Lists the following information (prints them to stdout and exits):\n");
+ fprintf(stderr, " Supported video codecs (h264, h264_software, hevc, hevc_hdr, hevc_10bit, av1, av1_hdr, av1_10bit, vp8, vp9 (if supported)).\n");
+ fprintf(stderr, " Supported capture options (window, focused, screen, monitors and portal, if supported by the system).\n");
+ fprintf(stderr, " If opengl initialization fails then the program exits with 22, if no usable drm device is found then it exits with 23. On success it exits with 0.\n");
+ fprintf(stderr, "\n");
+ fprintf(stderr, " --list-audio-devices\n");
+ fprintf(stderr, " List audio devices (for use by GPU Screen Recorder UI). Lists audio devices in the following format (prints them to stdout and exits):\n");
+ fprintf(stderr, " <audio_device_name>|<audio_device_name_in_human_readable_format>\n");
+ fprintf(stderr, " For example:\n");
+ fprintf(stderr, " bluez_input.88:C9:E8:66:A2:27|WH-1000XM4\n");
+ fprintf(stderr, " The <audio_device_name> is the name to pass to GPU Screen Recorder in a -a option.\n");
+ fprintf(stderr, " --version\n");
+ fprintf(stderr, " Print version (%s) and exit\n", GSR_VERSION);
fprintf(stderr, "\n");
- //fprintf(stderr, " -pixfmt The pixel format to use for the output video. yuv420 is the most common format and is best supported, but the color is compressed, so colors can look washed out and certain colors of text can look bad. Use yuv444 for no color compression, but the video may not work everywhere and it may not work with hardware video decoding. Optional, defaults to yuv420\n");
+ //fprintf(stderr, " -pixfmt The pixel format to use for the output video. yuv420 is the most common format and is best supported, but the color is compressed, so colors can look washed out and certain colors of text can look bad. Use yuv444 for no color compression, but the video may not work everywhere and it may not work with hardware video decoding. Optional, set to 'yuv420' by default\n");
fprintf(stderr, " -o The output file path. If omitted then the encoded data is sent to stdout. Required in replay mode (when using -r).\n");
fprintf(stderr, " In replay mode this has to be a directory instead of a file.\n");
- fprintf(stderr, " The directory to the file is created (recursively) if it doesn't already exist.\n");
+ fprintf(stderr, " Note: the directory to the file is created automatically if it doesn't already exist.\n");
fprintf(stderr, "\n");
fprintf(stderr, " -v Prints per second, fps updates. Optional, set to 'yes' by default.\n");
fprintf(stderr, "\n");
@@ -1003,9 +1190,12 @@ static void usage_full() {
fprintf(stderr, " Send signal SIGUSR2 to gpu-screen-recorder (killall -SIGUSR2 gpu-screen-recorder) to pause/unpause recording. Only applicable and useful when recording (not streaming nor replay).\n");
fprintf(stderr, "\n");
fprintf(stderr, "EXAMPLES:\n");
- fprintf(stderr, " %s -w screen -f 60 -a \"$(pactl get-default-sink).monitor\" -o \"$HOME/Videos/video.mp4\"\n", program_name);
- fprintf(stderr, " %s -w screen -f 60 -a \"$(pactl get-default-sink).monitor|$(pactl get-default-source)\" -o \"$HOME/Videos/video.mp4\"\n", program_name);
- fprintf(stderr, " %s -w screen -f 60 -a \"$(pactl get-default-sink).monitor\" -c mkv -r 60 -o \"$HOME/Videos\"\n", program_name);
+ fprintf(stderr, " %s -w screen -f 60 -a default_output -o \"$HOME/Videos/video.mp4\"\n", program_name);
+ fprintf(stderr, " %s -w screen -f 60 -a \"default_output|default_input\" -o \"$HOME/Videos/video.mp4\"\n", program_name);
+ fprintf(stderr, " %s -w screen -f 60 -a default_output -c mkv -r 60 -o \"$HOME/Videos\"\n", program_name);
+ fprintf(stderr, " %s -w screen -f 60 -a default_output -c mkv -sc script.sh -r 60 -o \"$HOME/Videos\"\n", program_name);
+ fprintf(stderr, " %s -w portal -f 60 -a default_output -restore-portal-session yes -o \"$HOME/Videos/video.mp4\"\n", program_name);
+ fprintf(stderr, " %s -w screen -f 60 -a default_output -bm cbr -vb 340000 -o \"$HOME/Videos/video.mp4\"\n", program_name);
//fprintf(stderr, " gpu-screen-recorder -w screen -f 60 -q ultra -pixfmt yuv444 -o video.mp4\n");
_exit(1);
}
@@ -1176,43 +1366,62 @@ struct AudioTrack {
int64_t pts = 0;
};
-static std::future<void> save_replay_thread;
-static std::vector<std::shared_ptr<PacketData>> save_replay_packets;
-static std::string save_replay_output_filepath;
+static bool add_hdr_metadata_to_video_stream(gsr_capture *cap, AVStream *video_stream) {
+ size_t light_metadata_size = 0;
+ size_t mastering_display_metadata_size = 0;
+ AVContentLightMetadata *light_metadata = av_content_light_metadata_alloc(&light_metadata_size);
+ #if LIBAVUTIL_VERSION_INT < AV_VERSION_INT(59, 37, 100)
+ AVMasteringDisplayMetadata *mastering_display_metadata = av_mastering_display_metadata_alloc();
+ mastering_display_metadata_size = sizeof(*mastering_display_metadata);
+ #else
+ AVMasteringDisplayMetadata *mastering_display_metadata = av_mastering_display_metadata_alloc_size(&mastering_display_metadata_size);
+ #endif
-static int create_directory_recursive(char *path) {
- int path_len = strlen(path);
- char *p = path;
- char *end = path + path_len;
- for(;;) {
- char *slash_p = strchr(p, '/');
+ if(!light_metadata || !mastering_display_metadata) {
+ if(light_metadata)
+ av_freep(light_metadata);
- // Skips first '/', we don't want to try and create the root directory
- if(slash_p == path) {
- ++p;
- continue;
- }
+ if(mastering_display_metadata)
+ av_freep(mastering_display_metadata);
- if(!slash_p)
- slash_p = end;
+ return false;
+ }
- char prev_char = *slash_p;
- *slash_p = '\0';
- int err = mkdir(path, S_IRWXU);
- *slash_p = prev_char;
+ if(!gsr_capture_set_hdr_metadata(cap, mastering_display_metadata, light_metadata)) {
+ av_freep(light_metadata);
+ av_freep(mastering_display_metadata);
+ return false;
+ }
- if(err == -1 && errno != EEXIST)
- return err;
+ // TODO: More error checking
- if(slash_p == end)
- break;
- else
- p = slash_p + 1;
- }
- return 0;
+ #if LIBAVCODEC_VERSION_INT < AV_VERSION_INT(60, 31, 102)
+ const bool content_light_level_added = av_stream_add_side_data(video_stream, AV_PKT_DATA_CONTENT_LIGHT_LEVEL, (uint8_t*)light_metadata, light_metadata_size) == 0;
+ #else
+ const bool content_light_level_added = av_packet_side_data_add(&video_stream->codecpar->coded_side_data, &video_stream->codecpar->nb_coded_side_data, AV_PKT_DATA_CONTENT_LIGHT_LEVEL, light_metadata, light_metadata_size, 0) != NULL;
+ #endif
+
+ #if LIBAVCODEC_VERSION_INT < AV_VERSION_INT(60, 31, 102)
+ const bool mastering_display_metadata_added = av_stream_add_side_data(video_stream, AV_PKT_DATA_MASTERING_DISPLAY_METADATA, (uint8_t*)mastering_display_metadata, mastering_display_metadata_size) == 0;
+ #else
+ const bool mastering_display_metadata_added = av_packet_side_data_add(&video_stream->codecpar->coded_side_data, &video_stream->codecpar->nb_coded_side_data, AV_PKT_DATA_MASTERING_DISPLAY_METADATA, mastering_display_metadata, mastering_display_metadata_size, 0) != NULL;
+ #endif
+
+ if(!content_light_level_added)
+ av_freep(light_metadata);
+
+ if(!mastering_display_metadata_added)
+ av_freep(mastering_display_metadata);
+
+ // Return true even on failure because we dont want to retry adding hdr metadata on failure
+ return true;
}
-static void save_replay_async(AVCodecContext *video_codec_context, int video_stream_index, std::vector<AudioTrack> &audio_tracks, std::deque<std::shared_ptr<PacketData>> &frame_data_queue, bool frames_erased, std::string output_dir, const char *container_format, const std::string &file_extension, std::mutex &write_output_mutex, bool make_folders) {
+static std::future<void> save_replay_thread;
+static std::vector<std::shared_ptr<PacketData>> save_replay_packets;
+static std::string save_replay_output_filepath;
+
+static void save_replay_async(AVCodecContext *video_codec_context, int video_stream_index, std::vector<AudioTrack> &audio_tracks, std::deque<std::shared_ptr<PacketData>> &frame_data_queue, bool frames_erased, std::string output_dir, const char *container_format, const std::string &file_extension, std::mutex &write_output_mutex, bool date_folders, bool hdr, gsr_capture *capture) {
if(save_replay_thread.valid())
return;
@@ -1255,7 +1464,7 @@ static void save_replay_async(AVCodecContext *video_codec_context, int video_str
}
}
- if (make_folders) {
+ if (date_folders) {
std::string output_folder = output_dir + '/' + get_date_only_str();
create_directory_recursive(&output_folder[0]);
save_replay_output_filepath = output_folder + "/Replay_" + get_time_only_str() + "." + file_extension;
@@ -1264,36 +1473,42 @@ static void save_replay_async(AVCodecContext *video_codec_context, int video_str
save_replay_output_filepath = output_dir + "/Replay_" + get_date_str() + "." + file_extension;
}
- save_replay_thread = std::async(std::launch::async, [video_stream_index, container_format, start_index, video_pts_offset, audio_pts_offset, video_codec_context, &audio_tracks]() mutable {
- AVFormatContext *av_format_context;
- avformat_alloc_output_context2(&av_format_context, nullptr, container_format, nullptr);
+ AVFormatContext *av_format_context;
+ avformat_alloc_output_context2(&av_format_context, nullptr, container_format, nullptr);
- AVStream *video_stream = create_stream(av_format_context, video_codec_context);
- avcodec_parameters_from_context(video_stream->codecpar, video_codec_context);
+ AVStream *video_stream = create_stream(av_format_context, video_codec_context);
+ avcodec_parameters_from_context(video_stream->codecpar, video_codec_context);
- std::unordered_map<int, AudioTrack*> stream_index_to_audio_track_map;
- for(AudioTrack &audio_track : audio_tracks) {
- stream_index_to_audio_track_map[audio_track.stream_index] = &audio_track;
- AVStream *audio_stream = create_stream(av_format_context, audio_track.codec_context);
- avcodec_parameters_from_context(audio_stream->codecpar, audio_track.codec_context);
- audio_track.stream = audio_stream;
- }
+ std::unordered_map<int, AudioTrack*> stream_index_to_audio_track_map;
+ for(AudioTrack &audio_track : audio_tracks) {
+ stream_index_to_audio_track_map[audio_track.stream_index] = &audio_track;
+ AVStream *audio_stream = create_stream(av_format_context, audio_track.codec_context);
+ avcodec_parameters_from_context(audio_stream->codecpar, audio_track.codec_context);
+ audio_track.stream = audio_stream;
+ }
- int ret = avio_open(&av_format_context->pb, save_replay_output_filepath.c_str(), AVIO_FLAG_WRITE);
- if (ret < 0) {
- fprintf(stderr, "Error: Could not open '%s': %s. Make sure %s is an existing directory with write access\n", save_replay_output_filepath.c_str(), av_error_to_string(ret), save_replay_output_filepath.c_str());
- return;
- }
+ const int open_ret = avio_open(&av_format_context->pb, save_replay_output_filepath.c_str(), AVIO_FLAG_WRITE);
+ if (open_ret < 0) {
+ fprintf(stderr, "Error: Could not open '%s': %s. Make sure %s is an existing directory with write access\n", save_replay_output_filepath.c_str(), av_error_to_string(open_ret), save_replay_output_filepath.c_str());
+ return;
+ }
- AVDictionary *options = nullptr;
- av_dict_set(&options, "strict", "experimental", 0);
+ AVDictionary *options = nullptr;
+ av_dict_set(&options, "strict", "experimental", 0);
- ret = avformat_write_header(av_format_context, &options);
- if (ret < 0) {
- fprintf(stderr, "Error occurred when writing header to output file: %s\n", av_error_to_string(ret));
- return;
- }
+ const int header_write_ret = avformat_write_header(av_format_context, &options);
+ if (header_write_ret < 0) {
+ fprintf(stderr, "Error occurred when writing header to output file: %s\n", av_error_to_string(header_write_ret));
+ avio_close(av_format_context->pb);
+ avformat_free_context(av_format_context);
+ av_dict_free(&options);
+ return;
+ }
+
+ if(hdr)
+ add_hdr_metadata_to_video_stream(capture, video_stream);
+ save_replay_thread = std::async(std::launch::async, [video_stream_index, video_stream, start_index, video_pts_offset, audio_pts_offset, video_codec_context, &audio_tracks, stream_index_to_audio_track_map, av_format_context, options]() mutable {
for(size_t i = start_index; i < save_replay_packets.size(); ++i) {
// TODO: Check if successful
AVPacket av_packet;
@@ -1325,7 +1540,7 @@ static void save_replay_async(AVCodecContext *video_codec_context, int video_str
av_packet.stream_index = stream->index;
av_packet_rescale_ts(&av_packet, codec_context->time_base, stream->time_base);
- ret = av_write_frame(av_format_context, &av_packet);
+ const int ret = av_write_frame(av_format_context, &av_packet);
if(ret < 0)
fprintf(stderr, "Error: Failed to write frame index %d to muxer, reason: %s (%d)\n", stream->index, av_error_to_string(ret), ret);
@@ -1498,6 +1713,69 @@ static int init_filter_graph(AVCodecContext *audio_codec_context, AVFilterGraph
return 0;
}
+static gsr_video_encoder* create_video_encoder(gsr_egl *egl, bool overclock, gsr_color_depth color_depth, bool use_software_video_encoder, VideoCodec video_codec) {
+ gsr_video_encoder *video_encoder = nullptr;
+
+ if(use_software_video_encoder) {
+ gsr_video_encoder_software_params params;
+ params.egl = egl;
+ params.color_depth = color_depth;
+ video_encoder = gsr_video_encoder_software_create(&params);
+ return video_encoder;
+ }
+
+ if(video_codec_is_vulkan(video_codec)) {
+ gsr_video_encoder_vulkan_params params;
+ params.egl = egl;
+ params.color_depth = color_depth;
+ video_encoder = gsr_video_encoder_vulkan_create(&params);
+ return video_encoder;
+ }
+
+ switch(egl->gpu_info.vendor) {
+ case GSR_GPU_VENDOR_AMD:
+ case GSR_GPU_VENDOR_INTEL: {
+ gsr_video_encoder_vaapi_params params;
+ params.egl = egl;
+ params.color_depth = color_depth;
+ video_encoder = gsr_video_encoder_vaapi_create(&params);
+ break;
+ }
+ case GSR_GPU_VENDOR_NVIDIA: {
+ gsr_video_encoder_cuda_params params;
+ params.egl = egl;
+ params.overclock = overclock;
+ params.color_depth = color_depth;
+ video_encoder = gsr_video_encoder_cuda_create(&params);
+ break;
+ }
+ }
+
+ return video_encoder;
+}
+
+static bool get_supported_video_codecs(gsr_egl *egl, VideoCodec video_codec, bool use_software_video_encoder, bool cleanup, gsr_supported_video_codecs *video_codecs) {
+ memset(video_codecs, 0, sizeof(*video_codecs));
+
+ if(use_software_video_encoder) {
+ video_codecs->h264.supported = true;
+ return true;
+ }
+
+ if(video_codec_is_vulkan(video_codec))
+ return gsr_get_supported_video_codecs_vulkan(video_codecs, egl->card_path, cleanup);
+
+ switch(egl->gpu_info.vendor) {
+ case GSR_GPU_VENDOR_AMD:
+ case GSR_GPU_VENDOR_INTEL:
+ return gsr_get_supported_video_codecs_vaapi(video_codecs, egl->card_path, cleanup);
+ case GSR_GPU_VENDOR_NVIDIA:
+ return gsr_get_supported_video_codecs_nvenc(video_codecs, cleanup);
+ }
+
+ return false;
+}
+
static void xwayland_check_callback(const gsr_monitor *monitor, void *userdata) {
bool *xwayland_found = (bool*)userdata;
if(monitor->name_len >= 8 && strncmp(monitor->name, "XWAYLAND", 8) == 0)
@@ -1512,11 +1790,195 @@ static bool is_xwayland(Display *display) {
return true;
bool xwayland_found = false;
- for_each_active_monitor_output_x11(display, xwayland_check_callback, &xwayland_found);
+ for_each_active_monitor_output_x11_not_cached(display, xwayland_check_callback, &xwayland_found);
return xwayland_found;
}
-static void list_supported_video_codecs() {
+static bool is_using_prime_run() {
+ const char *prime_render_offload = getenv("__NV_PRIME_RENDER_OFFLOAD");
+ return prime_render_offload && strcmp(prime_render_offload, "1") == 0;
+}
+
+static void disable_prime_run() {
+ unsetenv("__NV_PRIME_RENDER_OFFLOAD");
+ unsetenv("__NV_PRIME_RENDER_OFFLOAD_PROVIDER");
+ unsetenv("__GLX_VENDOR_LIBRARY_NAME");
+ unsetenv("__VK_LAYER_NV_optimus");
+}
+
+static void list_system_info(bool wayland) {
+ printf("display_server|%s\n", wayland ? "wayland" : "x11");
+}
+
+static void list_gpu_info(gsr_egl *egl) {
+ switch(egl->gpu_info.vendor) {
+ case GSR_GPU_VENDOR_AMD:
+ printf("vendor|amd\n");
+ break;
+ case GSR_GPU_VENDOR_INTEL:
+ printf("vendor|intel\n");
+ break;
+ case GSR_GPU_VENDOR_NVIDIA:
+ printf("vendor|nvidia\n");
+ break;
+ }
+}
+
+static const AVCodec* get_ffmpeg_video_codec(VideoCodec video_codec, gsr_gpu_vendor vendor) {
+ switch(video_codec) {
+ case VideoCodec::H264:
+ return avcodec_find_encoder_by_name(vendor == GSR_GPU_VENDOR_NVIDIA ? "h264_nvenc" : "h264_vaapi");
+ case VideoCodec::HEVC:
+ case VideoCodec::HEVC_HDR:
+ case VideoCodec::HEVC_10BIT:
+ return avcodec_find_encoder_by_name(vendor == GSR_GPU_VENDOR_NVIDIA ? "hevc_nvenc" : "hevc_vaapi");
+ case VideoCodec::AV1:
+ case VideoCodec::AV1_HDR:
+ case VideoCodec::AV1_10BIT:
+ return avcodec_find_encoder_by_name(vendor == GSR_GPU_VENDOR_NVIDIA ? "av1_nvenc" : "av1_vaapi");
+ case VideoCodec::VP8:
+ return avcodec_find_encoder_by_name(vendor == GSR_GPU_VENDOR_NVIDIA ? "vp8_nvenc" : "vp8_vaapi");
+ case VideoCodec::VP9:
+ return avcodec_find_encoder_by_name(vendor == GSR_GPU_VENDOR_NVIDIA ? "vp9_nvenc" : "vp9_vaapi");
+ case VideoCodec::H264_VULKAN:
+ return avcodec_find_encoder_by_name("h264_vulkan");
+ case VideoCodec::HEVC_VULKAN:
+ return avcodec_find_encoder_by_name("hevc_vulkan");
+ }
+ return nullptr;
+}
+
+static void set_supported_video_codecs_ffmpeg(gsr_supported_video_codecs *supported_video_codecs, gsr_supported_video_codecs *supported_video_codecs_vulkan, gsr_gpu_vendor vendor) {
+ if(!get_ffmpeg_video_codec(VideoCodec::H264, vendor)) {
+ supported_video_codecs->h264.supported = false;
+ }
+
+ if(!get_ffmpeg_video_codec(VideoCodec::HEVC, vendor)) {
+ supported_video_codecs->hevc.supported = false;
+ supported_video_codecs->hevc_hdr.supported = false;
+ supported_video_codecs->hevc_10bit.supported = false;
+ }
+
+ if(!get_ffmpeg_video_codec(VideoCodec::AV1, vendor)) {
+ supported_video_codecs->av1.supported = false;
+ supported_video_codecs->av1_hdr.supported = false;
+ supported_video_codecs->av1_10bit.supported = false;
+ }
+
+ if(!get_ffmpeg_video_codec(VideoCodec::VP8, vendor)) {
+ supported_video_codecs->vp8.supported = false;
+ }
+
+ if(!get_ffmpeg_video_codec(VideoCodec::VP9, vendor)) {
+ supported_video_codecs->vp9.supported = false;
+ }
+
+ if(!get_ffmpeg_video_codec(VideoCodec::H264_VULKAN, vendor)) {
+ supported_video_codecs_vulkan->h264.supported = false;
+ }
+
+ if(!get_ffmpeg_video_codec(VideoCodec::HEVC_VULKAN, vendor)) {
+ supported_video_codecs_vulkan->hevc.supported = false;
+ supported_video_codecs_vulkan->hevc_hdr.supported = false;
+ supported_video_codecs_vulkan->hevc_10bit.supported = false;
+ }
+}
+
+static void list_supported_video_codecs(gsr_egl *egl, bool wayland) {
+ // Dont clean it up on purpose to increase shutdown speed
+ gsr_supported_video_codecs supported_video_codecs;
+ get_supported_video_codecs(egl, VideoCodec::H264, false, false, &supported_video_codecs);
+
+ gsr_supported_video_codecs supported_video_codecs_vulkan;
+ get_supported_video_codecs(egl, VideoCodec::H264_VULKAN, false, false, &supported_video_codecs_vulkan);
+
+ set_supported_video_codecs_ffmpeg(&supported_video_codecs, &supported_video_codecs_vulkan, egl->gpu_info.vendor);
+
+ if(supported_video_codecs.h264.supported)
+ puts("h264");
+ if(avcodec_find_encoder_by_name("libx264"))
+ puts("h264_software");
+ if(supported_video_codecs.hevc.supported)
+ puts("hevc");
+ if(supported_video_codecs.hevc_hdr.supported && wayland)
+ puts("hevc_hdr");
+ if(supported_video_codecs.hevc_10bit.supported)
+ puts("hevc_10bit");
+ if(supported_video_codecs.av1.supported)
+ puts("av1");
+ if(supported_video_codecs.av1_hdr.supported && wayland)
+ puts("av1_hdr");
+ if(supported_video_codecs.av1_10bit.supported)
+ puts("av1_10bit");
+ if(supported_video_codecs.vp8.supported)
+ puts("vp8");
+ if(supported_video_codecs.vp9.supported)
+ puts("vp9");
+ //if(supported_video_codecs_vulkan.h264.supported)
+ // puts("h264_vulkan");
+ //if(supported_video_codecs_vulkan.hevc.supported)
+ // puts("hevc_vulkan"); // TODO: hdr, 10 bit
+}
+
+static bool monitor_capture_use_drm(gsr_egl *egl, bool wayland) {
+ return wayland || egl->gpu_info.vendor != GSR_GPU_VENDOR_NVIDIA;
+}
+
+typedef struct {
+ bool wayland;
+ gsr_egl *egl;
+} capture_options_callback;
+
+static void output_monitor_info(const gsr_monitor *monitor, void *userdata) {
+ const capture_options_callback *options = (capture_options_callback*)userdata;
+ if(options->wayland && monitor_capture_use_drm(options->egl, options->wayland)) {
+ vec2i monitor_size = monitor->size;
+ const gsr_monitor_rotation rot = drm_monitor_get_display_server_rotation(options->egl, monitor);
+ if(rot == GSR_MONITOR_ROT_90 || rot == GSR_MONITOR_ROT_270)
+ std::swap(monitor_size.x, monitor_size.y);
+ printf("%.*s|%dx%d\n", monitor->name_len, monitor->name, monitor_size.x, monitor_size.y);
+ } else {
+ printf("%.*s|%dx%d\n", monitor->name_len, monitor->name, monitor->size.x, monitor->size.y);
+ }
+}
+
+static void list_supported_capture_options(gsr_egl *egl, bool wayland) {
+ if(!wayland) {
+ puts("window");
+ puts("focused");
+ }
+
+ capture_options_callback options;
+ options.wayland = wayland;
+ options.egl = egl;
+ if(monitor_capture_use_drm(egl, wayland)) {
+ const bool is_x11 = gsr_egl_get_display_server(egl) == GSR_DISPLAY_SERVER_X11;
+ const gsr_connection_type connection_type = is_x11 ? GSR_CONNECTION_X11 : GSR_CONNECTION_DRM;
+ for_each_active_monitor_output(egl, connection_type, output_monitor_info, &options);
+ } else {
+ puts("screen"); // All monitors in one, only available on Nvidia X11
+ for_each_active_monitor_output(egl, GSR_CONNECTION_X11, output_monitor_info, &options);
+ }
+
+#ifdef GSR_PORTAL
+ // Desktop portal capture on x11 doesn't seem to be hardware accelerated
+ if(!wayland)
+ return;
+
+ gsr_dbus dbus;
+ if(!gsr_dbus_init(&dbus, NULL))
+ return;
+
+ char *session_handle = NULL;
+ if(gsr_dbus_screencast_create_session(&dbus, &session_handle) == 0) {
+ free(session_handle);
+ puts("portal");
+ }
+ gsr_dbus_deinit(&dbus);
+#endif
+}
+
+static void info_command() {
bool wayland = false;
Display *dpy = XOpenDisplay(nullptr);
if (!dpy) {
@@ -1530,46 +1992,81 @@ static void list_supported_video_codecs() {
if(!wayland)
wayland = is_xwayland(dpy);
+ if(!wayland && is_using_prime_run()) {
+ // Disable prime-run and similar options as it doesn't work, the monitor to capture has to be run on the same device.
+ // This is fine on wayland since nvidia uses drm interface there and the monitor query checks the monitors connected
+ // to the drm device.
+ fprintf(stderr, "Warning: use of prime-run on X11 is not supported. Disabling prime-run\n");
+ disable_prime_run();
+ }
+
gsr_egl egl;
if(!gsr_egl_load(&egl, dpy, wayland, false)) {
fprintf(stderr, "gsr error: failed to load opengl\n");
- _exit(1);
+ _exit(22);
}
- char card_path[128];
- card_path[0] = '\0';
- if(wayland || egl.gpu_info.vendor != GSR_GPU_VENDOR_NVIDIA) {
+ egl.card_path[0] = '\0';
+ if(monitor_capture_use_drm(&egl, wayland)) {
// TODO: Allow specifying another card, and in other places
- if(!gsr_get_valid_card_path(&egl, card_path, false)) {
- fprintf(stderr, "Error: no /dev/dri/cardX device found. If you are running GPU Screen Recorder with prime-run then try running without it. Also make sure that you have at least one connected monitor or record a single window instead on X11\n");
- _exit(2);
+ if(!gsr_get_valid_card_path(&egl, egl.card_path, false)) {
+ fprintf(stderr, "Error: no /dev/dri/cardX device found. Make sure that you have at least one monitor connected\n");
+ _exit(23);
}
}
av_log_set_level(AV_LOG_FATAL);
- // TODO: Output hdr
- if(find_h264_encoder(egl.gpu_info.vendor, card_path))
- puts("h264");
- if(find_hevc_encoder(egl.gpu_info.vendor, card_path))
- puts("hevc");
- if(find_av1_encoder(egl.gpu_info.vendor, card_path))
- puts("av1");
+ puts("section=system_info");
+ list_system_info(wayland);
+ if(egl.gpu_info.is_steam_deck)
+ puts("is_steam_deck|yes");
+ else
+ puts("is_steam_deck|no");
+ puts("section=gpu_info");
+ list_gpu_info(&egl);
+ puts("section=video_codecs");
+ list_supported_video_codecs(&egl, wayland);
+ puts("section=capture_options");
+ list_supported_capture_options(&egl, wayland);
fflush(stdout);
- gsr_egl_unload(&egl);
- if(dpy)
- XCloseDisplay(dpy);
+ // Not needed as this will just slow down shutdown
+ //gsr_egl_unload(&egl);
+ //if(dpy)
+ // XCloseDisplay(dpy);
+
+ _exit(0);
+}
+
+static void list_audio_devices_command() {
+ const AudioDevices audio_devices = get_pulseaudio_inputs();
+
+ if(!audio_devices.default_output.empty())
+ puts("default_output|Default output");
+
+ if(!audio_devices.default_input.empty())
+ puts("default_input|Default input");
+
+ for(const auto &audio_input : audio_devices.audio_inputs) {
+ printf("%s|%s\n", audio_input.name.c_str(), audio_input.description.c_str());
+ }
+
+ fflush(stdout);
+ _exit(0);
}
-static gsr_capture* create_capture_impl(const char *window_str, const char *screen_region, bool wayland, gsr_egl *egl, int fps, bool overclock, VideoCodec video_codec, gsr_color_range color_range, bool record_cursor, bool track_damage, bool use_software_video_encoder) {
+static gsr_capture* create_capture_impl(std::string &window_str, const char *screen_region, bool wayland, gsr_egl *egl, int fps, VideoCodec video_codec, gsr_color_range color_range,
+ bool record_cursor, bool use_software_video_encoder, bool restore_portal_session, const char *portal_session_token_filepath,
+ gsr_color_depth color_depth)
+{
vec2i region_size = { 0, 0 };
Window src_window_id = None;
bool follow_focused = false;
gsr_capture *capture = nullptr;
- if(strcmp(window_str, "focused") == 0) {
+ if(strcmp(window_str.c_str(), "focused") == 0) {
if(wayland) {
fprintf(stderr, "Error: GPU Screen Recorder window capture only works in a pure X11 session. Xwayland is not supported. You can record a monitor instead on wayland\n");
_exit(2);
@@ -1591,35 +2088,60 @@ static gsr_capture* create_capture_impl(const char *window_str, const char *scre
}
follow_focused = true;
- } else if(contains_non_hex_number(window_str)) {
- if(wayland || egl->gpu_info.vendor != GSR_GPU_VENDOR_NVIDIA) {
- if(strcmp(window_str, "screen") == 0) {
+ } else if(strcmp(window_str.c_str(), "portal") == 0) {
+#ifdef GSR_PORTAL
+ // Desktop portal capture on x11 doesn't seem to be hardware accelerated
+ if(!wayland) {
+ fprintf(stderr, "Error: desktop portal capture is not supported on X11\n");
+ _exit(1);
+ }
+
+ gsr_capture_portal_params portal_params;
+ portal_params.egl = egl;
+ portal_params.color_depth = color_depth;
+ portal_params.color_range = color_range;
+ portal_params.record_cursor = record_cursor;
+ portal_params.restore_portal_session = restore_portal_session;
+ portal_params.portal_session_token_filepath = portal_session_token_filepath;
+ capture = gsr_capture_portal_create(&portal_params);
+ if(!capture)
+ _exit(1);
+#else
+ fprintf(stderr, "Error: option '-w portal' used but GPU Screen Recorder was compiled without desktop portal support\n");
+ _exit(2);
+#endif
+ } else if(contains_non_hex_number(window_str.c_str())) {
+ if(monitor_capture_use_drm(egl, wayland)) {
+ const bool is_x11 = gsr_egl_get_display_server(egl) == GSR_DISPLAY_SERVER_X11;
+ const gsr_connection_type connection_type = is_x11 ? GSR_CONNECTION_X11 : GSR_CONNECTION_DRM;
+
+ if(strcmp(window_str.c_str(), "screen") == 0) {
FirstOutputCallback first_output;
first_output.output_name = NULL;
- for_each_active_monitor_output(egl, GSR_CONNECTION_DRM, get_first_output, &first_output);
+ for_each_active_monitor_output(egl, connection_type, get_first_output, &first_output);
if(first_output.output_name) {
window_str = first_output.output_name;
} else {
- fprintf(stderr, "Error: no available output found\n");
+ fprintf(stderr, "Error: no usable output found\n");
+ _exit(1);
+ }
+ } else {
+ gsr_monitor gmon;
+ if(!get_monitor_by_name(egl, connection_type, window_str.c_str(), &gmon)) {
+ fprintf(stderr, "gsr error: display \"%s\" not found, expected one of:\n", window_str.c_str());
+ fprintf(stderr, " \"screen\"\n");
+ for_each_active_monitor_output(egl, connection_type, monitor_output_callback_print, NULL);
_exit(1);
}
- }
-
- gsr_monitor gmon;
- if(!get_monitor_by_name(egl, GSR_CONNECTION_DRM, window_str, &gmon)) {
- fprintf(stderr, "gsr error: display \"%s\" not found, expected one of:\n", window_str);
- fprintf(stderr, " \"screen\"\n");
- for_each_active_monitor_output(egl, GSR_CONNECTION_DRM, monitor_output_callback_print, NULL);
- _exit(1);
}
} else {
- if(strcmp(window_str, "screen") != 0 && strcmp(window_str, "screen-direct") != 0 && strcmp(window_str, "screen-direct-force") != 0) {
+ if(strcmp(window_str.c_str(), "screen") != 0 && strcmp(window_str.c_str(), "screen-direct") != 0 && strcmp(window_str.c_str(), "screen-direct-force") != 0) {
gsr_monitor gmon;
- if(!get_monitor_by_name(egl, GSR_CONNECTION_X11, window_str, &gmon)) {
+ if(!get_monitor_by_name(egl, GSR_CONNECTION_X11, window_str.c_str(), &gmon)) {
const int screens_width = XWidthOfScreen(DefaultScreenOfDisplay(egl->x11.dpy));
const int screens_height = XWidthOfScreen(DefaultScreenOfDisplay(egl->x11.dpy));
- fprintf(stderr, "gsr error: display \"%s\" not found, expected one of:\n", window_str);
+ fprintf(stderr, "gsr error: display \"%s\" not found, expected one of:\n", window_str.c_str());
fprintf(stderr, " \"screen\" (%dx%d+%d+%d)\n", screens_width, screens_height, 0, 0);
fprintf(stderr, " \"screen-direct\" (%dx%d+%d+%d)\n", screens_width, screens_height, 0, 0);
fprintf(stderr, " \"screen-direct-force\" (%dx%d+%d+%d)\n", screens_width, screens_height, 0, 0);
@@ -1629,70 +2151,47 @@ static gsr_capture* create_capture_impl(const char *window_str, const char *scre
}
}
- if(use_software_video_encoder && (wayland || egl->gpu_info.vendor != GSR_GPU_VENDOR_NVIDIA)) {
+ if(egl->gpu_info.vendor == GSR_GPU_VENDOR_NVIDIA && !wayland) {
+ const char *capture_target = window_str.c_str();
+ bool direct_capture = strcmp(window_str.c_str(), "screen-direct") == 0;
+ if(direct_capture) {
+ capture_target = "screen";
+ // TODO: Temporary disable direct capture because push model causes stuttering when it's direct capturing. This might be a nvfbc bug. This does not happen when using a compositor.
+ direct_capture = false;
+ 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(strcmp(window_str.c_str(), "screen-direct-force") == 0) {
+ direct_capture = true;
+ capture_target = "screen";
+ }
+
+ gsr_capture_nvfbc_params nvfbc_params;
+ nvfbc_params.egl = egl;
+ nvfbc_params.display_to_capture = capture_target;
+ nvfbc_params.fps = fps;
+ nvfbc_params.pos = { 0, 0 };
+ nvfbc_params.size = { 0, 0 };
+ nvfbc_params.direct_capture = direct_capture;
+ nvfbc_params.color_depth = color_depth;
+ nvfbc_params.color_range = color_range;
+ nvfbc_params.record_cursor = record_cursor;
+ nvfbc_params.use_software_video_encoder = use_software_video_encoder;
+ capture = gsr_capture_nvfbc_create(&nvfbc_params);
+ if(!capture)
+ _exit(1);
+ } else {
gsr_capture_kms_params kms_params;
kms_params.egl = egl;
- kms_params.display_to_capture = window_str;
- kms_params.hdr = video_codec_is_hdr(video_codec);
+ kms_params.display_to_capture = window_str.c_str();
+ kms_params.color_depth = color_depth;
kms_params.color_range = color_range;
kms_params.record_cursor = record_cursor;
+ kms_params.hdr = video_codec_is_hdr(video_codec);
+ kms_params.fps = fps;
capture = gsr_capture_kms_create(&kms_params);
if(!capture)
_exit(1);
- } else {
- if(egl->gpu_info.vendor == GSR_GPU_VENDOR_NVIDIA) {
- if(wayland) {
- gsr_capture_kms_params kms_params;
- kms_params.egl = egl;
- kms_params.display_to_capture = window_str;
- kms_params.hdr = video_codec_is_hdr(video_codec);
- kms_params.color_range = color_range;
- kms_params.record_cursor = record_cursor;
- capture = gsr_capture_kms_create(&kms_params);
- if(!capture)
- _exit(1);
- } else {
- const char *capture_target = window_str;
- bool direct_capture = strcmp(window_str, "screen-direct") == 0;
- if(direct_capture) {
- capture_target = "screen";
- // TODO: Temporary disable direct capture because push model causes stuttering when it's direct capturing. This might be a nvfbc bug. This does not happen when using a compositor.
- direct_capture = false;
- 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(strcmp(window_str, "screen-direct-force") == 0) {
- direct_capture = true;
- capture_target = "screen";
- }
-
- gsr_capture_nvfbc_params nvfbc_params;
- nvfbc_params.egl = egl;
- nvfbc_params.display_to_capture = capture_target;
- nvfbc_params.fps = fps;
- nvfbc_params.pos = { 0, 0 };
- nvfbc_params.size = { 0, 0 };
- nvfbc_params.direct_capture = direct_capture;
- nvfbc_params.overclock = overclock;
- nvfbc_params.hdr = video_codec_is_hdr(video_codec);
- nvfbc_params.color_range = color_range;
- nvfbc_params.record_cursor = record_cursor;
- nvfbc_params.use_software_video_encoder = use_software_video_encoder;
- capture = gsr_capture_nvfbc_create(&nvfbc_params);
- if(!capture)
- _exit(1);
- }
- } else {
- gsr_capture_kms_params kms_params;
- kms_params.egl = egl;
- kms_params.display_to_capture = window_str;
- kms_params.hdr = video_codec_is_hdr(video_codec);
- kms_params.color_range = color_range;
- kms_params.record_cursor = record_cursor;
- capture = gsr_capture_kms_create(&kms_params);
- if(!capture)
- _exit(1);
- }
}
} else {
if(wayland) {
@@ -1701,9 +2200,9 @@ static gsr_capture* create_capture_impl(const char *window_str, const char *scre
}
errno = 0;
- src_window_id = strtol(window_str, nullptr, 0);
+ src_window_id = strtol(window_str.c_str(), nullptr, 0);
if(src_window_id == None || errno == EINVAL) {
- fprintf(stderr, "Invalid window number %s\n", window_str);
+ fprintf(stderr, "Invalid window number %s\n", window_str.c_str());
usage();
}
}
@@ -1716,7 +2215,7 @@ static gsr_capture* create_capture_impl(const char *window_str, const char *scre
xcomposite_params.region_size = region_size;
xcomposite_params.color_range = color_range;
xcomposite_params.record_cursor = record_cursor;
- xcomposite_params.track_damage = track_damage;
+ xcomposite_params.color_depth = color_depth;
capture = gsr_capture_xcomposite_create(&xcomposite_params);
if(!capture)
_exit(1);
@@ -1725,44 +2224,14 @@ static gsr_capture* create_capture_impl(const char *window_str, const char *scre
return capture;
}
-static gsr_video_encoder* create_video_encoder(gsr_egl *egl, bool overclock, bool hdr, bool use_software_video_encoder) {
- gsr_video_encoder *video_encoder = nullptr;
-
- if(use_software_video_encoder) {
- gsr_video_encoder_software_params params;
- params.egl = egl;
- params.hdr = hdr;
- video_encoder = gsr_video_encoder_software_create(&params);
- return video_encoder;
- }
-
- switch(egl->gpu_info.vendor) {
- case GSR_GPU_VENDOR_AMD:
- case GSR_GPU_VENDOR_INTEL: {
- gsr_video_encoder_vaapi_params params;
- params.egl = egl;
- params.hdr = hdr;
- video_encoder = gsr_video_encoder_vaapi_create(&params);
- break;
- }
- case GSR_GPU_VENDOR_NVIDIA: {
- gsr_video_encoder_cuda_params params;
- params.egl = egl;
- params.overclock = overclock;
- params.hdr = hdr;
- video_encoder = gsr_video_encoder_cuda_create(&params);
- break;
- }
- }
-
- return video_encoder;
-}
-
-static AVPixelFormat get_pixel_format(gsr_gpu_vendor vendor, bool use_software_video_encoder) {
+static AVPixelFormat get_pixel_format(VideoCodec video_codec, gsr_gpu_vendor vendor, bool use_software_video_encoder) {
if(use_software_video_encoder) {
return AV_PIX_FMT_NV12;
} else {
- return vendor == GSR_GPU_VENDOR_NVIDIA ? AV_PIX_FMT_CUDA : AV_PIX_FMT_VAAPI;
+ if(video_codec_is_vulkan(video_codec))
+ return AV_PIX_FMT_VULKAN;
+ else
+ return vendor == GSR_GPU_VENDOR_NVIDIA ? AV_PIX_FMT_CUDA : AV_PIX_FMT_VAAPI;
}
}
@@ -1778,6 +2247,350 @@ struct Arg {
}
};
+// Manually check if the audio inputs we give exist. This is only needed for pipewire, not pulseaudio.
+// Pipewire instead DEFAULTS TO THE DEFAULT AUDIO INPUT. THAT'S RETARDED.
+// OH, YOU MISSPELLED THE AUDIO INPUT? FUCK YOU
+static std::vector<MergedAudioInputs> parse_audio_inputs(const AudioDevices &audio_devices, const Arg &audio_input_arg, bool &uses_amix) {
+ std::vector<MergedAudioInputs> requested_audio_inputs;
+ uses_amix = false;
+
+ for(const char *audio_input : audio_input_arg.values) {
+ if(!audio_input || audio_input[0] == '\0')
+ continue;
+
+ requested_audio_inputs.push_back({parse_audio_input_arg(audio_input)});
+ if(requested_audio_inputs.back().audio_inputs.size() > 1)
+ uses_amix = true;
+
+ for(AudioInput &request_audio_input : requested_audio_inputs.back().audio_inputs) {
+ bool match = false;
+
+ if(!audio_devices.default_output.empty() && request_audio_input.name == "default_output") {
+ request_audio_input.name = audio_devices.default_output;
+ if(request_audio_input.description.empty())
+ request_audio_input.description = "gsr-Default output";
+ match = true;
+ }
+
+ if(!audio_devices.default_input.empty() && request_audio_input.name == "default_input") {
+ request_audio_input.name = audio_devices.default_input;
+ if(request_audio_input.description.empty())
+ request_audio_input.description = "gsr-Default input";
+ match = true;
+ }
+
+ for(const auto &existing_audio_input : audio_devices.audio_inputs) {
+ if(request_audio_input.name == existing_audio_input.name) {
+ if(request_audio_input.description.empty())
+ request_audio_input.description = "gsr-" + existing_audio_input.description;
+
+ match = true;
+ break;
+ }
+ }
+
+ 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());
+ if(!audio_devices.default_output.empty())
+ fprintf(stderr, " default_output (Default output)\n");
+ if(!audio_devices.default_input.empty())
+ fprintf(stderr, " default_input (Default input)\n");
+ for(const auto &existing_audio_input : audio_devices.audio_inputs) {
+ fprintf(stderr, " %s (%s)\n", existing_audio_input.name.c_str(), existing_audio_input.description.c_str());
+ }
+ _exit(2);
+ }
+ }
+ }
+
+ return requested_audio_inputs;
+}
+
+static AudioCodec select_audio_codec_with_fallback(AudioCodec audio_codec, const std::string &file_extension,bool uses_amix) {
+ switch(audio_codec) {
+ case AudioCodec::AAC: {
+ if(file_extension == "webm") {
+ //audio_codec_to_use = "opus";
+ audio_codec = AudioCodec::OPUS;
+ fprintf(stderr, "Warning: .webm files only support opus audio codec, changing audio codec from aac to opus\n");
+ }
+ break;
+ }
+ case AudioCodec::OPUS: {
+ // TODO: Also check mpegts?
+ if(file_extension != "mp4" && file_extension != "mkv" && file_extension != "webm") {
+ //audio_codec_to_use = "aac";
+ audio_codec = AudioCodec::AAC;
+ fprintf(stderr, "Warning: opus audio codec is only supported by .mp4, .mkv and .webm files, falling back to aac instead\n");
+ }
+ break;
+ }
+ case AudioCodec::FLAC: {
+ // TODO: Also check mpegts?
+ if(file_extension == "webm") {
+ //audio_codec_to_use = "opus";
+ audio_codec = AudioCodec::OPUS;
+ fprintf(stderr, "Warning: .webm files only support opus audio codec, changing audio codec from flac to opus\n");
+ } else if(file_extension != "mp4" && file_extension != "mkv") {
+ //audio_codec_to_use = "aac";
+ audio_codec = AudioCodec::AAC;
+ fprintf(stderr, "Warning: flac audio codec is only supported by .mp4 and .mkv files, falling back to aac instead\n");
+ } else if(uses_amix) {
+ // TODO: remove this? is it true anymore?
+ //audio_codec_to_use = "opus";
+ audio_codec = AudioCodec::OPUS;
+ fprintf(stderr, "Warning: flac audio codec is not supported when mixing audio sources, falling back to opus instead\n");
+ }
+ break;
+ }
+ }
+ return audio_codec;
+}
+
+static const char* video_codec_to_string(VideoCodec video_codec) {
+ switch(video_codec) {
+ case VideoCodec::H264: return "h264";
+ case VideoCodec::HEVC: return "hevc";
+ case VideoCodec::HEVC_HDR: return "hevc_hdr";
+ case VideoCodec::HEVC_10BIT: return "hevc_10bit";
+ case VideoCodec::AV1: return "av1";
+ case VideoCodec::AV1_HDR: return "av1_hdr";
+ case VideoCodec::AV1_10BIT: return "av1_10bit";
+ case VideoCodec::VP8: return "vp8";
+ case VideoCodec::VP9: return "vp9";
+ case VideoCodec::H264_VULKAN: return "h264_vulkan";
+ case VideoCodec::HEVC_VULKAN: return "hevc_vulkan";
+ }
+ return "";
+}
+
+static bool video_codec_only_supports_low_power_mode(const gsr_supported_video_codecs &supported_video_codecs, VideoCodec video_codec) {
+ switch(video_codec) {
+ case VideoCodec::H264: return supported_video_codecs.h264.low_power;
+ case VideoCodec::HEVC: return supported_video_codecs.hevc.low_power;
+ case VideoCodec::HEVC_HDR: return supported_video_codecs.hevc_hdr.low_power;
+ case VideoCodec::HEVC_10BIT: return supported_video_codecs.hevc_10bit.low_power;
+ case VideoCodec::AV1: return supported_video_codecs.av1.low_power;
+ case VideoCodec::AV1_HDR: return supported_video_codecs.av1_hdr.low_power;
+ case VideoCodec::AV1_10BIT: return supported_video_codecs.av1_10bit.low_power;
+ case VideoCodec::VP8: return supported_video_codecs.vp8.low_power;
+ case VideoCodec::VP9: return supported_video_codecs.vp9.low_power;
+ case VideoCodec::H264_VULKAN: return supported_video_codecs.h264.low_power;
+ case VideoCodec::HEVC_VULKAN: return supported_video_codecs.hevc.low_power; // TODO: hdr, 10 bit
+ }
+ return false;
+}
+
+static const AVCodec* pick_video_codec(VideoCodec *video_codec, gsr_egl *egl, bool use_software_video_encoder, bool video_codec_auto, const char *video_codec_to_use, bool is_flv, bool *low_power) {
+ // TODO: software encoder for hevc, av1, vp8 and vp9
+ *low_power = false;
+
+ gsr_supported_video_codecs supported_video_codecs;
+ if(!get_supported_video_codecs(egl, *video_codec, use_software_video_encoder, true, &supported_video_codecs)) {
+ fprintf(stderr, "Error: failed to query for supported video codecs\n");
+ _exit(11);
+ }
+
+ const AVCodec *video_codec_f = nullptr;
+
+ switch(*video_codec) {
+ case VideoCodec::H264: {
+ if(use_software_video_encoder)
+ video_codec_f = avcodec_find_encoder_by_name("libx264");
+ else if(supported_video_codecs.h264.supported)
+ video_codec_f = get_ffmpeg_video_codec(*video_codec, egl->gpu_info.vendor);
+ break;
+ }
+ case VideoCodec::HEVC: {
+ if(supported_video_codecs.hevc.supported)
+ video_codec_f = get_ffmpeg_video_codec(*video_codec, egl->gpu_info.vendor);
+ break;
+ }
+ case VideoCodec::HEVC_HDR: {
+ if(supported_video_codecs.hevc_hdr.supported)
+ video_codec_f = get_ffmpeg_video_codec(*video_codec, egl->gpu_info.vendor);
+ break;
+ }
+ case VideoCodec::HEVC_10BIT: {
+ if(supported_video_codecs.hevc_10bit.supported)
+ video_codec_f = get_ffmpeg_video_codec(*video_codec, egl->gpu_info.vendor);
+ break;
+ }
+ case VideoCodec::AV1: {
+ if(supported_video_codecs.av1.supported)
+ video_codec_f = get_ffmpeg_video_codec(*video_codec, egl->gpu_info.vendor);
+ break;
+ }
+ case VideoCodec::AV1_HDR: {
+ if(supported_video_codecs.av1_hdr.supported)
+ video_codec_f = get_ffmpeg_video_codec(*video_codec, egl->gpu_info.vendor);
+ break;
+ }
+ case VideoCodec::AV1_10BIT: {
+ if(supported_video_codecs.av1_10bit.supported)
+ video_codec_f = get_ffmpeg_video_codec(*video_codec, egl->gpu_info.vendor);
+ break;
+ }
+ case VideoCodec::VP8: {
+ if(supported_video_codecs.vp8.supported)
+ video_codec_f = get_ffmpeg_video_codec(*video_codec, egl->gpu_info.vendor);
+ break;
+ }
+ case VideoCodec::VP9: {
+ if(supported_video_codecs.vp9.supported)
+ video_codec_f = get_ffmpeg_video_codec(*video_codec, egl->gpu_info.vendor);
+ break;
+ }
+ case VideoCodec::H264_VULKAN: {
+ if(supported_video_codecs.h264.supported)
+ video_codec_f = get_ffmpeg_video_codec(*video_codec, egl->gpu_info.vendor);
+ break;
+ }
+ case VideoCodec::HEVC_VULKAN: {
+ // TODO: hdr, 10 bit
+ if(supported_video_codecs.hevc.supported)
+ video_codec_f = get_ffmpeg_video_codec(*video_codec, egl->gpu_info.vendor);
+ break;
+ }
+ }
+
+ if(!video_codec_auto && !video_codec_f && !is_flv) {
+ switch(*video_codec) {
+ case VideoCodec::H264: {
+ fprintf(stderr, "Warning: selected video codec h264 is not supported, trying hevc instead\n");
+ video_codec_to_use = "hevc";
+ if(supported_video_codecs.hevc.supported)
+ video_codec_f = get_ffmpeg_video_codec(*video_codec, egl->gpu_info.vendor);
+ break;
+ }
+ case VideoCodec::HEVC:
+ case VideoCodec::HEVC_HDR:
+ case VideoCodec::HEVC_10BIT: {
+ fprintf(stderr, "Warning: selected video codec hevc is not supported, trying h264 instead\n");
+ video_codec_to_use = "h264";
+ *video_codec = VideoCodec::H264;
+ if(supported_video_codecs.h264.supported)
+ video_codec_f = get_ffmpeg_video_codec(*video_codec, egl->gpu_info.vendor);
+ break;
+ }
+ case VideoCodec::AV1:
+ case VideoCodec::AV1_HDR:
+ case VideoCodec::AV1_10BIT: {
+ fprintf(stderr, "Warning: selected video codec av1 is not supported, trying h264 instead\n");
+ video_codec_to_use = "h264";
+ *video_codec = VideoCodec::H264;
+ if(supported_video_codecs.h264.supported)
+ video_codec_f = get_ffmpeg_video_codec(*video_codec, egl->gpu_info.vendor);
+ break;
+ }
+ case VideoCodec::VP8:
+ case VideoCodec::VP9:
+ // TODO: Cant fallback to other codec because webm only supports vp8/vp9
+ break;
+ case VideoCodec::H264_VULKAN: {
+ fprintf(stderr, "Warning: selected video codec h264_vulkan is not supported, trying h264 instead\n");
+ video_codec_to_use = "h264";
+ *video_codec = VideoCodec::H264;
+ // Need to do a query again because this time it's without vulkan
+ if(!get_supported_video_codecs(egl, *video_codec, use_software_video_encoder, true, &supported_video_codecs)) {
+ fprintf(stderr, "Error: failed to query for supported video codecs\n");
+ _exit(11);
+ }
+ if(supported_video_codecs.h264.supported)
+ video_codec_f = get_ffmpeg_video_codec(*video_codec, egl->gpu_info.vendor);
+ break;
+ }
+ case VideoCodec::HEVC_VULKAN: {
+ fprintf(stderr, "Warning: selected video codec hevc_vulkan is not supported, trying hevc instead\n");
+ video_codec_to_use = "hevc";
+ *video_codec = VideoCodec::HEVC;
+ // Need to do a query again because this time it's without vulkan
+ if(!get_supported_video_codecs(egl, *video_codec, use_software_video_encoder, true, &supported_video_codecs)) {
+ fprintf(stderr, "Error: failed to query for supported video codecs\n");
+ _exit(11);
+ }
+ if(supported_video_codecs.hevc.supported)
+ video_codec_f = get_ffmpeg_video_codec(*video_codec, egl->gpu_info.vendor);
+ break;
+ }
+ }
+ }
+
+ (void)video_codec_to_use;
+
+ if(!video_codec_f) {
+ const char *video_codec_name = video_codec_to_string(*video_codec);
+ fprintf(stderr, "Error: your gpu does not support '%s' video codec. If you are sure that your gpu does support '%s' video encoding and you are using an AMD/Intel GPU,\n"
+ " then make sure you have installed the GPU specific vaapi packages (intel-media-driver, libva-intel-driver, libva-mesa-driver and linux-firmware).\n"
+ " It's also possible that your distro has disabled hardware accelerated video encoding for '%s' video codec.\n"
+ " This may be the case on corporate distros such as Manjaro, Fedora or OpenSUSE.\n"
+ " You can test this by running 'vainfo | grep VAEntrypointEncSlice' to see if it matches any H264/HEVC/AV1/VP8/VP9 profile.\n"
+ " On such distros, you need to manually install mesa from source to enable H264/HEVC hardware acceleration, or use a more user friendly distro. Alternatively record with AV1 if supported by your GPU.\n"
+ " You can alternatively use the flatpak version of GPU Screen Recorder (https://flathub.org/apps/com.dec05eba.gpu_screen_recorder) which bypasses system issues with patented H264/HEVC codecs.\n"
+ " Make sure you have mesa-extra freedesktop runtime installed when using the flatpak (this should be the default), which can be installed with this command:\n"
+ " flatpak install --system org.freedesktop.Platform.GL.default//23.08-extra\n"
+ " If your GPU doesn't support hardware accelerated video encoding then you can use '-encoder cpu' option to encode with your cpu instead.\n", video_codec_name, video_codec_name, video_codec_name);
+ _exit(2);
+ }
+
+ *low_power = video_codec_only_supports_low_power_mode(supported_video_codecs, *video_codec);
+
+ return video_codec_f;
+}
+
+static const AVCodec* select_video_codec_with_fallback(VideoCodec *video_codec, const char *video_codec_to_use, const char *file_extension, bool use_software_video_encoder, gsr_egl *egl, bool *low_power) {
+ const bool video_codec_auto = strcmp(video_codec_to_use, "auto") == 0;
+ if(video_codec_auto) {
+ if(strcmp(file_extension, "webm") == 0) {
+ fprintf(stderr, "Info: using vp8 encoder because a codec was not specified and the file extension is .webm\n");
+ video_codec_to_use = "vp8";
+ *video_codec = VideoCodec::VP8;
+ } else {
+ fprintf(stderr, "Info: using h264 encoder because a codec was not specified\n");
+ video_codec_to_use = "h264";
+ *video_codec = VideoCodec::H264;
+ }
+ }
+
+ // TODO: Allow hevc, vp9 and av1 in (enhanced) flv (supported since ffmpeg 6.1)
+ const bool is_flv = strcmp(file_extension, "flv") == 0;
+ if(is_flv) {
+ if(*video_codec != VideoCodec::H264) {
+ video_codec_to_use = "h264";
+ *video_codec = VideoCodec::H264;
+ fprintf(stderr, "Warning: hevc/av1 is not compatible with flv, falling back to h264 instead.\n");
+ }
+
+ // if(audio_codec != AudioCodec::AAC) {
+ // audio_codec_to_use = "aac";
+ // audio_codec = AudioCodec::AAC;
+ // fprintf(stderr, "Warning: flv only supports aac, falling back to aac instead.\n");
+ // }
+ }
+
+ const bool is_hls = strcmp(file_extension, "m3u8") == 0;
+ if(is_hls) {
+ if(video_codec_is_av1(*video_codec)) {
+ video_codec_to_use = "hevc";
+ *video_codec = VideoCodec::HEVC;
+ fprintf(stderr, "Warning: av1 is not compatible with hls (m3u8), falling back to hevc instead.\n");
+ }
+
+ // if(audio_codec != AudioCodec::AAC) {
+ // audio_codec_to_use = "aac";
+ // audio_codec = AudioCodec::AAC;
+ // fprintf(stderr, "Warning: hls (m3u8) only supports aac, falling back to aac instead.\n");
+ // }
+ }
+
+ if(use_software_video_encoder && *video_codec != VideoCodec::H264) {
+ fprintf(stderr, "Error: \"-encoder cpu\" option is currently only available when using h264 codec option (-k)\n");
+ usage();
+ }
+
+ return pick_video_codec(video_codec, egl, use_software_video_encoder, video_codec_auto, video_codec_to_use, is_flv, low_power);
+}
+
int main(int argc, char **argv) {
signal(SIGINT, stop_handler);
signal(SIGUSR1, save_replay_handler);
@@ -1803,8 +2616,18 @@ int main(int argc, char **argv) {
if(argc == 2 && (strcmp(argv[1], "-h") == 0 || strcmp(argv[1], "--help") == 0))
usage_full();
- if(argc == 2 && strcmp(argv[1], "--list-supported-video-codecs") == 0) {
- list_supported_video_codecs();
+ if(argc == 2 && strcmp(argv[1], "--info") == 0) {
+ info_command();
+ _exit(0);
+ }
+
+ if(argc == 2 && strcmp(argv[1], "--list-audio-devices") == 0) {
+ list_audio_devices_command();
+ _exit(0);
+ }
+
+ if(argc == 2 && strcmp(argv[1], "--version") == 0) {
+ puts(GSR_VERSION);
_exit(0);
}
@@ -1817,6 +2640,7 @@ int main(int argc, char **argv) {
{ "-s", Arg { {}, true, false } },
{ "-a", Arg { {}, true, true } },
{ "-q", Arg { {}, true, false } },
+ { "-vb", Arg { {}, true, false } },
{ "-o", Arg { {}, true, false } },
{ "-r", Arg { {}, true, false } },
{ "-k", Arg { {}, true, false } },
@@ -1824,14 +2648,17 @@ int main(int argc, char **argv) {
{ "-ab", Arg { {}, true, false } },
{ "-oc", Arg { {}, true, false } },
{ "-fm", Arg { {}, true, false } },
+ { "-bm", Arg { {}, true, false } },
{ "-pixfmt", Arg { {}, true, false } },
{ "-v", Arg { {}, true, false } },
- { "-mf", Arg { {}, true, false } },
+ { "-mf", Arg { {}, true, false } }, // TODO: Remove, this exists for backwards compatibility. -df should be used instead
+ { "-df", Arg { {}, true, false } },
{ "-sc", Arg { {}, true, false } },
{ "-cr", Arg { {}, true, false } },
{ "-cursor", Arg { {}, true, false } },
- { "-gopm", Arg { {}, true, false } }, // deprecated, used keyint instead
{ "-keyint", Arg { {}, true, false } },
+ { "-restore-portal-session", Arg { {}, true, false } },
+ { "-portal-session-token-filepath", Arg { {}, true, false } },
{ "-encoder", Arg { {}, true, false } },
};
@@ -1873,12 +2700,24 @@ int main(int argc, char **argv) {
video_codec = VideoCodec::HEVC;
} else if(strcmp(video_codec_to_use, "hevc_hdr") == 0) {
video_codec = VideoCodec::HEVC_HDR;
+ } else if(strcmp(video_codec_to_use, "hevc_10bit") == 0) {
+ video_codec = VideoCodec::HEVC_10BIT;
} else if(strcmp(video_codec_to_use, "av1") == 0) {
video_codec = VideoCodec::AV1;
} else if(strcmp(video_codec_to_use, "av1_hdr") == 0) {
video_codec = VideoCodec::AV1_HDR;
+ } else if(strcmp(video_codec_to_use, "av1_10bit") == 0) {
+ video_codec = VideoCodec::AV1_10BIT;
+ } else if(strcmp(video_codec_to_use, "vp8") == 0) {
+ video_codec = VideoCodec::VP8;
+ } else if(strcmp(video_codec_to_use, "vp9") == 0) {
+ video_codec = VideoCodec::VP9;
+ //} else if(strcmp(video_codec_to_use, "h264_vulkan") == 0) {
+ // video_codec = VideoCodec::H264_VULKAN;
+ //} else if(strcmp(video_codec_to_use, "hevc_vulkan") == 0) {
+ // video_codec = VideoCodec::HEVC_VULKAN;
} else if(strcmp(video_codec_to_use, "auto") != 0) {
- fprintf(stderr, "Error: -k should either be either 'auto', 'h264', 'hevc', 'hevc_hdr', 'av1' or 'av1_hdr', got: '%s'\n", video_codec_to_use);
+ fprintf(stderr, "Error: -k should either be either 'auto', 'h264', 'hevc', 'av1', 'vp8', 'vp9', 'hevc_hdr', 'av1_hdr', 'hevc_10bit' or 'av1_10bit', got: '%s'\n", video_codec_to_use);
usage();
}
@@ -1904,13 +2743,25 @@ int main(int argc, char **argv) {
audio_codec = AudioCodec::OPUS;
}
- int audio_bitrate = 0;
+ int64_t audio_bitrate = 0;
const char *audio_bitrate_str = args["-ab"].value();
if(audio_bitrate_str) {
- if(sscanf(audio_bitrate_str, "%d", &audio_bitrate) != 1) {
+ if(sscanf(audio_bitrate_str, "%" PRIi64, &audio_bitrate) != 1) {
fprintf(stderr, "Error: -ab argument \"%s\" is not an integer\n", audio_bitrate_str);
usage();
}
+
+ if(audio_bitrate < 0) {
+ fprintf(stderr, "Error: -ab is expected to be 0 or larger, got %" PRIi64 "\n", audio_bitrate);
+ usage();
+ }
+
+ if(audio_bitrate > 50000) {
+ fprintf(stderr, "Error: audio bitrate %" PRIi64 "is too high. It's expected to be in kbps, normally in the range 54-300\n", audio_bitrate);
+ usage();
+ }
+
+ audio_bitrate *= 1000LL;
}
float keyint = 2.0;
@@ -1922,7 +2773,7 @@ int main(int argc, char **argv) {
}
if(keyint < 0) {
- fprintf(stderr, "Error: -keyint is expected to be 0 or larger\n");
+ fprintf(stderr, "Error: -keyint is expected to be 0 or larger, got %f\n", keyint);
usage();
}
}
@@ -1982,20 +2833,48 @@ int main(int argc, char **argv) {
usage();
}
- bool make_folders = false;
- const char *make_folders_str = args["-mf"].value();
- if(!make_folders_str)
- make_folders_str = "no";
+ bool date_folders = false;
+ const char *date_folders_str = args["-df"].value();
+ if(!date_folders_str) {
+ date_folders_str = args["-mf"].value();
+ if(date_folders_str)
+ fprintf(stderr, "Warning: -mf is deprecated, use -df instead\n");
+ }
+ if(!date_folders_str)
+ date_folders_str = "no";
- if(strcmp(make_folders_str, "yes") == 0) {
- make_folders = true;
- } else if(strcmp(make_folders_str, "no") == 0) {
- make_folders = false;
+ if(strcmp(date_folders_str, "yes") == 0) {
+ date_folders = true;
+ } else if(strcmp(date_folders_str, "no") == 0) {
+ date_folders = false;
} else {
- fprintf(stderr, "Error: -mf should either be either 'yes' or 'no', got: '%s'\n", make_folders_str);
+ fprintf(stderr, "Error: -df should either be either 'yes' or 'no', got: '%s'\n", date_folders_str);
usage();
}
+ bool restore_portal_session = false;
+ const char *restore_portal_session_str = args["-restore-portal-session"].value();
+ if(!restore_portal_session_str)
+ restore_portal_session_str = "no";
+
+ if(strcmp(restore_portal_session_str, "yes") == 0) {
+ restore_portal_session = true;
+ } else if(strcmp(restore_portal_session_str, "no") == 0) {
+ restore_portal_session = false;
+ } else {
+ fprintf(stderr, "Error: -restore-portal-session should either be either 'yes' or 'no', got: '%s'\n", restore_portal_session_str);
+ usage();
+ }
+
+ const char *portal_session_token_filepath = args["-portal-session-token-filepath"].value();
+ if(portal_session_token_filepath) {
+ int len = strlen(portal_session_token_filepath);
+ if(len > 0 && portal_session_token_filepath[len - 1] == '/') {
+ fprintf(stderr, "Error: -portal-session-token-filepath should be a path to a file but it ends with a /: %s\n", portal_session_token_filepath);
+ _exit(1);
+ }
+ }
+
const char *recording_saved_script = args["-sc"].value();
if(recording_saved_script) {
struct stat buf;
@@ -2025,44 +2904,12 @@ int main(int argc, char **argv) {
}
const Arg &audio_input_arg = args["-a"];
- std::vector<AudioInput> audio_inputs;
+ AudioDevices audio_devices;
if(!audio_input_arg.values.empty())
- audio_inputs = get_pulseaudio_inputs();
- std::vector<MergedAudioInputs> requested_audio_inputs;
- bool uses_amix = false;
-
- // Manually check if the audio inputs we give exist. This is only needed for pipewire, not pulseaudio.
- // Pipewire instead DEFAULTS TO THE DEFAULT AUDIO INPUT. THAT'S RETARDED.
- // OH, YOU MISSPELLED THE AUDIO INPUT? FUCK YOU
- for(const char *audio_input : audio_input_arg.values) {
- if(!audio_input || audio_input[0] == '\0')
- continue;
-
- requested_audio_inputs.push_back({parse_audio_input_arg(audio_input)});
- if(requested_audio_inputs.back().audio_inputs.size() > 1)
- uses_amix = true;
-
- for(AudioInput &request_audio_input : requested_audio_inputs.back().audio_inputs) {
- bool match = false;
- for(const auto &existing_audio_input : audio_inputs) {
- if(strcmp(request_audio_input.name.c_str(), existing_audio_input.name.c_str()) == 0) {
- if(request_audio_input.description.empty())
- request_audio_input.description = "gsr-" + existing_audio_input.description;
-
- match = true;
- break;
- }
- }
+ audio_devices = get_pulseaudio_inputs();
- 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());
- for(const auto &existing_audio_input : audio_inputs) {
- fprintf(stderr, " %s (%s)\n", existing_audio_input.name.c_str(), existing_audio_input.description.c_str());
- }
- _exit(2);
- }
- }
- }
+ bool uses_amix = false;
+ std::vector<MergedAudioInputs> requested_audio_inputs = parse_audio_inputs(audio_devices, audio_input_arg, uses_amix);
const char *container_format = args["-c"].value();
if(container_format && strcmp(container_format, "mkv") == 0)
@@ -2076,24 +2923,6 @@ int main(int argc, char **argv) {
if(fps < 1)
fps = 1;
- VideoQuality quality = VideoQuality::VERY_HIGH;
- const char *quality_str = args["-q"].value();
- if(!quality_str)
- quality_str = "very_high";
-
- if(strcmp(quality_str, "medium") == 0) {
- quality = VideoQuality::MEDIUM;
- } else if(strcmp(quality_str, "high") == 0) {
- quality = VideoQuality::HIGH;
- } else if(strcmp(quality_str, "very_high") == 0) {
- quality = VideoQuality::VERY_HIGH;
- } else if(strcmp(quality_str, "ultra") == 0) {
- quality = VideoQuality::ULTRA;
- } else {
- fprintf(stderr, "Error: -q should either be either 'medium', 'high', 'very_high' or 'ultra', got: '%s'\n", quality_str);
- usage();
- }
-
int replay_buffer_size_secs = -1;
const char *replay_buffer_size_secs_str = args["-r"].value();
if(replay_buffer_size_secs_str) {
@@ -2105,7 +2934,12 @@ int main(int argc, char **argv) {
replay_buffer_size_secs += std::ceil(keyint); // Add a few seconds to account of lost packets because of non-keyframe packets skipped
}
- const char *window_str = strdup(args["-w"].value());
+ std::string window_str = args["-w"].value();
+ const bool is_portal_capture = strcmp(window_str.c_str(), "portal") == 0;
+
+ if(!restore_portal_session && is_portal_capture) {
+ fprintf(stderr, "gsr info: option '-w portal' was used without '-restore-portal-session yes'. The previous screencast session will be ignored\n");
+ }
bool wayland = false;
Display *dpy = XOpenDisplay(nullptr);
@@ -2120,18 +2954,36 @@ int main(int argc, char **argv) {
if(!wayland)
wayland = is_xwayland(dpy);
+ if(!wayland && is_using_prime_run()) {
+ // Disable prime-run and similar options as it doesn't work, the monitor to capture has to be run on the same device.
+ // This is fine on wayland since nvidia uses drm interface there and the monitor query checks the monitors connected
+ // to the drm device.
+ fprintf(stderr, "Warning: use of prime-run on X11 is not supported. Disabling prime-run\n");
+ disable_prime_run();
+ }
+
+ if(is_portal_capture && is_using_prime_run()) {
+ fprintf(stderr, "Warning: use of prime-run with -w portal option is currently not supported. Disabling prime-run\n");
+ disable_prime_run();
+ }
+
if(video_codec_is_hdr(video_codec) && !wayland) {
fprintf(stderr, "Error: hdr video codec option %s is not available on X11\n", video_codec_to_use);
_exit(1);
}
- const bool is_monitor_capture = strcmp(window_str, "focused") != 0 && contains_non_hex_number(window_str);
+ const bool is_monitor_capture = strcmp(window_str.c_str(), "focused") != 0 && !is_portal_capture && contains_non_hex_number(window_str.c_str());
gsr_egl egl;
if(!gsr_egl_load(&egl, dpy, wayland, is_monitor_capture)) {
fprintf(stderr, "gsr error: failed to load opengl\n");
_exit(1);
}
+ if(egl.gpu_info.is_steam_deck) {
+ fprintf(stderr, "gsr warning: steam deck has multiple driver issues. One of them has been reported here: https://github.com/ValveSoftware/SteamOS/issues/1609\n"
+ "If you have issues with GPU Screen Recorder on steam deck that you don't have on a desktop computer then report the issue to Valve and/or AMD.\n");
+ }
+
bool very_old_gpu = false;
if(egl.gpu_info.vendor == GSR_GPU_VENDOR_NVIDIA && egl.gpu_info.gpu_version != 0 && egl.gpu_info.gpu_version < 900) {
@@ -2150,14 +3002,19 @@ int main(int argc, char **argv) {
}
egl.card_path[0] = '\0';
- if(wayland || egl.gpu_info.vendor != GSR_GPU_VENDOR_NVIDIA) {
+ if(monitor_capture_use_drm(&egl, wayland)) {
// TODO: Allow specifying another card, and in other places
if(!gsr_get_valid_card_path(&egl, egl.card_path, is_monitor_capture)) {
- fprintf(stderr, "Error: no /dev/dri/cardX device found. If you are running GPU Screen Recorder with prime-run then try running without it. Also make sure that you have at least one connected monitor or record a single window instead on X11\n");
+ fprintf(stderr, "Error: no /dev/dri/cardX device found. Make sure that you have at least one monitor connected or record a single window instead on X11 or record with the -w portal option\n");
_exit(2);
}
}
+ // if(wayland && is_monitor_capture) {
+ // fprintf(stderr, "gsr warning: it's not possible to sync video to recorded monitor exactly on wayland when recording a monitor."
+ // " If you experience stutter in the video then record with portal capture option instead (-w portal) or use X11 instead\n");
+ // }
+
// TODO: Fix constant framerate not working properly on amd/intel because capture framerate gets locked to the same framerate as
// game framerate, which doesn't work well when you need to encode multiple duplicate frames (AMD/Intel is slow at encoding!).
// It also appears to skip audio frames on nvidia wayland? why? that should be fine, but it causes video stuttering because of audio/video sync.
@@ -2177,11 +3034,92 @@ int main(int argc, char **argv) {
usage();
}
- if(framerate_mode == FramerateMode::CONTENT && (wayland || is_monitor_capture)) {
- fprintf(stderr, "Error: -fm 'content' is currently only supported on X11 and when capturing a single window.\n");
+ if(framerate_mode == FramerateMode::CONTENT && wayland && !is_portal_capture) {
+ fprintf(stderr, "Error: -fm 'content' is currently only supported on X11 or when using portal capture option\n");
usage();
}
+ BitrateMode bitrate_mode = BitrateMode::QP;
+ const char *bitrate_mode_str = args["-bm"].value();
+ if(!bitrate_mode_str)
+ bitrate_mode_str = "auto";
+
+ if(strcmp(bitrate_mode_str, "qp") == 0) {
+ bitrate_mode = BitrateMode::QP;
+ } else if(strcmp(bitrate_mode_str, "vbr") == 0) {
+ bitrate_mode = BitrateMode::VBR;
+ } else if(strcmp(bitrate_mode_str, "cbr") == 0) {
+ bitrate_mode = BitrateMode::CBR;
+ } else if(strcmp(bitrate_mode_str, "auto") != 0) {
+ fprintf(stderr, "Error: -bm should either be either 'auto', 'qp', 'vbr' or 'cbr', got: '%s'\n", bitrate_mode_str);
+ usage();
+ }
+
+ if(strcmp(bitrate_mode_str, "auto") == 0) {
+ // QP is broken on steam deck, see https://github.com/ValveSoftware/SteamOS/issues/1609
+ bitrate_mode = egl.gpu_info.is_steam_deck ? BitrateMode::VBR : BitrateMode::QP;
+ }
+
+ if(egl.gpu_info.is_steam_deck && bitrate_mode == BitrateMode::QP) {
+ fprintf(stderr, "Warning: qp bitrate mode is not supported on Steam Deck because of Steam Deck driver bugs. Using vbr instead\n");
+ bitrate_mode = BitrateMode::VBR;
+ }
+
+ if(use_software_video_encoder && bitrate_mode == BitrateMode::VBR) {
+ fprintf(stderr, "Warning: bitrate mode has been forcefully set to qp because software encoding option doesn't support vbr option\n");
+ bitrate_mode = BitrateMode::QP;
+ }
+
+ const char *quality_str = args["-q"].value();
+ const char *video_bitrate_str = args["-vb"].value();
+
+ if(bitrate_mode == BitrateMode::CBR && quality_str) {
+ fprintf(stderr, "Error: '-q' option can't be used when using the '-bm cbr' option. Use '-vb' option instead to specify the video quality\n");
+ usage();
+ }
+
+ if(bitrate_mode != BitrateMode::CBR && video_bitrate_str) {
+ fprintf(stderr, "Error: '-vb' option can't be used when using the '-bm qp' (default option) or '-bm vbr' option. Use '-q' option instead to specify the video quality\n");
+ usage();
+ }
+
+ if(bitrate_mode == BitrateMode::CBR && !video_bitrate_str) {
+ fprintf(stderr, "Error: option '-vb' is required when using '-bm cbr' option\n");
+ usage();
+ }
+
+ VideoQuality quality = VideoQuality::VERY_HIGH;
+ if(!quality_str)
+ quality_str = "very_high";
+
+ if(strcmp(quality_str, "medium") == 0) {
+ quality = VideoQuality::MEDIUM;
+ } else if(strcmp(quality_str, "high") == 0) {
+ quality = VideoQuality::HIGH;
+ } else if(strcmp(quality_str, "very_high") == 0) {
+ quality = VideoQuality::VERY_HIGH;
+ } else if(strcmp(quality_str, "ultra") == 0) {
+ quality = VideoQuality::ULTRA;
+ } else {
+ fprintf(stderr, "Error: -q should either be either 'medium', 'high', 'very_high' or 'ultra', got: '%s'\n", quality_str);
+ usage();
+ }
+
+ int64_t video_bitrate = 0;
+ if(video_bitrate_str) {
+ if(sscanf(video_bitrate_str, "%" PRIi64, &video_bitrate) != 1) {
+ fprintf(stderr, "Error: -vb argument \"%s\" is not an integer value\n", video_bitrate_str);
+ usage();
+ }
+
+ if(video_bitrate < 0) {
+ fprintf(stderr, "Error: -vb is expected to be 0 or larger, got %" PRIi64 "\n", video_bitrate);
+ usage();
+ }
+
+ video_bitrate *= 1000LL;
+ }
+
gsr_color_range color_range = GSR_COLOR_RANGE_LIMITED;
const char *color_range_str = args["-cr"].value();
if(!color_range_str)
@@ -2198,7 +3136,7 @@ int main(int argc, char **argv) {
const char *screen_region = args["-s"].value();
- if(screen_region && strcmp(window_str, "focused") != 0) {
+ if(screen_region && strcmp(window_str.c_str(), "focused") != 0) {
fprintf(stderr, "Error: option -s is only available when using -w focused\n");
usage();
}
@@ -2215,7 +3153,7 @@ int main(int argc, char **argv) {
} else {
if(replay_buffer_size_secs == -1) {
char directory_buf[PATH_MAX];
- strcpy(directory_buf, filename);
+ snprintf(directory_buf, sizeof(directory_buf), "%s", filename);
char *directory = dirname(directory_buf);
if(strcmp(directory, ".") != 0 && strcmp(directory, "/") != 0) {
if(create_directory_recursive(directory) != 0) {
@@ -2275,173 +3213,19 @@ int main(int argc, char **argv) {
}
const bool force_no_audio_offset = is_livestream || is_output_piped || (file_extension != "mp4" && file_extension != "mkv" && file_extension != "webm");
-
- switch(audio_codec) {
- case AudioCodec::AAC: {
- if(file_extension == "webm") {
- audio_codec_to_use = "opus";
- audio_codec = AudioCodec::OPUS;
- fprintf(stderr, "Warning: .webm files only support opus audio codec, changing audio codec from aac to opus\n");
- }
- break;
- }
- case AudioCodec::OPUS: {
- // TODO: Also check mpegts?
- if(file_extension != "mp4" && file_extension != "mkv" && file_extension != "webm") {
- audio_codec_to_use = "aac";
- audio_codec = AudioCodec::AAC;
- fprintf(stderr, "Warning: opus audio codec is only supported by .mp4, .mkv and .webm files, falling back to aac instead\n");
- }
- break;
- }
- case AudioCodec::FLAC: {
- // TODO: Also check mpegts?
- if(file_extension == "webm") {
- audio_codec_to_use = "opus";
- audio_codec = AudioCodec::OPUS;
- fprintf(stderr, "Warning: .webm files only support opus audio codec, changing audio codec from flac to opus\n");
- } else if(file_extension != "mp4" && file_extension != "mkv") {
- audio_codec_to_use = "aac";
- audio_codec = AudioCodec::AAC;
- fprintf(stderr, "Warning: flac audio codec is only supported by .mp4 and .mkv files, falling back to aac instead\n");
- } else if(uses_amix) {
- // TODO: remove this? is it true anymore?
- audio_codec_to_use = "opus";
- audio_codec = AudioCodec::OPUS;
- fprintf(stderr, "Warning: flac audio codec is not supported when mixing audio sources, falling back to opus instead\n");
- }
- break;
- }
- }
-
const double target_fps = 1.0 / (double)fps;
- const bool video_codec_auto = strcmp(video_codec_to_use, "auto") == 0;
- if(video_codec_auto) {
- fprintf(stderr, "Info: using h264 encoder because a codec was not specified\n");
- video_codec_to_use = "h264";
- video_codec = VideoCodec::H264;
- }
-
- // TODO: Allow hevc, vp9 and av1 in (enhanced) flv (supported since ffmpeg 6.1)
- const bool is_flv = strcmp(file_extension.c_str(), "flv") == 0;
- if(is_flv) {
- if(video_codec != VideoCodec::H264) {
- video_codec_to_use = "h264";
- video_codec = VideoCodec::H264;
- fprintf(stderr, "Warning: hevc/av1 is not compatible with flv, falling back to h264 instead.\n");
- }
-
- if(audio_codec != AudioCodec::AAC) {
- audio_codec_to_use = "aac";
- audio_codec = AudioCodec::AAC;
- fprintf(stderr, "Warning: flv only supports aac, falling back to aac instead.\n");
- }
- }
-
- const bool is_hls = strcmp(file_extension.c_str(), "m3u8") == 0;
- if(is_hls) {
- if(video_codec == VideoCodec::AV1 || video_codec == VideoCodec::AV1_HDR) {
- video_codec_to_use = "hevc";
- video_codec = VideoCodec::HEVC;
- fprintf(stderr, "Warning: av1 is not compatible with hls (m3u8), falling back to hevc instead.\n");
- }
-
- if(audio_codec != AudioCodec::AAC) {
- audio_codec_to_use = "aac";
- audio_codec = AudioCodec::AAC;
- fprintf(stderr, "Warning: hls (m3u8) only supports aac, falling back to aac instead.\n");
- }
- }
-
- if(use_software_video_encoder && video_codec != VideoCodec::H264) {
- fprintf(stderr, "Error: \"-encoder cpu\" option is currently only available when using h264 codec option (-k)\n");
- usage();
- }
-
- const AVCodec *video_codec_f = nullptr;
- switch(video_codec) {
- case VideoCodec::H264: {
- if(use_software_video_encoder) {
- video_codec_f = find_h264_software_encoder();
- } else {
- video_codec_f = find_h264_encoder(egl.gpu_info.vendor, egl.card_path);
- }
- break;
- }
- case VideoCodec::HEVC:
- case VideoCodec::HEVC_HDR:
- // TODO: software encoder
- video_codec_f = find_hevc_encoder(egl.gpu_info.vendor, egl.card_path);
- break;
- case VideoCodec::AV1:
- case VideoCodec::AV1_HDR:
- // TODO: software encoder
- video_codec_f = find_av1_encoder(egl.gpu_info.vendor, egl.card_path);
- break;
- }
-
- if(!video_codec_auto && !video_codec_f && !is_flv) {
- switch(video_codec) {
- case VideoCodec::H264: {
- fprintf(stderr, "Warning: selected video codec h264 is not supported, trying hevc instead\n");
- video_codec_to_use = "hevc";
- video_codec = VideoCodec::HEVC;
- video_codec_f = find_hevc_encoder(egl.gpu_info.vendor, egl.card_path);
- break;
- }
- case VideoCodec::HEVC:
- case VideoCodec::HEVC_HDR: {
- fprintf(stderr, "Warning: selected video codec hevc is not supported, trying h264 instead\n");
- video_codec_to_use = "h264";
- video_codec = VideoCodec::H264;
- video_codec_f = find_h264_encoder(egl.gpu_info.vendor, egl.card_path);
- break;
- }
- case VideoCodec::AV1:
- case VideoCodec::AV1_HDR: {
- fprintf(stderr, "Warning: selected video codec av1 is not supported, trying h264 instead\n");
- video_codec_to_use = "h264";
- video_codec = VideoCodec::H264;
- video_codec_f = find_h264_encoder(egl.gpu_info.vendor, egl.card_path);
- break;
- }
- }
+ if(video_codec_is_hdr(video_codec) && is_portal_capture) {
+ fprintf(stderr, "Warning: portal capture option doesn't support hdr yet (pipewire doesn't support hdr), the video will be tonemapped from hdr to sdr\n");
+ video_codec = hdr_video_codec_to_sdr_video_codec(video_codec);
}
- if(!video_codec_f) {
- const char *video_codec_name = "";
- switch(video_codec) {
- case VideoCodec::H264: {
- video_codec_name = "h264";
- break;
- }
- case VideoCodec::HEVC:
- case VideoCodec::HEVC_HDR: {
- video_codec_name = "hevc";
- break;
- }
- case VideoCodec::AV1:
- case VideoCodec::AV1_HDR: {
- video_codec_name = "av1";
- break;
- }
- }
+ audio_codec = select_audio_codec_with_fallback(audio_codec, file_extension, uses_amix);
+ bool low_power = false;
+ const AVCodec *video_codec_f = select_video_codec_with_fallback(&video_codec, video_codec_to_use, file_extension.c_str(), use_software_video_encoder, &egl, &low_power);
- fprintf(stderr, "Error: your gpu does not support '%s' video codec. If you are sure that your gpu does support '%s' video encoding and you are using an AMD/Intel GPU,\n"
- " then make sure you have installed the GPU specific vaapi packages (intel-media-driver, libva-intel-driver, libva-mesa-driver and linux-firmware).\n"
- " It's also possible that your distro has disabled hardware accelerated video encoding for '%s' video codec.\n"
- " This may be the case on corporate distros such as Manjaro, Fedora or OpenSUSE.\n"
- " You can test this by running 'vainfo | grep VAEntrypointEncSlice' to see if it matches any H264/HEVC profile.\n"
- " On such distros, you need to manually install mesa from source to enable H264/HEVC hardware acceleration, or use a more user friendly distro. Alternatively record with AV1 if supported by your GPU.\n"
- " You can alternatively use the flatpak version of GPU Screen Recorder (https://flathub.org/apps/com.dec05eba.gpu_screen_recorder) which bypasses system issues with patented H264/HEVC codecs.\n"
- " Make sure you have mesa-extra freedesktop runtime installed when using the flatpak (this should be the default), which can be installed with this command:\n"
- " flatpak install --system org.freedesktop.Platform.GL.default//23.08-extra\n"
- " If your GPU doesn't support hardware accelerated video encoding then you can use '-encoder cpu' option to encode with your cpu instead.\n", video_codec_name, video_codec_name, video_codec_name);
- _exit(2);
- }
-
- gsr_capture *capture = create_capture_impl(window_str, screen_region, wayland, &egl, fps, overclock, video_codec, color_range, record_cursor, framerate_mode == FramerateMode::CONTENT, use_software_video_encoder);
+ const gsr_color_depth color_depth = video_codec_to_bit_depth(video_codec);
+ gsr_capture *capture = create_capture_impl(window_str, screen_region, wayland, &egl, fps, video_codec, color_range, record_cursor, use_software_video_encoder, restore_portal_session, portal_session_token_filepath, color_depth);
// (Some?) livestreaming services require at least one audio track to work.
// If not audio is provided then create one silent audio track.
@@ -2462,7 +3246,8 @@ int main(int argc, char **argv) {
const bool hdr = video_codec_is_hdr(video_codec);
const bool low_latency_recording = is_livestream || is_output_piped;
- AVCodecContext *video_codec_context = create_video_codec_context(get_pixel_format(egl.gpu_info.vendor, use_software_video_encoder), quality, fps, video_codec_f, low_latency_recording, egl.gpu_info.vendor, framerate_mode, hdr, color_range, keyint);
+ const enum AVPixelFormat video_pix_fmt = get_pixel_format(video_codec, egl.gpu_info.vendor, use_software_video_encoder);
+ AVCodecContext *video_codec_context = create_video_codec_context(video_pix_fmt, quality, fps, video_codec_f, low_latency_recording, egl.gpu_info.vendor, framerate_mode, hdr, color_range, keyint, use_software_video_encoder, bitrate_mode, video_codec, video_bitrate);
if(replay_buffer_size_secs == -1)
video_stream = create_stream(av_format_context, video_codec_context);
@@ -2486,7 +3271,7 @@ int main(int argc, char **argv) {
_exit(capture_result);
}
- gsr_video_encoder *video_encoder = create_video_encoder(&egl, overclock, hdr, use_software_video_encoder);
+ gsr_video_encoder *video_encoder = create_video_encoder(&egl, overclock, color_depth, use_software_video_encoder, video_codec);
if(!video_encoder) {
fprintf(stderr, "Error: failed to create video encoder\n");
_exit(1);
@@ -2514,9 +3299,9 @@ int main(int argc, char **argv) {
gsr_color_conversion_clear(&color_conversion);
if(use_software_video_encoder) {
- open_video_software(video_codec_context, quality, pixel_format, hdr);
+ open_video_software(video_codec_context, quality, pixel_format, hdr, color_depth, bitrate_mode);
} else {
- open_video_hardware(video_codec_context, quality, very_old_gpu, egl.gpu_info.vendor, pixel_format, hdr);
+ open_video_hardware(video_codec_context, quality, very_old_gpu, egl.gpu_info.vendor, pixel_format, hdr, color_depth, bitrate_mode, video_codec, low_power);
}
if(video_stream)
avcodec_parameters_from_context(video_stream->codecpar, video_codec_context);
@@ -2562,7 +3347,7 @@ int main(int argc, char **argv) {
const double audio_startup_time_seconds = force_no_audio_offset ? 0 : audio_codec_get_desired_delay(audio_codec, fps);// * ((double)audio_codec_context->frame_size / 1024.0);
const double num_audio_frames_shift = audio_startup_time_seconds / timeout_sec;
- std::vector<AudioDevice> audio_devices;
+ std::vector<AudioDevice> audio_track_audio_devices;
for(size_t i = 0; i < merged_audio_inputs.audio_inputs.size(); ++i) {
auto &audio_input = merged_audio_inputs.audio_inputs[i];
AVFilterContext *src_ctx = nullptr;
@@ -2586,13 +3371,13 @@ int main(int argc, char **argv) {
audio_device.frame = create_audio_frame(audio_codec_context);
audio_device.frame->pts = -audio_codec_context->frame_size * num_audio_frames_shift;
- audio_devices.push_back(std::move(audio_device));
+ audio_track_audio_devices.push_back(std::move(audio_device));
}
AudioTrack audio_track;
audio_track.codec_context = audio_codec_context;
audio_track.stream = audio_stream;
- audio_track.audio_devices = std::move(audio_devices);
+ audio_track.audio_devices = std::move(audio_track_audio_devices);
audio_track.graph = graph;
audio_track.sink = sink;
audio_track.stream_index = audio_stream_index;
@@ -2628,7 +3413,7 @@ int main(int argc, char **argv) {
}
double fps_start_time = clock_get_monotonic_seconds();
- double frame_timer_start = fps_start_time - target_fps; // We want to capture the first frame immediately
+ double frame_timer_start = fps_start_time;
int fps_counter = 0;
int damage_fps_counter = 0;
@@ -2707,7 +3492,7 @@ int main(int argc, char **argv) {
if(paused) {
if(!audio_device.sound_device.handle)
- usleep(timeout_ms * 1000);
+ av_usleep(timeout_ms * 1000);
continue;
}
@@ -2770,7 +3555,7 @@ int main(int argc, char **argv) {
}
if(!audio_device.sound_device.handle)
- usleep(timeout_ms * 1000);
+ av_usleep(timeout_ms * 1000);
if(got_audio_data) {
// TODO: Instead of converting audio, get float audio from alsa. Or does alsa do conversion internally to get this format?
@@ -2807,57 +3592,105 @@ int main(int argc, char **argv) {
}
}
+ std::thread amix_thread;
+ if(uses_amix) {
+ amix_thread = std::thread([&]() {
+ AVFrame *aframe = av_frame_alloc();
+ while(running) {
+ {
+ std::lock_guard<std::mutex> lock(audio_filter_mutex);
+ for(AudioTrack &audio_track : audio_tracks) {
+ if(!audio_track.sink)
+ continue;
+
+ int err = 0;
+ while ((err = av_buffersink_get_frame(audio_track.sink, aframe)) >= 0) {
+ aframe->pts = audio_track.pts;
+ err = avcodec_send_frame(audio_track.codec_context, aframe);
+ if(err >= 0){
+ // TODO: Move to separate thread because this could write to network (for example when livestreaming)
+ receive_frames(audio_track.codec_context, audio_track.stream_index, audio_track.stream, aframe->pts, av_format_context, record_start_time, frame_data_queue, replay_buffer_size_secs, frames_erased, write_output_mutex, paused_time_offset);
+ } else {
+ fprintf(stderr, "Failed to encode audio!\n");
+ }
+ av_frame_unref(aframe);
+ audio_track.pts += audio_track.codec_context->frame_size;
+ }
+ }
+ }
+ av_usleep(5 * 1000); // 5 milliseconds
+ }
+ av_frame_free(&aframe);
+ });
+ }
+
// Set update_fps to 24 to test if duplicate/delayed frames cause video/audio desync or too fast/slow video.
const double update_fps = fps + 190;
bool should_stop_error = false;
- AVFrame *aframe = av_frame_alloc();
-
int64_t video_pts_counter = 0;
int64_t video_prev_pts = 0;
+ bool hdr_metadata_set = false;
+
+ double damage_timeout_seconds = framerate_mode == FramerateMode::CONTENT ? 0.5 : 0.1;
+ damage_timeout_seconds = std::max(damage_timeout_seconds, target_fps);
+
+ bool use_damage_tracking = false;
+ gsr_damage damage;
+ memset(&damage, 0, sizeof(damage));
+ if(gsr_egl_get_display_server(&egl) == GSR_DISPLAY_SERVER_X11) {
+ gsr_damage_init(&damage, &egl, record_cursor);
+ use_damage_tracking = true;
+ }
+
+ if(is_monitor_capture)
+ gsr_damage_set_target_monitor(&damage, window_str.c_str());
+
while(running) {
- double frame_start = clock_get_monotonic_seconds();
+ const double frame_start = clock_get_monotonic_seconds();
+
+ while(gsr_egl_process_event(&egl)) {
+ gsr_damage_on_event(&damage, gsr_egl_get_event_data(&egl));
+ gsr_capture_on_event(capture, &egl);
+ }
+ gsr_damage_tick(&damage);
+ gsr_capture_tick(capture);
+
+ if(!is_monitor_capture) {
+ Window damage_target_window = 0;
+ if(capture->get_window_id)
+ damage_target_window = capture->get_window_id(capture);
+
+ if(damage_target_window != 0)
+ gsr_damage_set_target_window(&damage, damage_target_window);
+ }
- gsr_capture_tick(capture, video_codec_context);
should_stop_error = false;
if(gsr_capture_should_stop(capture, &should_stop_error)) {
running = 0;
break;
}
- // TODO: Move to another thread, since this shouldn't be locked to video encoding fps
- {
- std::lock_guard<std::mutex> lock(audio_filter_mutex);
- for(AudioTrack &audio_track : audio_tracks) {
- if(!audio_track.sink)
- continue;
+ bool damaged = false;
+ if(use_damage_tracking)
+ damaged = gsr_damage_is_damaged(&damage);
+ else if(capture->is_damaged)
+ damaged = capture->is_damaged(capture);
+ else
+ damaged = true;
- int err = 0;
- while ((err = av_buffersink_get_frame(audio_track.sink, aframe)) >= 0) {
- aframe->pts = audio_track.pts;
- err = avcodec_send_frame(audio_track.codec_context, aframe);
- if(err >= 0){
- // TODO: Move to separate thread because this could write to network (for example when livestreaming)
- receive_frames(audio_track.codec_context, audio_track.stream_index, audio_track.stream, aframe->pts, av_format_context, record_start_time, frame_data_queue, replay_buffer_size_secs, frames_erased, write_output_mutex, paused_time_offset);
- } else {
- fprintf(stderr, "Failed to encode audio!\n");
- }
- av_frame_unref(aframe);
- audio_track.pts += audio_track.codec_context->frame_size;
- }
- }
- }
+ // TODO: Readd wayland sync warning when removing this
+ if(framerate_mode != FramerateMode::CONTENT)
+ damaged = true;
- const bool damaged = !capture->is_damaged || capture->is_damaged(capture);
- if(damaged) {
+ if(damaged)
++damage_fps_counter;
- }
++fps_counter;
- double time_now = clock_get_monotonic_seconds();
- double frame_timer_elapsed = time_now - frame_timer_start;
- double elapsed = time_now - fps_start_time;
+ const double time_now = clock_get_monotonic_seconds();
+ const double frame_timer_elapsed = time_now - frame_timer_start;
+ const double elapsed = time_now - fps_start_time;
if (elapsed >= 1.0) {
if(verbose) {
fprintf(stderr, "update fps: %d, damage fps: %d\n", fps_counter, damage_fps_counter);
@@ -2868,10 +3701,11 @@ int main(int argc, char **argv) {
}
double frame_time_overflow = frame_timer_elapsed - target_fps;
- if (frame_time_overflow >= 0.0 && damaged) {
+ if ((frame_time_overflow >= 0.0 || video_pts_counter == 0) && damaged) {
+ gsr_damage_clear(&damage);
if(capture->clear_damage)
capture->clear_damage(capture);
- frame_time_overflow = std::min(frame_time_overflow, target_fps);
+ frame_time_overflow = std::min(std::max(0.0, frame_time_overflow), target_fps);
frame_timer_start = time_now - frame_time_overflow;
const double this_video_frame_time = clock_get_monotonic_seconds() - paused_time_offset;
@@ -2879,8 +3713,13 @@ int main(int argc, char **argv) {
const int num_frames = framerate_mode == FramerateMode::CONSTANT ? std::max((int64_t)0LL, expected_frames - video_pts_counter) : 1;
if(num_frames > 0 && !paused) {
+ egl.glClear(0);
gsr_capture_capture(capture, video_frame, &color_conversion);
- gsr_video_encoder_copy_textures_to_frame(video_encoder, video_frame);
+ gsr_egl_swap_buffers(&egl);
+ gsr_video_encoder_copy_textures_to_frame(video_encoder, video_frame, &color_conversion);
+
+ if(hdr && !hdr_metadata_set && replay_buffer_size_secs == -1 && add_hdr_metadata_to_video_stream(capture, video_stream))
+ hdr_metadata_set = true;
// TODO: Check if duplicate frame can be saved just by writing it with a different pts instead of sending it again
for(int i = 0; i < num_frames; ++i) {
@@ -2904,7 +3743,6 @@ int main(int argc, char **argv) {
}
}
- gsr_capture_capture_end(capture, video_frame);
video_pts_counter += num_frames;
}
}
@@ -2935,14 +3773,14 @@ int main(int argc, char **argv) {
if(save_replay == 1 && !save_replay_thread.valid() && replay_buffer_size_secs != -1) {
save_replay = 0;
- save_replay_async(video_codec_context, VIDEO_STREAM_INDEX, audio_tracks, frame_data_queue, frames_erased, filename, container_format, file_extension, write_output_mutex, make_folders);
+ save_replay_async(video_codec_context, VIDEO_STREAM_INDEX, audio_tracks, frame_data_queue, frames_erased, filename, container_format, file_extension, write_output_mutex, date_folders, hdr, capture);
}
double frame_end = clock_get_monotonic_seconds();
double frame_sleep_fps = 1.0 / update_fps;
double sleep_time = frame_sleep_fps - (frame_end - frame_start);
if(sleep_time > 0.0)
- usleep(sleep_time * 1000.0 * 1000.0);
+ av_usleep(sleep_time * 1000.0 * 1000.0);
}
running = 0;
@@ -2964,7 +3802,8 @@ int main(int argc, char **argv) {
}
}
- av_frame_free(&aframe);
+ if(amix_thread.joinable())
+ amix_thread.join();
if (replay_buffer_size_secs == -1 && av_write_trailer(av_format_context) != 0) {
fprintf(stderr, "Failed to write trailer\n");
@@ -2973,6 +3812,7 @@ int main(int argc, char **argv) {
if(replay_buffer_size_secs == -1 && !(output_format->flags & AVFMT_NOFILE))
avio_close(av_format_context->pb);
+ gsr_damage_deinit(&damage);
gsr_color_conversion_deinit(&color_conversion);
gsr_video_encoder_destroy(video_encoder, video_codec_context);
gsr_capture_destroy(capture, video_codec_context);
@@ -2986,7 +3826,6 @@ int main(int argc, char **argv) {
}
//av_frame_free(&video_frame);
- free((void*)window_str);
free(empty_audio);
// We do an _exit here because cuda uses at_exit to do _something_ that causes the program to freeze,
// but only on some nvidia driver versions on some gpus (RTX?), and _exit exits the program without calling