diff options
Diffstat (limited to 'src/main.cpp')
-rw-r--r-- | src/main.cpp | 1189 |
1 files changed, 900 insertions, 289 deletions
diff --git a/src/main.cpp b/src/main.cpp index 020deb7..f987f0e 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -6,9 +6,16 @@ extern "C" { #include "../include/capture/portal.h" #include "../include/dbus.h" #endif -#include "../include/encoder/video/cuda.h" +#ifdef GSR_APP_AUDIO +#include "../include/pipewire_audio.h" +#endif +#include "../include/encoder/video/nvenc.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" @@ -28,6 +35,7 @@ extern "C" { #include <sys/stat.h> #include <unistd.h> #include <sys/wait.h> +#include <inttypes.h> #include <libgen.h> #include "../include/sound.hpp" @@ -100,7 +108,9 @@ enum class VideoCodec { AV1_HDR, AV1_10BIT, VP8, - VP9 + VP9, + H264_VULKAN, + HEVC_VULKAN }; enum class AudioCodec { @@ -122,7 +132,8 @@ enum class FramerateMode { enum class BitrateMode { QP, - VBR + VBR, + CBR }; static int x11_error_handler(Display*, XErrorEvent*) { @@ -134,6 +145,7 @@ 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: @@ -144,6 +156,7 @@ static bool video_codec_is_hdr(VideoCodec video_codec) { } static VideoCodec hdr_video_codec_to_sdr_video_codec(VideoCodec video_codec) { + // TODO: Vulkan switch(video_codec) { case VideoCodec::HEVC_HDR: return VideoCodec::HEVC; @@ -155,6 +168,7 @@ static VideoCodec hdr_video_codec_to_sdr_video_codec(VideoCodec 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: @@ -167,6 +181,7 @@ static gsr_color_depth video_codec_to_bit_depth(VideoCodec video_codec) { } // static bool video_codec_is_hevc(VideoCodec video_codec) { +// TODO: Vulkan // switch(video_codec) { // case VideoCodec::HEVC: // case VideoCodec::HEVC_HDR: @@ -178,6 +193,7 @@ static gsr_color_depth video_codec_to_bit_depth(VideoCodec video_codec) { // } static bool video_codec_is_av1(VideoCodec video_codec) { + // TODO: Vulkan switch(video_codec) { case VideoCodec::AV1: case VideoCodec::AV1_HDR: @@ -188,6 +204,16 @@ static bool video_codec_is_av1(VideoCodec video_codec) { } } +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; + } +} + struct PacketData { PacketData() {} PacketData(const PacketData&) = delete; @@ -379,7 +405,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) { @@ -466,7 +492,7 @@ static int vbr_get_quality_parameter(AVCodecContext *codec_context, VideoQuality 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 use_software_video_encoder, BitrateMode bitrate_mode) { + 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); @@ -510,7 +536,13 @@ static AVCodecContext *create_video_codec_context(AVPixelFormat pix_fmt, if(codec->id == AV_CODEC_ID_HEVC) codec_context->codec_tag = MKTAG('h', 'v', 'c', '1'); // QuickTime on MacOS requires this or the video wont be playable - if(bitrate_mode == BitrateMode::VBR) { + 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 = 0;//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: @@ -536,15 +568,17 @@ static AVCodecContext *create_video_codec_context(AVPixelFormat pix_fmt, } codec_context->rc_max_rate = codec_context->bit_rate; - codec_context->rc_min_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->rc_initial_buffer_occupancy = codec_context->bit_rate;//codec_context->bit_rate * 1000; + } else { + //codec_context->rc_buffer_size = 50000 * 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) { + if(!use_software_video_encoder && vendor != GSR_GPU_VENDOR_NVIDIA && bitrate_mode != BitrateMode::CBR) { // 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) { @@ -556,7 +590,7 @@ static AVCodecContext *create_video_codec_context(AVPixelFormat pix_fmt, codec_context->global_quality = 120 * quality_multiply; break; case VideoQuality::VERY_HIGH: - codec_context->global_quality = 100 * quality_multiply; + codec_context->global_quality = 115 * quality_multiply; break; case VideoQuality::ULTRA: codec_context->global_quality = 90 * quality_multiply; @@ -571,7 +605,7 @@ static AVCodecContext *create_video_codec_context(AVPixelFormat pix_fmt, codec_context->global_quality = 30 * quality_multiply; break; case VideoQuality::VERY_HIGH: - codec_context->global_quality = 20 * quality_multiply; + codec_context->global_quality = 25 * quality_multiply; break; case VideoQuality::ULTRA: codec_context->global_quality = 10 * quality_multiply; @@ -586,7 +620,7 @@ static AVCodecContext *create_video_codec_context(AVPixelFormat pix_fmt, codec_context->global_quality = 30 * quality_multiply; break; case VideoQuality::VERY_HIGH: - codec_context->global_quality = 20 * quality_multiply; + codec_context->global_quality = 25 * quality_multiply; break; case VideoQuality::ULTRA: codec_context->global_quality = 10 * quality_multiply; @@ -601,10 +635,35 @@ 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; - if(bitrate_mode == BitrateMode::QP) - av_opt_set(codec_context->priv_data, "rc_mode", "CQP", 0); - else - av_opt_set(codec_context->priv_data, "rc_mode", "VBR", 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; } @@ -654,6 +713,50 @@ static AVFrame* create_audio_frame(AVCodecContext *audio_codec_context) { return frame; } +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; @@ -681,10 +784,10 @@ static void video_software_set_qp(AVCodecContext *codec_context, VideoQuality vi av_dict_set_int(options, "qp", 30 * qp_multiply, 0); break; case VideoQuality::VERY_HIGH: - av_dict_set_int(options, "qp", 23 * qp_multiply, 0); + av_dict_set_int(options, "qp", 25 * qp_multiply, 0); break; case VideoQuality::ULTRA: - av_dict_set_int(options, "qp", 20 * qp_multiply, 0); + av_dict_set_int(options, "qp", 22 * qp_multiply, 0); break; } } else { @@ -712,14 +815,9 @@ static void open_video_software(AVCodecContext *codec_context, VideoQuality vide if(bitrate_mode == BitrateMode::QP) video_software_set_qp(codec_context, video_quality, hdr, &options); - av_dict_set(&options, "preset", "medium", 0); - 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); - } - // TODO: If streaming or piping output set this to zerolatency - av_dict_set(&options, "tune", "fastdecode", 0); + av_dict_set(&options, "preset", "veryfast", 0); + av_dict_set(&options, "tune", "film", 0); + dict_set_profile(codec_context, GSR_GPU_VENDOR_INTEL, color_depth, &options); if(codec_context->codec_id == AV_CODEC_ID_H264) { av_dict_set(&options, "coder", "cabac", 0); // TODO: cavlc is faster than cabac but worse compression. Which to use? @@ -734,6 +832,38 @@ static void open_video_software(AVCodecContext *codec_context, VideoQuality vide } } +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; @@ -748,7 +878,7 @@ static void video_hardware_set_qp(AVCodecContext *codec_context, VideoQuality vi 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); + av_dict_set_int(options, "qp", 27 * qp_multiply, 0); break; case VideoQuality::ULTRA: av_dict_set_int(options, "qp", 22 * qp_multiply, 0); @@ -757,16 +887,16 @@ static void video_hardware_set_qp(AVCodecContext *codec_context, VideoQuality vi } 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", 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", 23 * qp_multiply, 0); + av_dict_set_int(options, "qp", 27 * qp_multiply, 0); break; case VideoQuality::ULTRA: - av_dict_set_int(options, "qp", 20 * qp_multiply, 0); + av_dict_set_int(options, "qp", 22 * qp_multiply, 0); break; } } else if(codec_context->codec_id == AV_CODEC_ID_HEVC) { @@ -778,7 +908,7 @@ static void video_hardware_set_qp(AVCodecContext *codec_context, VideoQuality vi 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); + av_dict_set_int(options, "qp", 27 * qp_multiply, 0); break; case VideoQuality::ULTRA: av_dict_set_int(options, "qp", 22 * qp_multiply, 0); @@ -793,31 +923,29 @@ static void video_hardware_set_qp(AVCodecContext *codec_context, VideoQuality vi 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); + av_dict_set_int(options, "qp", 27 * qp_multiply, 0); break; case VideoQuality::ULTRA: av_dict_set_int(options, "qp", 22 * qp_multiply, 0); break; } } - - av_dict_set(options, "rc", "constqp", 0); } else { if(codec_context->codec_id == AV_CODEC_ID_AV1) { // Using global_quality option } 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", 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", 23 * qp_multiply, 0); + av_dict_set_int(options, "qp", 27 * qp_multiply, 0); break; case VideoQuality::ULTRA: - av_dict_set_int(options, "qp", 20 * qp_multiply, 0); + av_dict_set_int(options, "qp", 22 * qp_multiply, 0); break; } } else if(codec_context->codec_id == AV_CODEC_ID_HEVC) { @@ -829,7 +957,7 @@ static void video_hardware_set_qp(AVCodecContext *codec_context, VideoQuality vi 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); + av_dict_set_int(options, "qp", 27 * qp_multiply, 0); break; case VideoQuality::ULTRA: av_dict_set_int(options, "qp", 22 * qp_multiply, 0); @@ -844,51 +972,54 @@ static void video_hardware_set_qp(AVCodecContext *codec_context, VideoQuality vi 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); + av_dict_set_int(options, "qp", 27 * qp_multiply, 0); break; case VideoQuality::ULTRA: av_dict_set_int(options, "qp", 22 * qp_multiply, 0); break; } } - - av_dict_set(options, "rc_mode", "CQP", 0); } } -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) { +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) { + if(bitrate_mode == BitrateMode::QP) video_hardware_set_qp(codec_context, video_quality, vendor, hdr, &options); - } else { - if(vendor == GSR_GPU_VENDOR_NVIDIA) { - av_dict_set(&options, "rc", "vbr", 0); - } else { - av_dict_set(&options, "rc_mode", "VBR", 0); - } - } + + video_set_rc(video_codec, vendor, bitrate_mode, &options); + + // TODO: Enable multipass + + // TODO: Set "usage" option to "record"/"stream" and "content" option to "rendered" for vulkan encoding if(vendor == GSR_GPU_VENDOR_NVIDIA) { + // TODO: These dont seem to be necessary + // av_dict_set_int(&options, "zerolatency", 1, 0); + // if(codec_context->codec_id == AV_CODEC_ID_AV1) { + // av_dict_set(&options, "tune", "ll", 0); + // } else if(codec_context->codec_id == AV_CODEC_ID_H264 || codec_context->codec_id == AV_CODEC_ID_HEVC) { + // av_dict_set(&options, "preset", "llhq", 0); + // av_dict_set(&options, "tune", "ll", 0); + // } av_dict_set(&options, "tune", "hq", 0); - // TODO: Enable multipass + dict_set_profile(codec_context, vendor, color_depth, &options); if(codec_context->codec_id == AV_CODEC_ID_H264) { // TODO: h264 10bit? - switch(pixel_format) { - case PixelFormat::YUV420: - av_dict_set(&options, "profile", "high", 0); - break; - case PixelFormat::YUV444: - av_dict_set(&options, "profile", "high444p", 0); - break; - } + // 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) { - if(color_depth == GSR_COLOR_DEPTH_10_BITS) - av_dict_set_int(&options, "highbitdepth", 1, 0); - switch(pixel_format) { case PixelFormat::YUV420: av_dict_set(&options, "rgb_mode", "yuv420", 0); @@ -899,34 +1030,20 @@ static void open_video_hardware(AVCodecContext *codec_context, VideoQuality vide } } else if(codec_context->codec_id == AV_CODEC_ID_HEVC) { //av_dict_set(&options, "pix_fmt", "yuv420p16le", 0); - 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); } } else { // TODO: More quality options - //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); + //av_dict_set_int(&options, "async_depth", 8, 0); if(codec_context->codec_id == AV_CODEC_ID_H264) { - // TODO: - 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); // 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_int(&options, "profile", AV_PROFILE_AV1_MAIN, 0); // TODO: use professional instead? av_dict_set(&options, "tier", "main", 0); } 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); - if(hdr) av_dict_set(&options, "sei", "hdr", 0); } @@ -950,7 +1067,12 @@ 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|portal> [-c <container_format>] [-s WxH] -f <fps> [-a <audio_input>] [-q <quality>] [-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] [-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); +#ifdef GSR_APP_AUDIO + const char *app_audio_options = " [-aa <application_name>] [-aai <application_name>] "; +#else + const char *app_audio_options = ""; +#endif + fprintf(stderr, "usage: %s -w <window_id|monitor|focused|portal> [-c <container_format>] [-s WxH] -f <fps> [-a <audio_input>]%s[-q <quality>] [-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, app_audio_options); } // TODO: Update with portal info @@ -961,18 +1083,23 @@ static void usage_full() { fprintf(stderr, "\n"); fprintf(stderr, "OPTIONS:\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 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, " If this is \"screen\" or \"screen-direct-force\" then all monitors are recorded on Nvidia X11.\n"); + fprintf(stderr, " 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\n"); + fprintf(stderr, " 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, " Run GPU Screen Recorder with the --list-capture-options option to list valid values for this option.\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, 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, " -s The output resolution limit of the video in the format WxH, for example 1920x1080. If this is 0x0 then the original resolution is used. Optional, except when -w is \"focused\".\n"); + fprintf(stderr, " Note: the captured content is scaled to this size. The output resolution might not be exactly as specified by this option. The original aspect ratio is respected so the resolution will match that.\n"); + fprintf(stderr, " The video encoder might also need to add padding, which will result in black bars on the sides of the video. This is especially an issue on AMD.\n"); fprintf(stderr, "\n"); fprintf(stderr, " -f Frame rate to record at. Recording will only capture frames at this target frame rate.\n"); fprintf(stderr, " For constant frame rate mode this option is the frame rate every frame will be captured at and if the capture frame rate is below this target frame rate then the frames will be duplicated.\n"); @@ -983,18 +1110,41 @@ static void usage_full() { 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, " If the audio device is an empty string then the argument is ignored.\n"); fprintf(stderr, " Optional, no audio track is added by default.\n"); + fprintf(stderr, " Run GPU Screen Recorder with the --list-audio-devices option to list valid audio devices to use with this -a option.\n"); + fprintf(stderr, "\n"); +#ifdef GSR_APP_AUDIO + fprintf(stderr, " -aa Application to record audio from (case-insensitive). Can be specified multiple times. Each time this is specified a new audio track is added for the specified application audio.\n"); + fprintf(stderr, " Multiple application audio can be merged into one audio track by using \"|\" as a separator into one -a argument, for example: -a \"firefox|csgo\".\n"); + fprintf(stderr, " If the application name is an empty string then the argument is ignored.\n"); + fprintf(stderr, " Optional, no application audio is added by default.\n"); + fprintf(stderr, " Note: this option is only available when the sound server on the system is PipeWire.\n"); + fprintf(stderr, " Run GPU Screen Recorder with the --list-application-audio option to list valid application names to use with this -aa option.\n"); + fprintf(stderr, " It's possible to use an application name that is not listed in --list-application-audio, for example when trying to record audio from an application that hasn't started yet.\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, " -aai Record audio from all applications except the ones specified with this option (case-insensitive). Can be specified multiple times.\n"); + fprintf(stderr, " Each time this is specified a new audio track is added that records all applications except the ones specified.\n"); + fprintf(stderr, " Multiple application audio can be merged into one audio track by using \"|\" as a separator into one -a argument, for example: -a \"firefox|csgo\".\n"); + fprintf(stderr, " If the application name is an empty string then the argument is ignored.\n"); + fprintf(stderr, " Optional, no application audio is added by default.\n"); + fprintf(stderr, " Note: this option is only available when the sound server on the system is PipeWire.\n"); + fprintf(stderr, " Run GPU Screen Recorder with the --list-application-audio option to list valid application names to use with this -aai option.\n"); + fprintf(stderr, " It's possible to use an application name that is not listed in --list-application-audio, for example when trying to record audio and the target application hasn't started yet.\n"); + fprintf(stderr, "\n"); +#endif + fprintf(stderr, " -q Video quality. Should be either 'medium', 'high', 'very_high' or 'ultra' when using '-bm qp' or '-bm vbr' options, and '-bm qp' is the default option used.\n"); + fprintf(stderr, " 'high' is the recommended option when live streaming or when you have a slower harddrive.\n"); + fprintf(stderr, " When using '-bm cbr' option then this is option is instead used to specify the video bitrate in kbps.\n"); + fprintf(stderr, " Optional when using '-bm qp' or '-bm vbr' options, set to 'very_high' be default.\n"); + fprintf(stderr, " Required when using '-bm cbr' option.\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', 'vp8', 'vp9', 'hevc_hdr', 'av1_hdr', 'hevc_10bit' or 'av1_10bit'. Optional, set to 'auto' by default which defaults to 'h264'.\n"); - fprintf(stderr, " Forcefully set to 'h264' if the file container type is 'flv'.\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"); @@ -1004,8 +1154,8 @@ static void usage_full() { 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"); @@ -1015,9 +1165,9 @@ static void usage_full() { 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 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, " -bm Bitrate mode. Should be either 'auto', 'qp' (constant quality) or 'vbr' (variable bitrate). Optional, set to 'auto' by default which defaults to 'qp' on all devices\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, " 'vbr' option is not supported when using '-encoder cpu' option.\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"); @@ -1049,21 +1199,40 @@ static void usage_full() { 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, " Which device should be used for video encoding. Should either be 'gpu' or 'cpu'. 'cpu' option currently only work with h264 codec option (-k).\n"); fprintf(stderr, " Optional, set to 'gpu' by default.\n"); fprintf(stderr, "\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, " List info about the system. 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-capture-options\n"); + fprintf(stderr, " List available capture options. Lists capture options in the following format (prints them to stdout and exits):\n"); + fprintf(stderr, " <option>\n"); + fprintf(stderr, " <monitor_name>|<resolution>\n"); + fprintf(stderr, " For example:\n"); + fprintf(stderr, " window\n"); + fprintf(stderr, " DP-1|1920x1080\n"); + fprintf(stderr, " The <option> and <monitor_name> is the name that can be passed to GPU Screen Recorder with the -w option.\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, " List audio devices. 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, " alsa_output.pci-0000_0c_00.4.iec958-stereo|Monitor of Starship/Matisse HD Audio Controller Digital Stereo (IEC958)\n"); + fprintf(stderr, " The <audio_device_name> is the name that can be passed to GPU Screen Recorder with the -a option.\n"); + fprintf(stderr, "\n"); +#ifdef GSR_APP_AUDIO + fprintf(stderr, " --list-application-audio\n"); + fprintf(stderr, " Lists application that you can record from (with the -aa or -aai option) (prints them to stdout and exits), for example:\n"); + fprintf(stderr, " firefox\n"); + fprintf(stderr, " csgo\n"); + fprintf(stderr, " These names are the application audio names that can be passed to GPU Screen Recorder with the -aa option.\n"); + fprintf(stderr, "\n"); +#endif fprintf(stderr, " --version\n"); fprintf(stderr, " Print version (%s) and exit\n", GSR_VERSION); fprintf(stderr, "\n"); @@ -1072,7 +1241,7 @@ static void usage_full() { fprintf(stderr, " In replay mode this has to be a directory instead of a file.\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, " -v Prints fps and damage info once per second. Optional, set to 'yes' by default.\n"); fprintf(stderr, "\n"); fprintf(stderr, " -h, --help\n"); fprintf(stderr, " Show this help.\n"); @@ -1084,10 +1253,16 @@ static void usage_full() { fprintf(stderr, "\n"); fprintf(stderr, "EXAMPLES:\n"); 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 -a default_input -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 -q 15000 -o \"$HOME/Videos/video.mp4\"\n", program_name); +#ifdef GSR_APP_AUDIO + fprintf(stderr, " %s -w screen -f 60 -aa \"firefox|csgo\" -o \"$HOME/Videos/video.mp4\"\n", program_name); + fprintf(stderr, " %s -w screen -f 60 -aai \"firefox|csgo\" -o \"$HOME/Videos/video.mp4\"\n", program_name); +#endif //fprintf(stderr, " gpu-screen-recorder -w screen -f 60 -q ultra -pixfmt yuv444 -o video.mp4\n"); _exit(1); } @@ -1244,6 +1419,7 @@ struct AudioDevice { AVFilterContext *src_filter_ctx = nullptr; AVFrame *frame = nullptr; std::thread thread; // TODO: Instead of having a thread for each track, have one thread for all threads and read the data with non-blocking read + std::string combined_sink_name; }; // TODO: Cleanup @@ -1260,8 +1436,14 @@ struct AudioTrack { 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 if(!light_metadata || !mastering_display_metadata) { if(light_metadata) @@ -1282,17 +1464,24 @@ static bool add_hdr_metadata_to_video_stream(gsr_capture *cap, AVStream *video_s // TODO: More error checking #if LIBAVCODEC_VERSION_INT < AV_VERSION_INT(60, 31, 102) - const bool added_light_metadata = av_stream_add_side_data(video_stream, AV_PKT_DATA_CONTENT_LIGHT_LEVEL, (uint8_t*)light_metadata, light_metadata_size); + 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 - 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); + 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 added_display_metadata = av_stream_add_side_data(video_stream, AV_PKT_DATA_MASTERING_DISPLAY_METADATA, (uint8_t*)mastering_display_metadata, sizeof(*mastering_display_metadata)); + 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 - 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, sizeof(*mastering_display_metadata), 0); + 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; } @@ -1453,16 +1642,39 @@ static void split_string(const std::string &str, char delimiter, std::function<b } } -static std::vector<AudioInput> parse_audio_input_arg(const char *str) { +static const AudioInput* get_audio_device_by_name(const std::vector<AudioInput> &audio_inputs, const std::string &name) { + for(const auto &audio_input : audio_inputs) { + if(audio_input.name == name) + return &audio_input; + } + return nullptr; +} + +static std::vector<AudioInput> parse_audio_input_arg(const char *str, const AudioDevices &audio_devices) { std::vector<AudioInput> audio_inputs; - split_string(str, '|', [&audio_inputs](const char *sub, size_t size) { + split_string(str, '|', [&](const char *sub, size_t size) { AudioInput audio_input; audio_input.name.assign(sub, size); + + const bool name_is_existing_audio_device = get_audio_device_by_name(audio_devices.audio_inputs, audio_input.name); const size_t index = audio_input.name.find('/'); - if(index != std::string::npos) { + if(!name_is_existing_audio_device && index != std::string::npos) { audio_input.description = audio_input.name.substr(0, index); audio_input.name.erase(audio_input.name.begin(), audio_input.name.begin() + index + 1); } + + audio_inputs.push_back(std::move(audio_input)); + return true; + }); + return audio_inputs; +} + +static std::vector<AudioInput> parse_app_audio_input_arg(const char *str) { + std::vector<AudioInput> audio_inputs; + split_string(str, '|', [&](const char *sub, size_t size) { + AudioInput audio_input; + audio_input.name.assign(sub, size); + audio_input.description = audio_input.name; audio_inputs.push_back(std::move(audio_input)); return true; }); @@ -1592,7 +1804,7 @@ 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) { +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) { @@ -1603,6 +1815,14 @@ static gsr_video_encoder* create_video_encoder(gsr_egl *egl, bool overclock, gsr 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(¶ms); + return video_encoder; + } + switch(egl->gpu_info.vendor) { case GSR_GPU_VENDOR_AMD: case GSR_GPU_VENDOR_INTEL: { @@ -1613,11 +1833,11 @@ static gsr_video_encoder* create_video_encoder(gsr_egl *egl, bool overclock, gsr break; } case GSR_GPU_VENDOR_NVIDIA: { - gsr_video_encoder_cuda_params params; + gsr_video_encoder_nvenc_params params; params.egl = egl; params.overclock = overclock; params.color_depth = color_depth; - video_encoder = gsr_video_encoder_cuda_create(¶ms); + video_encoder = gsr_video_encoder_nvenc_create(¶ms); break; } } @@ -1625,6 +1845,28 @@ static gsr_video_encoder* create_video_encoder(gsr_egl *egl, bool overclock, gsr 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) @@ -1689,65 +1931,84 @@ static const AVCodec* get_ffmpeg_video_codec(VideoCodec video_codec, gsr_gpu_ven 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_gpu_vendor vendor) { +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 = false; + supported_video_codecs->h264.supported = false; } if(!get_ffmpeg_video_codec(VideoCodec::HEVC, vendor)) { - supported_video_codecs->hevc = false; - supported_video_codecs->hevc_hdr = false; - supported_video_codecs->hevc_10bit = false; + 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 = false; - supported_video_codecs->av1_hdr = false; - supported_video_codecs->av1_10bit = false; + 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 = false; + supported_video_codecs->vp8.supported = false; } if(!get_ffmpeg_video_codec(VideoCodec::VP9, vendor)) { - supported_video_codecs->vp9 = false; + 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_video_encoder *video_encoder = create_video_encoder(egl, false, GSR_COLOR_DEPTH_8_BITS, false); - if(!video_encoder) - return; - - gsr_supported_video_codecs supported_video_codecs = gsr_video_encoder_get_supported_codecs(video_encoder, false); - set_supported_video_codecs_ffmpeg(&supported_video_codecs, egl->gpu_info.vendor); + gsr_supported_video_codecs supported_video_codecs; + get_supported_video_codecs(egl, VideoCodec::H264, false, false, &supported_video_codecs); - if(supported_video_codecs.h264) + 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) + if(supported_video_codecs.hevc.supported) puts("hevc"); - if(supported_video_codecs.hevc_hdr && wayland) + if(supported_video_codecs.hevc_hdr.supported && wayland) puts("hevc_hdr"); - if(supported_video_codecs.hevc_10bit) + if(supported_video_codecs.hevc_10bit.supported) puts("hevc_10bit"); - if(supported_video_codecs.av1) + if(supported_video_codecs.av1.supported) puts("av1"); - if(supported_video_codecs.av1_hdr && wayland) + if(supported_video_codecs.av1_hdr.supported && wayland) puts("av1_hdr"); - if(supported_video_codecs.av1_10bit) + if(supported_video_codecs.av1_10bit.supported) puts("av1_10bit"); - if(supported_video_codecs.vp8) + if(supported_video_codecs.vp8.supported) puts("vp8"); - if(supported_video_codecs.vp9) + 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) { @@ -1887,11 +2148,78 @@ static void list_audio_devices_command() { _exit(0); } -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, +static bool app_audio_query_callback(const char *app_name, void*) { + puts(app_name); + return true; +} + +static void list_application_audio_command() { +#ifdef GSR_APP_AUDIO + gsr_pipewire_audio audio; + if(gsr_pipewire_audio_init(&audio)) { + gsr_pipewire_audio_for_each_app(&audio, app_audio_query_callback, NULL); + gsr_pipewire_audio_deinit(&audio); + } +#endif + + fflush(stdout); + _exit(0); +} + +static void list_capture_options_command() { + bool wayland = false; + Display *dpy = XOpenDisplay(nullptr); + if (!dpy) { + wayland = true; + fprintf(stderr, "Warning: failed to connect to the X server. Assuming wayland is running without Xwayland\n"); + } + + XSetErrorHandler(x11_error_handler); + XSetIOErrorHandler(x11_io_error_handler); + + 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); + } + + 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, 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); + list_supported_capture_options(&egl, wayland); + + fflush(stdout); + + // Not needed as this will just slow down shutdown + //gsr_egl_unload(&egl); + //if(dpy) + // XCloseDisplay(dpy); + + _exit(0); +} + +static gsr_capture* create_capture_impl(std::string &window_str, vec2i output_resolution, 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; @@ -1902,18 +2230,8 @@ static gsr_capture* create_capture_impl(std::string &window_str, const char *scr _exit(2); } - if(!screen_region) { - fprintf(stderr, "Error: option -s is required when using -w focused\n"); - usage(); - } - - if(sscanf(screen_region, "%dx%d", ®ion_size.x, ®ion_size.y) != 2) { - fprintf(stderr, "Error: invalid value for option -s '%s', expected a value in format WxH\n", screen_region); - usage(); - } - - if(region_size.x <= 0 || region_size.y <= 0) { - fprintf(stderr, "Error: invalud value for option -s '%s', expected width and height to be greater than 0\n", screen_region); + if(output_resolution.x <= 0 || output_resolution.y <= 0) { + fprintf(stderr, "Error: invalid value for option -s '%dx%d' when using -w focused option. expected width and height to be greater than 0\n", output_resolution.x, output_resolution.y); usage(); } @@ -1933,11 +2251,12 @@ static gsr_capture* create_capture_impl(std::string &window_str, const char *scr portal_params.record_cursor = record_cursor; portal_params.restore_portal_session = restore_portal_session; portal_params.portal_session_token_filepath = portal_session_token_filepath; + portal_params.output_resolution = output_resolution; 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"); + fprintf(stderr, "Error: option '-w portal' used but GPU Screen Recorder was compiled without desktop portal support. Please recompile GPU Screen recorder with the -Dportal=true option\n"); _exit(2); #endif } else if(contains_non_hex_number(window_str.c_str())) { @@ -2007,6 +2326,7 @@ static gsr_capture* create_capture_impl(std::string &window_str, const char *scr nvfbc_params.color_range = color_range; nvfbc_params.record_cursor = record_cursor; nvfbc_params.use_software_video_encoder = use_software_video_encoder; + nvfbc_params.output_resolution = output_resolution; capture = gsr_capture_nvfbc_create(&nvfbc_params); if(!capture) _exit(1); @@ -2018,6 +2338,8 @@ static gsr_capture* create_capture_impl(std::string &window_str, const char *scr 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; + kms_params.output_resolution = output_resolution; capture = gsr_capture_kms_create(&kms_params); if(!capture) _exit(1); @@ -2041,10 +2363,10 @@ static gsr_capture* create_capture_impl(std::string &window_str, const char *scr xcomposite_params.egl = egl; xcomposite_params.window = src_window_id; xcomposite_params.follow_focused = follow_focused; - xcomposite_params.region_size = region_size; xcomposite_params.color_range = color_range; xcomposite_params.record_cursor = record_cursor; xcomposite_params.color_depth = color_depth; + xcomposite_params.output_resolution = output_resolution; capture = gsr_capture_xcomposite_create(&xcomposite_params); if(!capture) _exit(1); @@ -2053,11 +2375,14 @@ static gsr_capture* create_capture_impl(std::string &window_str, const char *scr return capture; } -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; } } @@ -2073,10 +2398,30 @@ struct Arg { } }; +static void match_app_audio_input_to_available_apps(const std::vector<AudioInput> &requested_audio_inputs, const std::vector<std::string> &app_audio_names) { + for(const AudioInput &request_audio_input : requested_audio_inputs) { + bool match = false; + for(const std::string &app_name : app_audio_names) { + if(strcasecmp(app_name.c_str(), request_audio_input.name.c_str()) == 0) { + match = true; + break; + } + } + + if(!match) { + fprintf(stderr, "gsr warning: no audio application with the name \"%s\" was found, expected one of the following:\n", request_audio_input.name.c_str()); + for(const std::string &app_name : app_audio_names) { + fprintf(stderr, " * %s\n", app_name.c_str()); + } + fprintf(stderr, " assuming this is intentional (if you are trying to record audio for applications that haven't started yet).\n"); + } + } +} + // 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) { +static std::vector<MergedAudioInputs> parse_audio_inputs(const AudioDevices &audio_devices, const Arg &audio_input_arg, const Arg &app_audio_input_arg, const Arg &app_audio_input_inverted_arg, bool &uses_amix, const std::vector<std::string> &app_audio_names) { std::vector<MergedAudioInputs> requested_audio_inputs; uses_amix = false; @@ -2084,7 +2429,7 @@ static std::vector<MergedAudioInputs> parse_audio_inputs(const AudioDevices &aud if(!audio_input || audio_input[0] == '\0') continue; - requested_audio_inputs.push_back({parse_audio_input_arg(audio_input)}); + requested_audio_inputs.push_back({parse_audio_input_arg(audio_input, audio_devices), AudioInputType::DEVICE, false}); if(requested_audio_inputs.back().audio_inputs.size() > 1) uses_amix = true; @@ -2105,14 +2450,11 @@ static std::vector<MergedAudioInputs> parse_audio_inputs(const AudioDevices &aud 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; - } + const AudioInput* existing_audio_input = get_audio_device_by_name(audio_devices.audio_inputs, request_audio_input.name); + if(existing_audio_input) { + if(request_audio_input.description.empty()) + request_audio_input.description = "gsr-" + existing_audio_input->description; + match = true; } if(!match) { @@ -2121,14 +2463,30 @@ static std::vector<MergedAudioInputs> parse_audio_inputs(const AudioDevices &aud 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()); + for(const auto &audio_device_input : audio_devices.audio_inputs) { + fprintf(stderr, " %s (%s)\n", audio_device_input.name.c_str(), audio_device_input.description.c_str()); } _exit(2); } } } + for(const char *app_audio_input : app_audio_input_arg.values) { + if(!app_audio_input || app_audio_input[0] == '\0') + continue; + + requested_audio_inputs.push_back({parse_app_audio_input_arg(app_audio_input), AudioInputType::APPLICATION, false}); + match_app_audio_input_to_available_apps(requested_audio_inputs.back().audio_inputs, app_audio_names); + } + + for(const char *app_audio_input : app_audio_input_inverted_arg.values) { + if(!app_audio_input || app_audio_input[0] == '\0') + continue; + + requested_audio_inputs.push_back({parse_app_audio_input_arg(app_audio_input), AudioInputType::APPLICATION, true}); + match_app_audio_input_to_available_apps(requested_audio_inputs.back().audio_inputs, app_audio_names); + } + return requested_audio_inputs; } @@ -2174,80 +2532,107 @@ static AudioCodec select_audio_codec_with_fallback(AudioCodec audio_codec, const } 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"; + 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 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) { +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_video_encoder *video_encoder = create_video_encoder(egl, false, GSR_COLOR_DEPTH_8_BITS, use_software_video_encoder); - if(!video_encoder) { - fprintf(stderr, "Error: failed to create video encoder\n"); - _exit(1); + 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 gsr_supported_video_codecs supported_video_codecs = gsr_video_encoder_get_supported_codecs(video_encoder, true); const AVCodec *video_codec_f = nullptr; - // TODO: Cleanup - // gsr_video_encoder_destroy - 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) + 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) + 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) + 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) + 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) + 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) + 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) + 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) + 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) + 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; } @@ -2258,7 +2643,7 @@ static const AVCodec* pick_video_codec(VideoCodec *video_codec, gsr_egl *egl, bo 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) + if(supported_video_codecs.hevc.supported) video_codec_f = get_ffmpeg_video_codec(*video_codec, egl->gpu_info.vendor); break; } @@ -2268,7 +2653,7 @@ static const AVCodec* pick_video_codec(VideoCodec *video_codec, gsr_egl *egl, bo 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) + if(supported_video_codecs.h264.supported) video_codec_f = get_ffmpeg_video_codec(*video_codec, egl->gpu_info.vendor); break; } @@ -2278,7 +2663,7 @@ static const AVCodec* pick_video_codec(VideoCodec *video_codec, gsr_egl *egl, bo 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) + if(supported_video_codecs.h264.supported) video_codec_f = get_ffmpeg_video_codec(*video_codec, egl->gpu_info.vendor); break; } @@ -2286,6 +2671,32 @@ static const AVCodec* pick_video_codec(VideoCodec *video_codec, gsr_egl *egl, bo 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; + } } } @@ -2306,10 +2717,12 @@ static const AVCodec* pick_video_codec(VideoCodec *video_codec, gsr_egl *egl, bo _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) { +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) { @@ -2359,10 +2772,83 @@ static const AVCodec* select_video_codec_with_fallback(VideoCodec *video_codec, usage(); } - return pick_video_codec(video_codec, egl, use_software_video_encoder, video_codec_auto, video_codec_to_use, is_flv); + return pick_video_codec(video_codec, egl, use_software_video_encoder, video_codec_auto, video_codec_to_use, is_flv, low_power); } +static std::vector<AudioDevice> create_device_audio_inputs(const std::vector<AudioInput> &audio_inputs, AVCodecContext *audio_codec_context, int num_channels, double num_audio_frames_shift, std::vector<AVFilterContext*> &src_filter_ctx, bool use_amix) { + std::vector<AudioDevice> audio_track_audio_devices; + for(size_t i = 0; i < audio_inputs.size(); ++i) { + const auto &audio_input = audio_inputs[i]; + AVFilterContext *src_ctx = nullptr; + if(use_amix) + src_ctx = src_filter_ctx[i]; + + AudioDevice audio_device; + audio_device.audio_input = audio_input; + audio_device.src_filter_ctx = src_ctx; + + if(audio_input.name.empty()) { + audio_device.sound_device.handle = NULL; + audio_device.sound_device.frames = 0; + } else { + if(sound_device_get_by_name(&audio_device.sound_device, audio_input.name.c_str(), audio_input.description.c_str(), num_channels, audio_codec_context->frame_size, audio_codec_context_get_audio_format(audio_codec_context)) != 0) { + fprintf(stderr, "Error: failed to get \"%s\" audio device\n", audio_input.name.c_str()); + _exit(1); + } + } + + audio_device.frame = create_audio_frame(audio_codec_context); + audio_device.frame->pts = -audio_codec_context->frame_size * num_audio_frames_shift; + + audio_track_audio_devices.push_back(std::move(audio_device)); + } + return audio_track_audio_devices; +} + +#ifdef GSR_APP_AUDIO +static AudioDevice create_application_audio_audio_input(const MergedAudioInputs &merged_audio_inputs, AVCodecContext *audio_codec_context, int num_channels, double num_audio_frames_shift, gsr_pipewire_audio *pipewire_audio) { + AudioDevice audio_device; + audio_device.frame = create_audio_frame(audio_codec_context); + audio_device.frame->pts = -audio_codec_context->frame_size * num_audio_frames_shift; + + char random_str[8]; + if(!generate_random_characters_standard_alphabet(random_str, sizeof(random_str))) { + fprintf(stderr, "gsr error: ailed to generate random string\n"); + _exit(1); + } + audio_device.combined_sink_name = "gsr-combined-"; + audio_device.combined_sink_name.append(random_str, sizeof(random_str)); + + if(sound_device_create_combined_sink_connect(&audio_device.sound_device, audio_device.combined_sink_name.c_str(), num_channels, audio_codec_context->frame_size, audio_codec_context_get_audio_format(audio_codec_context)) != 0) { + fprintf(stderr, "Error: failed to setup audio recording to combined sink\n"); + _exit(1); + } + + std::vector<const char*> app_names; + app_names.reserve(merged_audio_inputs.audio_inputs.size()); + for(const auto &audio_input : merged_audio_inputs.audio_inputs) { + app_names.push_back(audio_input.name.c_str()); + } + + if(merged_audio_inputs.inverted) { + if(!gsr_pipewire_audio_add_link_from_apps_to_sink_inverted(pipewire_audio, app_names.data(), app_names.size(), audio_device.combined_sink_name.c_str())) { + fprintf(stderr, "gsr error: failed to add application audio link\n"); + _exit(1); + } + } else { + if(!gsr_pipewire_audio_add_link_from_apps_to_sink(pipewire_audio, app_names.data(), app_names.size(), audio_device.combined_sink_name.c_str())) { + fprintf(stderr, "gsr error: failed to add application audio link\n"); + _exit(1); + } + } + + return audio_device; +} +#endif + int main(int argc, char **argv) { + setlocale(LC_ALL, "C"); // Sigh... stupid C + signal(SIGINT, stop_handler); signal(SIGUSR1, save_replay_handler); signal(SIGUSR2, toggle_pause_handler); @@ -2372,6 +2858,13 @@ int main(int argc, char **argv) { // If this is set to 1 then cuGraphicsGLRegisterImage will fail for egl context with error: invalid OpenGL or DirectX context, // so we overwrite it setenv("__GL_THREADED_OPTIMIZATIONS", "0", true); + // Forces low latency encoding mode. Use this environment variable until vaapi supports setting this as a parameter. + // The downside of this is that it always uses maximum power, which is not ideal for replay mode that runs on system startup. + // This option was added in mesa 24.1.4, released in july 17, 2024. + // TODO: Add an option to enable/disable this? + // Seems like the performance issue is not in encoding, but rendering the frame. + // Some frames end up taking 10 times longer. Seems to be an issue with amd gpu power management when letting the application sleep on the cpu side? + setenv("AMD_DEBUG", "lowlatencyenc", true); // Some people set this to nvidia (for nvdec) or vdpau (for nvidia vdpau), which breaks gpu screen recorder since // nvidia doesn't support vaapi and nvidia-vaapi-driver doesn't support encoding yet. // Let vaapi find the match vaapi driver instead of forcing a specific one. @@ -2397,6 +2890,16 @@ int main(int argc, char **argv) { _exit(0); } + if(argc == 2 && strcmp(argv[1], "--list-application-audio") == 0) { + list_application_audio_command(); + _exit(0); + } + + if(argc == 2 && strcmp(argv[1], "--list-capture-options") == 0) { + list_capture_options_command(); + _exit(0); + } + if(argc == 2 && strcmp(argv[1], "--version") == 0) { puts(GSR_VERSION); _exit(0); @@ -2410,6 +2913,10 @@ int main(int argc, char **argv) { { "-f", Arg { {}, false, false } }, { "-s", Arg { {}, true, false } }, { "-a", Arg { {}, true, true } }, +#ifdef GSR_APP_AUDIO + { "-aa", Arg { {}, true, true } }, + { "-aai", Arg { {}, true, true } }, +#endif { "-q", Arg { {}, true, false } }, { "-o", Arg { {}, true, false } }, { "-r", Arg { {}, true, false } }, @@ -2421,7 +2928,6 @@ int main(int argc, char **argv) { { "-bm", Arg { {}, true, false } }, { "-pixfmt", Arg { {}, true, false } }, { "-v", 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 } }, @@ -2482,6 +2988,10 @@ int main(int argc, char **argv) { 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', 'av1', 'vp8', 'vp9', 'hevc_hdr', 'av1_hdr', 'hevc_10bit' or 'av1_10bit', got: '%s'\n", video_codec_to_use); usage(); @@ -2509,13 +3019,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; @@ -2527,7 +3049,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(); } } @@ -2589,11 +3111,6 @@ int main(int argc, char **argv) { 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"; @@ -2658,12 +3175,37 @@ int main(int argc, char **argv) { } const Arg &audio_input_arg = args["-a"]; + const Arg &app_audio_input_arg = args["-aa"]; + const Arg &app_audio_input_inverted_arg = args["-aai"]; + AudioDevices audio_devices; if(!audio_input_arg.values.empty()) audio_devices = get_pulseaudio_inputs(); + bool uses_app_audio = false; + if(!app_audio_input_arg.values.empty() || !app_audio_input_inverted_arg.values.empty()) + uses_app_audio = true; + + std::vector<std::string> app_audio_names; +#ifdef GSR_APP_AUDIO + gsr_pipewire_audio pipewire_audio; + memset(&pipewire_audio, 0, sizeof(pipewire_audio)); + if(uses_app_audio) { + if(!gsr_pipewire_audio_init(&pipewire_audio)) { + fprintf(stderr, "gsr error: failed to setup PipeWire audio for application audio capture. The likely reason for this failure is that your sound server is not PipeWire\n"); + _exit(2); + } + + gsr_pipewire_audio_for_each_app(&pipewire_audio, [](const char *app_name, void *userdata) { + std::vector<std::string> *app_audio_names = (std::vector<std::string>*)userdata; + app_audio_names->push_back(app_name); + return true; + }, &app_audio_names); + } +#endif + bool uses_amix = false; - std::vector<MergedAudioInputs> requested_audio_inputs = parse_audio_inputs(audio_devices, audio_input_arg, uses_amix); + std::vector<MergedAudioInputs> requested_audio_inputs = parse_audio_inputs(audio_devices, audio_input_arg, app_audio_input_arg, app_audio_input_inverted_arg, uses_amix, app_audio_names); const char *container_format = args["-c"].value(); if(container_format && strcmp(container_format, "mkv") == 0) @@ -2677,24 +3219,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) { @@ -2782,10 +3306,10 @@ int main(int argc, char **argv) { } } - 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"); - } + // 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!). @@ -2820,8 +3344,10 @@ int main(int argc, char **argv) { 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', got: '%s'\n", bitrate_mode_str); + fprintf(stderr, "Error: -bm should either be either 'auto', 'qp', 'vbr' or 'cbr', got: '%s'\n", bitrate_mode_str); usage(); } @@ -2830,11 +3356,55 @@ int main(int argc, char **argv) { bitrate_mode = egl.gpu_info.is_steam_deck ? BitrateMode::VBR : BitrateMode::QP; } - if(use_software_video_encoder && bitrate_mode != 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(); + VideoQuality quality = VideoQuality::VERY_HIGH; + int64_t video_bitrate = 0; + + if(bitrate_mode == BitrateMode::CBR) { + if(!quality_str) { + fprintf(stderr, "Error: option '-q' is required when using '-bm cbr' option\n"); + usage(); + } + + if(sscanf(quality_str, "%" PRIi64, &video_bitrate) != 1) { + fprintf(stderr, "Error: -q argument \"%s\" is not an integer value. When using '-bm cbr' option '-q' is expected to be an integer value\n", quality_str); + usage(); + } + + if(video_bitrate < 0) { + fprintf(stderr, "Error: -q is expected to be 0 or larger, got %" PRIi64 "\n", video_bitrate); + usage(); + } + + video_bitrate *= 1000LL; + } else { + 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(); + } + } + gsr_color_range color_range = GSR_COLOR_RANGE_LIMITED; const char *color_range_str = args["-cr"].value(); if(!color_range_str) @@ -2849,13 +3419,25 @@ int main(int argc, char **argv) { usage(); } - const char *screen_region = args["-s"].value(); - - if(screen_region && strcmp(window_str.c_str(), "focused") != 0) { - fprintf(stderr, "Error: option -s is only available when using -w focused\n"); + const char *output_resolution_str = args["-s"].value(); + if(!output_resolution_str && strcmp(window_str.c_str(), "focused") == 0) { + fprintf(stderr, "Error: option -s is required when using -w focused option\n"); usage(); } + vec2i output_resolution = {0, 0}; + if(output_resolution_str) { + if(sscanf(output_resolution_str, "%dx%d", &output_resolution.x, &output_resolution.y) != 2) { + fprintf(stderr, "Error: invalid value for option -s '%s', expected a value in format WxH\n", output_resolution_str); + usage(); + } + + if(output_resolution.x < 0 || output_resolution.y < 0) { + fprintf(stderr, "Error: invalud value for option -s '%s', expected width and height to be greater or equal to 0\n", output_resolution_str); + usage(); + } + } + bool is_livestream = false; const char *filename = args["-o"].value(); if(filename) { @@ -2931,15 +3513,16 @@ int main(int argc, char **argv) { const double target_fps = 1.0 / (double)fps; 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"); + 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); } audio_codec = select_audio_codec_with_fallback(audio_codec, file_extension, uses_amix); - 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); + 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); 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); + gsr_capture *capture = create_capture_impl(window_str, output_resolution, 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. @@ -2960,7 +3543,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, use_software_video_encoder, bitrate_mode); + 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); @@ -2984,7 +3568,7 @@ int main(int argc, char **argv) { _exit(capture_result); } - gsr_video_encoder *video_encoder = create_video_encoder(&egl, overclock, color_depth, 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); @@ -2999,7 +3583,6 @@ int main(int argc, char **argv) { memset(&color_conversion_params, 0, sizeof(color_conversion_params)); color_conversion_params.color_range = color_range; color_conversion_params.egl = &egl; - color_conversion_params.source_color = gsr_capture_get_source_color(capture); color_conversion_params.load_external_image_shader = gsr_capture_uses_external_image(capture); gsr_video_encoder_get_textures(video_encoder, color_conversion_params.destination_textures, &color_conversion_params.num_destination_textures, &color_conversion_params.destination_color); @@ -3014,7 +3597,7 @@ int main(int argc, char **argv) { if(use_software_video_encoder) { 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, color_depth, bitrate_mode); + 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); @@ -3044,7 +3627,7 @@ int main(int argc, char **argv) { std::vector<AVFilterContext*> src_filter_ctx; AVFilterGraph *graph = nullptr; AVFilterContext *sink = nullptr; - if(use_amix) { + if(use_amix && merged_audio_inputs.type == AudioInputType::DEVICE) { int err = init_filter_graph(audio_codec_context, &graph, &sink, src_filter_ctx, merged_audio_inputs.audio_inputs.size()); if(err < 0) { fprintf(stderr, "Error: failed to create audio filter\n"); @@ -3061,30 +3644,15 @@ int main(int argc, char **argv) { const double num_audio_frames_shift = audio_startup_time_seconds / timeout_sec; 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; - if(use_amix) - src_ctx = src_filter_ctx[i]; - - AudioDevice audio_device; - audio_device.audio_input = audio_input; - audio_device.src_filter_ctx = src_ctx; - - if(audio_input.name.empty()) { - audio_device.sound_device.handle = NULL; - audio_device.sound_device.frames = 0; - } else { - if(sound_device_get_by_name(&audio_device.sound_device, audio_input.name.c_str(), audio_input.description.c_str(), num_channels, audio_codec_context->frame_size, audio_codec_context_get_audio_format(audio_codec_context)) != 0) { - fprintf(stderr, "Error: failed to get \"%s\" sound device\n", audio_input.name.c_str()); - _exit(1); - } - } - - audio_device.frame = create_audio_frame(audio_codec_context); - audio_device.frame->pts = -audio_codec_context->frame_size * num_audio_frames_shift; - - audio_track_audio_devices.push_back(std::move(audio_device)); + switch(merged_audio_inputs.type) { + case AudioInputType::DEVICE: + audio_track_audio_devices = create_device_audio_inputs(merged_audio_inputs.audio_inputs, audio_codec_context, num_channels, num_audio_frames_shift, src_filter_ctx, use_amix); + break; + case AudioInputType::APPLICATION: +#ifdef GSR_APP_AUDIO + audio_track_audio_devices.push_back(create_application_audio_audio_input(merged_audio_inputs, audio_codec_context, num_channels, num_audio_frames_shift, &pipewire_audio)); +#endif + break; } AudioTrack audio_track; @@ -3126,6 +3694,7 @@ int main(int argc, char **argv) { } double fps_start_time = clock_get_monotonic_seconds(); + //double frame_timer_start = fps_start_time; int fps_counter = 0; int damage_fps_counter = 0; @@ -3337,7 +3906,7 @@ int main(int argc, char **argv) { } // 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; + //const double update_fps = fps + 190; bool should_stop_error = false; int64_t video_pts_counter = 0; @@ -3359,12 +3928,15 @@ int main(int argc, char **argv) { if(is_monitor_capture) gsr_damage_set_target_monitor(&damage, window_str.c_str()); + double last_capture_seconds = record_start_time; + bool wait_until_frame_time_elapsed = false; + while(running) { const double frame_start = clock_get_monotonic_seconds(); while(gsr_egl_process_event(&egl)) { - gsr_capture_on_event(capture, &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); @@ -3392,11 +3964,16 @@ int main(int argc, char **argv) { else damaged = true; + // TODO: Readd wayland sync warning when removing this + if(framerate_mode != FramerateMode::CONTENT) + damaged = true; + if(damaged) ++damage_fps_counter; ++fps_counter; - double time_now = clock_get_monotonic_seconds(); + 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) { @@ -3408,26 +3985,42 @@ int main(int argc, char **argv) { } const double this_video_frame_time = clock_get_monotonic_seconds() - paused_time_offset; - const int64_t expected_frames = std::round((this_video_frame_time - record_start_time) / target_fps); - const int num_frames = std::max((int64_t)0LL, expected_frames - video_pts_counter); - const double num_frames_seconds = num_frames * target_fps; - if((damaged || (framerate_mode == FramerateMode::CONSTANT && num_frames > 0) || (framerate_mode != FramerateMode::CONSTANT && num_frames_seconds >= damage_timeout_seconds)) && !paused) { + const double time_since_last_frame_captured_seconds = this_video_frame_time - last_capture_seconds; + double frame_time_overflow = time_since_last_frame_captured_seconds - target_fps; + const bool frame_timeout = frame_time_overflow >= 0.0; + + bool force_frame_capture = wait_until_frame_time_elapsed && frame_timeout; + bool allow_capture = !wait_until_frame_time_elapsed || force_frame_capture; + if(framerate_mode == FramerateMode::CONTENT) { + force_frame_capture = false; + allow_capture = frame_timeout; + } + + bool frame_captured = false; + if((damaged || force_frame_capture) && allow_capture && !paused) { + frame_captured = true; + frame_time_overflow = std::min(std::max(0.0, frame_time_overflow), target_fps); + last_capture_seconds = this_video_frame_time - frame_time_overflow; + wait_until_frame_time_elapsed = false; + gsr_damage_clear(&damage); if(capture->clear_damage) capture->clear_damage(capture); - if(damaged || video_pts_counter == 0) { - egl.glClear(0); - gsr_capture_capture(capture, video_frame, &color_conversion); - gsr_egl_swap_buffers(&egl); - gsr_video_encoder_copy_textures_to_frame(video_encoder, video_frame); - } + // TODO: Dont do this if no damage? + egl.glClear(0); + gsr_capture_capture(capture, video_frame, &color_conversion); + 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; + const int64_t expected_frames = std::round((this_video_frame_time - record_start_time) / target_fps); + const int num_missed_frames = std::max((int64_t)1LL, expected_frames - video_pts_counter); + // TODO: Check if duplicate frame can be saved just by writing it with a different pts instead of sending it again - const int num_frames_to_encode = framerate_mode == FramerateMode::CONSTANT ? num_frames : 1; + const int num_frames_to_encode = framerate_mode == FramerateMode::CONSTANT ? num_missed_frames : 1; for(int i = 0; i < num_frames_to_encode; ++i) { if(framerate_mode == FramerateMode::CONSTANT) { video_frame->pts = video_pts_counter + i; @@ -3449,7 +4042,7 @@ int main(int argc, char **argv) { } } - video_pts_counter += num_frames; + video_pts_counter += num_frames_to_encode; } if(toggle_pause == 1) { @@ -3482,13 +4075,28 @@ int main(int argc, char **argv) { } const double frame_end = clock_get_monotonic_seconds(); - const double frame_sleep_fps = 1.0 / update_fps; - const double sleep_time = frame_sleep_fps - (frame_end - frame_start); - if(sleep_time > 0.0) { - if(damaged) - av_usleep(sleep_time * 1000.0 * 1000.0); - else - av_usleep(2 * 1000.0); // 2 milliseconds + const double time_at_frame_end = frame_end - paused_time_offset; + const double time_elapsed_total = time_at_frame_end - record_start_time; + const int64_t frames_elapsed = (int64_t)(time_elapsed_total / target_fps); + const double time_at_next_frame = (frames_elapsed + 1) * target_fps; + double time_to_next_frame = time_at_next_frame - time_elapsed_total; + if(time_to_next_frame > target_fps*1.1) + time_to_next_frame = target_fps; + + const double frame_time = frame_end - frame_start; + const bool frame_deadline_missed = frame_time > target_fps; + if(time_to_next_frame >= 0.0 && !frame_deadline_missed && frame_captured) + av_usleep(time_to_next_frame * 1000.0 * 1000.0); + else { + if(paused) + av_usleep(20.0 * 1000.0); // 20 milliseconds + else if(frame_deadline_missed) + {} + else if(framerate_mode == FramerateMode::CONTENT || !frame_captured) + av_usleep(2.8 * 1000.0); // 2.8 milliseconds + else if(!frame_captured) + av_usleep(1.0 * 1000.0); // 1 milliseconds + wait_until_frame_time_elapsed = true; } } @@ -3525,6 +4133,9 @@ int main(int argc, char **argv) { gsr_color_conversion_deinit(&color_conversion); gsr_video_encoder_destroy(video_encoder, video_codec_context); gsr_capture_destroy(capture, video_codec_context); +#ifdef GSR_APP_AUDIO + gsr_pipewire_audio_deinit(&pipewire_audio); +#endif if(replay_buffer_size_secs == -1 && recording_saved_script) run_recording_saved_script_async(recording_saved_script, filename, "regular"); |