From 3ec93bcc41d9636f0acd195c60ca86e58f356637 Mon Sep 17 00:00:00 2001 From: dec05eba Date: Sat, 17 Aug 2024 19:55:09 +0200 Subject: Add hevc_10bit and av1_10bit options for 10bit color range, increase video quality (qp) --- TODO | 10 +- include/capture/kms.h | 1 + include/capture/nvfbc.h | 2 +- include/capture/portal.h | 2 +- include/capture/xcomposite.h | 1 + include/color_conversion.h | 5 + include/encoder/video/cuda.h | 2 +- include/encoder/video/software.h | 2 +- include/encoder/video/vaapi.h | 2 +- src/encoder/video/cuda.c | 8 +- src/encoder/video/software.c | 4 +- src/encoder/video/vaapi.c | 4 +- src/main.cpp | 593 ++++++++++++++++++++++----------------- 13 files changed, 365 insertions(+), 271 deletions(-) diff --git a/TODO b/TODO index 32e5da0..157fa52 100644 --- a/TODO +++ b/TODO @@ -150,4 +150,12 @@ Explicit sync is done with the drm property IN_FENCE_FD (see https://drmdb.emers The update fps appear to be lower when recording a monitor instead of using portal on intel. Does this reflect in game framerate? -Fix glitches when using prime-run with desktop portal. It happens when moving a window around. It's probably a syncing issue. \ No newline at end of file +Fix glitches when using prime-run with desktop portal. It happens when moving a window around. It's probably a syncing issue. + +Allow prime-run on x11 if monitor capture and the prime gpu is not nvidia. + +Enable 2-pass encoding. + +Add even higher quality option or make ultra quality much higher quality. Check with vlc to see artifacts better, mpv masks them. + +Verify 10 bit works on x11 nvidia. \ No newline at end of file diff --git a/include/capture/kms.h b/include/capture/kms.h index 278a391..f19e462 100644 --- a/include/capture/kms.h +++ b/include/capture/kms.h @@ -6,6 +6,7 @@ typedef struct { gsr_egl *egl; const char *display_to_capture; /* if this is "screen", then the first monitor is captured. A copy is made of this */ + gsr_color_depth color_depth; gsr_color_range color_range; bool hdr; bool record_cursor; diff --git a/include/capture/nvfbc.h b/include/capture/nvfbc.h index e24b712..9655921 100644 --- a/include/capture/nvfbc.h +++ b/include/capture/nvfbc.h @@ -12,7 +12,7 @@ typedef struct { vec2i size; bool direct_capture; bool overclock; - bool hdr; + gsr_color_depth color_depth; gsr_color_range color_range; bool record_cursor; bool use_software_video_encoder; diff --git a/include/capture/portal.h b/include/capture/portal.h index f80325d..2e2c6f2 100644 --- a/include/capture/portal.h +++ b/include/capture/portal.h @@ -5,8 +5,8 @@ typedef struct { gsr_egl *egl; + gsr_color_depth color_depth; gsr_color_range color_range; - bool hdr; bool record_cursor; bool restore_portal_session; /* If this is set to NULL then this defaults to $XDG_CONFIG_HOME/gpu-screen-recorder/restore_token ($XDG_CONFIG_HOME defaults to $HOME/.config) */ diff --git a/include/capture/xcomposite.h b/include/capture/xcomposite.h index 707421f..98b8766 100644 --- a/include/capture/xcomposite.h +++ b/include/capture/xcomposite.h @@ -12,6 +12,7 @@ typedef struct { gsr_color_range color_range; bool record_cursor; bool track_damage; + gsr_color_depth color_depth; } gsr_capture_xcomposite_params; gsr_capture* gsr_capture_xcomposite_create(const gsr_capture_xcomposite_params *params); diff --git a/include/color_conversion.h b/include/color_conversion.h index d05df6a..236bfbd 100644 --- a/include/color_conversion.h +++ b/include/color_conversion.h @@ -10,6 +10,11 @@ typedef enum { GSR_COLOR_RANGE_FULL } gsr_color_range; +typedef enum { + GSR_COLOR_DEPTH_8_BITS, + GSR_COLOR_DEPTH_10_BITS +} gsr_color_depth; + typedef enum { GSR_SOURCE_COLOR_RGB, GSR_SOURCE_COLOR_BGR diff --git a/include/encoder/video/cuda.h b/include/encoder/video/cuda.h index 802e72e..6d32e09 100644 --- a/include/encoder/video/cuda.h +++ b/include/encoder/video/cuda.h @@ -8,7 +8,7 @@ typedef struct gsr_egl gsr_egl; typedef struct { gsr_egl *egl; bool overclock; - bool hdr; + gsr_color_depth color_depth; } gsr_video_encoder_cuda_params; gsr_video_encoder* gsr_video_encoder_cuda_create(const gsr_video_encoder_cuda_params *params); diff --git a/include/encoder/video/software.h b/include/encoder/video/software.h index e39d5f8..fd2dc6b 100644 --- a/include/encoder/video/software.h +++ b/include/encoder/video/software.h @@ -7,7 +7,7 @@ typedef struct gsr_egl gsr_egl; typedef struct { gsr_egl *egl; - bool hdr; + gsr_color_depth color_depth; } gsr_video_encoder_software_params; gsr_video_encoder* gsr_video_encoder_software_create(const gsr_video_encoder_software_params *params); diff --git a/include/encoder/video/vaapi.h b/include/encoder/video/vaapi.h index 2981a01..b509f17 100644 --- a/include/encoder/video/vaapi.h +++ b/include/encoder/video/vaapi.h @@ -7,7 +7,7 @@ typedef struct gsr_egl gsr_egl; typedef struct { gsr_egl *egl; - bool hdr; + gsr_color_depth color_depth; } gsr_video_encoder_vaapi_params; gsr_video_encoder* gsr_video_encoder_vaapi_create(const gsr_video_encoder_vaapi_params *params); diff --git a/src/encoder/video/cuda.c b/src/encoder/video/cuda.c index 2568bc7..4142e97 100644 --- a/src/encoder/video/cuda.c +++ b/src/encoder/video/cuda.c @@ -44,7 +44,7 @@ static bool gsr_video_encoder_cuda_setup_context(gsr_video_encoder_cuda *self, A AVHWFramesContext *hw_frame_context = (AVHWFramesContext*)frame_context->data; hw_frame_context->width = video_codec_context->width; hw_frame_context->height = video_codec_context->height; - hw_frame_context->sw_format = self->params.hdr ? AV_PIX_FMT_P010LE : AV_PIX_FMT_NV12; + hw_frame_context->sw_format = self->params.color_depth == GSR_COLOR_DEPTH_10_BITS ? AV_PIX_FMT_P010LE : AV_PIX_FMT_NV12; hw_frame_context->format = video_codec_context->pix_fmt; hw_frame_context->device_ref = device_ctx; hw_frame_context->device_ctx = (AVHWDeviceContext*)device_ctx->data; @@ -108,7 +108,7 @@ static bool gsr_video_encoder_cuda_setup_textures(gsr_video_encoder_cuda *self, const int div[2] = {1, 2}; // divide UV texture size by 2 because chroma is half size for(int i = 0; i < 2; ++i) { - self->target_textures[i] = gl_create_texture(self->params.egl, video_codec_context->width / div[i], video_codec_context->height / div[i], !self->params.hdr ? internal_formats_nv12[i] : internal_formats_p010[i], formats[i]); + self->target_textures[i] = gl_create_texture(self->params.egl, video_codec_context->width / div[i], video_codec_context->height / div[i], self->params.color_depth == GSR_COLOR_DEPTH_8_BITS ? internal_formats_nv12[i] : internal_formats_p010[i], formats[i]); if(self->target_textures[i] == 0) { fprintf(stderr, "gsr error: gsr_video_encoder_cuda_setup_textures: failed to create opengl texture\n"); return false; @@ -187,7 +187,7 @@ static void gsr_video_encoder_cuda_copy_textures_to_frame(gsr_video_encoder *enc memcpy_struct.srcPitch = frame->width / div[i]; memcpy_struct.dstDevice = (CUdeviceptr)frame->data[i]; memcpy_struct.dstPitch = frame->linesize[i]; - memcpy_struct.WidthInBytes = frame->width * (encoder_cuda->params.hdr ? 2 : 1); + memcpy_struct.WidthInBytes = frame->width * (encoder_cuda->params.color_depth == GSR_COLOR_DEPTH_10_BITS ? 2 : 1); memcpy_struct.Height = frame->height / div[i]; // TODO: Remove this copy if possible encoder_cuda->cuda.cuMemcpy2DAsync_v2(&memcpy_struct, encoder_cuda->cuda_stream); @@ -202,7 +202,7 @@ static void gsr_video_encoder_cuda_get_textures(gsr_video_encoder *encoder, unsi textures[0] = encoder_cuda->target_textures[0]; textures[1] = encoder_cuda->target_textures[1]; *num_textures = 2; - *destination_color = encoder_cuda->params.hdr ? GSR_DESTINATION_COLOR_P010 : GSR_DESTINATION_COLOR_NV12; + *destination_color = encoder_cuda->params.color_depth == GSR_COLOR_DEPTH_10_BITS ? GSR_DESTINATION_COLOR_P010 : GSR_DESTINATION_COLOR_NV12; } static void gsr_video_encoder_cuda_destroy(gsr_video_encoder *encoder, AVCodecContext *video_codec_context) { diff --git a/src/encoder/video/software.c b/src/encoder/video/software.c index f3b311e..192be17 100644 --- a/src/encoder/video/software.c +++ b/src/encoder/video/software.c @@ -48,7 +48,7 @@ static bool gsr_video_encoder_software_setup_textures(gsr_video_encoder_software const int div[2] = {1, 2}; // divide UV texture size by 2 because chroma is half size for(int i = 0; i < 2; ++i) { - self->target_textures[i] = gl_create_texture(self->params.egl, video_codec_context->width / div[i], video_codec_context->height / div[i], !self->params.hdr ? internal_formats_nv12[i] : internal_formats_p010[i], formats[i]); + self->target_textures[i] = gl_create_texture(self->params.egl, video_codec_context->width / div[i], video_codec_context->height / div[i], self->params.color_depth == GSR_COLOR_DEPTH_8_BITS ? internal_formats_nv12[i] : internal_formats_p010[i], formats[i]); if(self->target_textures[i] == 0) { fprintf(stderr, "gsr error: gsr_capture_kms_setup_cuda_textures: failed to create opengl texture\n"); return false; @@ -103,7 +103,7 @@ static void gsr_video_encoder_software_get_textures(gsr_video_encoder *encoder, textures[0] = encoder_software->target_textures[0]; textures[1] = encoder_software->target_textures[1]; *num_textures = 2; - *destination_color = encoder_software->params.hdr ? GSR_DESTINATION_COLOR_P010 : GSR_DESTINATION_COLOR_NV12; + *destination_color = encoder_software->params.color_depth == GSR_COLOR_DEPTH_10_BITS ? GSR_DESTINATION_COLOR_P010 : GSR_DESTINATION_COLOR_NV12; } static void gsr_video_encoder_software_destroy(gsr_video_encoder *encoder, AVCodecContext *video_codec_context) { diff --git a/src/encoder/video/vaapi.c b/src/encoder/video/vaapi.c index 910eb18..9470e76 100644 --- a/src/encoder/video/vaapi.c +++ b/src/encoder/video/vaapi.c @@ -43,7 +43,7 @@ static bool gsr_video_encoder_vaapi_setup_context(gsr_video_encoder_vaapi *self, (AVHWFramesContext *)frame_context->data; hw_frame_context->width = video_codec_context->width; hw_frame_context->height = video_codec_context->height; - hw_frame_context->sw_format = self->params.hdr ? AV_PIX_FMT_P010LE : AV_PIX_FMT_NV12; + hw_frame_context->sw_format = self->params.color_depth == GSR_COLOR_DEPTH_10_BITS ? AV_PIX_FMT_P010LE : AV_PIX_FMT_NV12; hw_frame_context->format = video_codec_context->pix_fmt; hw_frame_context->device_ref = device_ctx; hw_frame_context->device_ctx = (AVHWDeviceContext*)device_ctx->data; @@ -211,7 +211,7 @@ static void gsr_video_encoder_vaapi_get_textures(gsr_video_encoder *encoder, uns textures[0] = encoder_vaapi->target_textures[0]; textures[1] = encoder_vaapi->target_textures[1]; *num_textures = 2; - *destination_color = encoder_vaapi->params.hdr ? GSR_DESTINATION_COLOR_P010 : GSR_DESTINATION_COLOR_NV12; + *destination_color = encoder_vaapi->params.color_depth == GSR_COLOR_DEPTH_10_BITS ? GSR_DESTINATION_COLOR_P010 : GSR_DESTINATION_COLOR_NV12; } static void gsr_video_encoder_vaapi_destroy(gsr_video_encoder *encoder, AVCodecContext *video_codec_context) { diff --git a/src/main.cpp b/src/main.cpp index cd1ec5d..a628a13 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -90,8 +90,10 @@ enum class VideoCodec { H264, HEVC, HEVC_HDR, + HEVC_10BIT, AV1, AV1_HDR, + AV1_10BIT, VP8, VP9 }; @@ -131,6 +133,42 @@ static bool video_codec_is_hdr(VideoCodec video_codec) { } } +static gsr_color_depth video_codec_to_bit_depth(VideoCodec video_codec) { + switch(video_codec) { + case VideoCodec::HEVC: + case VideoCodec::HEVC_HDR: + case VideoCodec::HEVC_10BIT: + case VideoCodec::AV1: + case VideoCodec::AV1_HDR: + case VideoCodec::AV1_10BIT: + return GSR_COLOR_DEPTH_10_BITS; + default: + return GSR_COLOR_DEPTH_8_BITS; + } +} + +// static bool video_codec_is_hevc(VideoCodec video_codec) { +// switch(video_codec) { +// case VideoCodec::HEVC: +// case VideoCodec::HEVC_HDR: +// case VideoCodec::HEVC_10BIT: +// return true; +// default: +// return false; +// } +// } + +static bool video_codec_is_av1(VideoCodec video_codec) { + switch(video_codec) { + case VideoCodec::AV1: + case VideoCodec::AV1_HDR: + case VideoCodec::AV1_10BIT: + return true; + default: + return false; + } +} + struct PacketData { PacketData() {} PacketData(const PacketData&) = delete; @@ -431,16 +469,16 @@ static AVCodecContext *create_video_codec_context(AVPixelFormat pix_fmt, if(codec_context->codec_id == AV_CODEC_ID_AV1 || codec_context->codec_id == AV_CODEC_ID_H264 || codec_context->codec_id == AV_CODEC_ID_HEVC) { switch(video_quality) { case VideoQuality::MEDIUM: - codec_context->global_quality = 180 * quality_multiply; + codec_context->global_quality = 160 * quality_multiply; break; case VideoQuality::HIGH: - codec_context->global_quality = 140 * quality_multiply; + codec_context->global_quality = 130 * quality_multiply; break; case VideoQuality::VERY_HIGH: - codec_context->global_quality = 120 * quality_multiply; + codec_context->global_quality = 110 * quality_multiply; break; case VideoQuality::ULTRA: - codec_context->global_quality = 100 * quality_multiply; + codec_context->global_quality = 90 * quality_multiply; break; } } else if(codec_context->codec_id == AV_CODEC_ID_VP8) { @@ -702,7 +740,7 @@ static AVFrame* create_audio_frame(AVCodecContext *audio_codec_context) { return frame; } -static void open_video_software(AVCodecContext *codec_context, VideoQuality video_quality, PixelFormat pixel_format, bool hdr) { +static void open_video_software(AVCodecContext *codec_context, VideoQuality video_quality, PixelFormat pixel_format, bool hdr, gsr_color_depth color_depth) { (void)pixel_format; // TODO: AVDictionary *options = nullptr; @@ -710,16 +748,16 @@ static void open_video_software(AVCodecContext *codec_context, VideoQuality vide if(codec_context->codec_id == AV_CODEC_ID_AV1) { switch(video_quality) { case VideoQuality::MEDIUM: - av_dict_set_int(&options, "qp", 37 * qp_multiply, 0); + av_dict_set_int(&options, "qp", 35 * qp_multiply, 0); break; case VideoQuality::HIGH: - av_dict_set_int(&options, "qp", 32 * qp_multiply, 0); + av_dict_set_int(&options, "qp", 30 * qp_multiply, 0); break; case VideoQuality::VERY_HIGH: - av_dict_set_int(&options, "qp", 28 * qp_multiply, 0); + av_dict_set_int(&options, "qp", 25 * qp_multiply, 0); break; case VideoQuality::ULTRA: - av_dict_set_int(&options, "qp", 24 * qp_multiply, 0); + av_dict_set_int(&options, "qp", 22 * qp_multiply, 0); break; } } else if(codec_context->codec_id == AV_CODEC_ID_H264) { @@ -731,31 +769,31 @@ static void open_video_software(AVCodecContext *codec_context, VideoQuality vide av_dict_set_int(&options, "qp", 30 * qp_multiply, 0); break; case VideoQuality::VERY_HIGH: - av_dict_set_int(&options, "qp", 26 * qp_multiply, 0); + av_dict_set_int(&options, "qp", 23 * qp_multiply, 0); break; case VideoQuality::ULTRA: - av_dict_set_int(&options, "qp", 22 * qp_multiply, 0); + av_dict_set_int(&options, "qp", 20 * qp_multiply, 0); break; } } else { switch(video_quality) { case VideoQuality::MEDIUM: - av_dict_set_int(&options, "qp", 37 * qp_multiply, 0); + av_dict_set_int(&options, "qp", 35 * qp_multiply, 0); break; case VideoQuality::HIGH: - av_dict_set_int(&options, "qp", 32 * qp_multiply, 0); + av_dict_set_int(&options, "qp", 30 * qp_multiply, 0); break; case VideoQuality::VERY_HIGH: - av_dict_set_int(&options, "qp", 28 * qp_multiply, 0); + av_dict_set_int(&options, "qp", 25 * qp_multiply, 0); break; case VideoQuality::ULTRA: - av_dict_set_int(&options, "qp", 24 * qp_multiply, 0); + av_dict_set_int(&options, "qp", 22 * qp_multiply, 0); break; } } av_dict_set(&options, "preset", "medium", 0); - if(hdr) { + if(color_depth == GSR_COLOR_DEPTH_10_BITS) { av_dict_set(&options, "profile", "high10", 0); } else { av_dict_set(&options, "profile", "high", 0); @@ -776,7 +814,7 @@ static void open_video_software(AVCodecContext *codec_context, VideoQuality vide } } -static void open_video_hardware(AVCodecContext *codec_context, VideoQuality video_quality, bool very_old_gpu, gsr_gpu_vendor vendor, PixelFormat pixel_format, bool hdr) { +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) { (void)very_old_gpu; AVDictionary *options = nullptr; // 8 bit / 10 bit = 80% @@ -801,16 +839,16 @@ static void open_video_hardware(AVCodecContext *codec_context, VideoQuality vide if(codec_context->codec_id == AV_CODEC_ID_AV1) { switch(video_quality) { case VideoQuality::MEDIUM: - av_dict_set_int(&options, "qp", 37 * qp_multiply, 0); + av_dict_set_int(&options, "qp", 35 * qp_multiply, 0); break; case VideoQuality::HIGH: - av_dict_set_int(&options, "qp", 32 * qp_multiply, 0); + av_dict_set_int(&options, "qp", 30 * qp_multiply, 0); break; case VideoQuality::VERY_HIGH: - av_dict_set_int(&options, "qp", 28 * qp_multiply, 0); + av_dict_set_int(&options, "qp", 25 * qp_multiply, 0); break; case VideoQuality::ULTRA: - av_dict_set_int(&options, "qp", 24 * qp_multiply, 0); + av_dict_set_int(&options, "qp", 22 * qp_multiply, 0); break; } } else if(codec_context->codec_id == AV_CODEC_ID_H264) { @@ -822,40 +860,40 @@ static void open_video_hardware(AVCodecContext *codec_context, VideoQuality vide av_dict_set_int(&options, "qp", 30 * qp_multiply, 0); break; case VideoQuality::VERY_HIGH: - av_dict_set_int(&options, "qp", 26 * qp_multiply, 0); + av_dict_set_int(&options, "qp", 23 * qp_multiply, 0); break; case VideoQuality::ULTRA: - av_dict_set_int(&options, "qp", 22 * qp_multiply, 0); + av_dict_set_int(&options, "qp", 20 * qp_multiply, 0); break; } } else if(codec_context->codec_id == AV_CODEC_ID_HEVC) { switch(video_quality) { case VideoQuality::MEDIUM: - av_dict_set_int(&options, "qp", 37 * qp_multiply, 0); + av_dict_set_int(&options, "qp", 35 * qp_multiply, 0); break; case VideoQuality::HIGH: - av_dict_set_int(&options, "qp", 32 * qp_multiply, 0); + av_dict_set_int(&options, "qp", 30 * qp_multiply, 0); break; case VideoQuality::VERY_HIGH: - av_dict_set_int(&options, "qp", 28 * qp_multiply, 0); + av_dict_set_int(&options, "qp", 25 * qp_multiply, 0); break; case VideoQuality::ULTRA: - av_dict_set_int(&options, "qp", 24 * qp_multiply, 0); + av_dict_set_int(&options, "qp", 22 * qp_multiply, 0); break; } } else if(codec_context->codec_id == AV_CODEC_ID_VP8 || codec_context->codec_id == AV_CODEC_ID_VP9) { switch(video_quality) { case VideoQuality::MEDIUM: - av_dict_set_int(&options, "qp", 37 * qp_multiply, 0); + av_dict_set_int(&options, "qp", 35 * qp_multiply, 0); break; case VideoQuality::HIGH: - av_dict_set_int(&options, "qp", 32 * qp_multiply, 0); + av_dict_set_int(&options, "qp", 30 * qp_multiply, 0); break; case VideoQuality::VERY_HIGH: - av_dict_set_int(&options, "qp", 28 * qp_multiply, 0); + av_dict_set_int(&options, "qp", 25 * qp_multiply, 0); break; case VideoQuality::ULTRA: - av_dict_set_int(&options, "qp", 24 * qp_multiply, 0); + av_dict_set_int(&options, "qp", 22 * qp_multiply, 0); break; } } @@ -891,6 +929,7 @@ static void open_video_hardware(AVCodecContext *codec_context, VideoQuality vide // TODO: Enable multipass 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); @@ -900,6 +939,9 @@ static void open_video_hardware(AVCodecContext *codec_context, VideoQuality vide 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); @@ -911,11 +953,10 @@ static void open_video_hardware(AVCodecContext *codec_context, VideoQuality vide } else if(codec_context->codec_id == AV_CODEC_ID_HEVC) { //av_dict_set(&options, "profile", "main10", 0); //av_dict_set(&options, "pix_fmt", "yuv420p16le", 0); - if(hdr) { + if(color_depth == GSR_COLOR_DEPTH_10_BITS) av_dict_set(&options, "profile", "main10", 0); - } else { + else av_dict_set(&options, "profile", "main", 0); - } } } else { if(codec_context->codec_id == AV_CODEC_ID_AV1) { @@ -929,40 +970,40 @@ static void open_video_hardware(AVCodecContext *codec_context, VideoQuality vide av_dict_set_int(&options, "qp", 30 * qp_multiply, 0); break; case VideoQuality::VERY_HIGH: - av_dict_set_int(&options, "qp", 26 * qp_multiply, 0); + av_dict_set_int(&options, "qp", 23 * qp_multiply, 0); break; case VideoQuality::ULTRA: - av_dict_set_int(&options, "qp", 22 * qp_multiply, 0); + av_dict_set_int(&options, "qp", 20 * qp_multiply, 0); break; } } else if(codec_context->codec_id == AV_CODEC_ID_HEVC) { switch(video_quality) { case VideoQuality::MEDIUM: - av_dict_set_int(&options, "qp", 37 * qp_multiply, 0); + av_dict_set_int(&options, "qp", 35 * qp_multiply, 0); break; case VideoQuality::HIGH: - av_dict_set_int(&options, "qp", 32 * qp_multiply, 0); + av_dict_set_int(&options, "qp", 30 * qp_multiply, 0); break; case VideoQuality::VERY_HIGH: - av_dict_set_int(&options, "qp", 28 * qp_multiply, 0); + av_dict_set_int(&options, "qp", 25 * qp_multiply, 0); break; case VideoQuality::ULTRA: - av_dict_set_int(&options, "qp", 24 * qp_multiply, 0); + av_dict_set_int(&options, "qp", 22 * qp_multiply, 0); break; } } else if(codec_context->codec_id == AV_CODEC_ID_VP8 || codec_context->codec_id == AV_CODEC_ID_VP9) { switch(video_quality) { case VideoQuality::MEDIUM: - av_dict_set_int(&options, "qp", 37 * qp_multiply, 0); + av_dict_set_int(&options, "qp", 35 * qp_multiply, 0); break; case VideoQuality::HIGH: - av_dict_set_int(&options, "qp", 32 * qp_multiply, 0); + av_dict_set_int(&options, "qp", 30 * qp_multiply, 0); break; case VideoQuality::VERY_HIGH: - av_dict_set_int(&options, "qp", 28 * qp_multiply, 0); + av_dict_set_int(&options, "qp", 25 * qp_multiply, 0); break; case VideoQuality::ULTRA: - av_dict_set_int(&options, "qp", 24 * qp_multiply, 0); + av_dict_set_int(&options, "qp", 22 * qp_multiply, 0); break; } } @@ -972,20 +1013,27 @@ static void open_video_hardware(AVCodecContext *codec_context, VideoQuality vide //av_dict_set_int(&options, "low_power", 1, 0); if(codec_context->codec_id == AV_CODEC_ID_H264) { - av_dict_set(&options, "profile", "high", 0); + // TODO: + if(color_depth == GSR_COLOR_DEPTH_10_BITS) + av_dict_set(&options, "profile", "high10", 0); + else + av_dict_set(&options, "profile", "high", 0); // Removed because it causes stutter in games for some people //av_dict_set_int(&options, "quality", 5, 0); // quality preset } else if(codec_context->codec_id == AV_CODEC_ID_AV1) { av_dict_set(&options, "profile", "main", 0); // TODO: use professional instead? av_dict_set(&options, "tier", "main", 0); } else if(codec_context->codec_id == AV_CODEC_ID_HEVC) { - if(hdr) { + if(color_depth == GSR_COLOR_DEPTH_10_BITS) av_dict_set(&options, "profile", "main10", 0); - av_dict_set(&options, "sei", "hdr", 0); - } else { + else av_dict_set(&options, "profile", "main", 0); - } + + if(hdr) + av_dict_set(&options, "sei", "hdr", 0); } + + // TODO: vp8/vp9 10bit } if(codec_context->codec_id == AV_CODEC_ID_H264) { @@ -1004,7 +1052,7 @@ 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 [-c ] [-s WxH] -f [-a ] [-q ] [-r ] [-k h264|hevc|hevc_hdr|av1|av1_hdr|vp8|vp9] [-ac aac|opus|flac] [-ab ] [-oc yes|no] [-fm cfr|vfr|content] [-cr limited|full] [-df yes|no] [-sc ] [-cursor yes|no] [-keyint ] [-restore-portal-session yes|no] [-portal-session-token-filepath filepath] [-encoder gpu|cpu] [-o ] [-v yes|no] [-h|--help]\n", program_name); + fprintf(stderr, "usage: %s -w [-c ] [-s WxH] -f [-a ] [-q ] [-r ] [-k h264|hevc|av1|vp8|vp9|hevc_hdr|av1_hdr|hevc_10bit|av1_10bit] [-ac aac|opus|flac] [-ab ] [-oc yes|no] [-fm cfr|vfr|content] [-cr limited|full] [-df yes|no] [-sc ] [-cursor yes|no] [-keyint ] [-restore-portal-session yes|no] [-portal-session-token-filepath filepath] [-encoder gpu|cpu] [-o ] [-v yes|no] [-h|--help]\n", program_name); } // TODO: Update with portal info @@ -1047,9 +1095,12 @@ static void usage_full() { fprintf(stderr, " and the video will only be saved when the gpu-screen-recorder is closed. This feature is similar to Nvidia's instant replay feature.\n"); fprintf(stderr, " This option has be between 5 and 1200. Note that the replay buffer size will not always be precise, because of keyframes. Optional, disabled by default.\n"); fprintf(stderr, "\n"); - fprintf(stderr, " -k Video codec to use. Should be either 'auto', 'h264', 'hevc', 'av1', 'hevc_hdr', 'av1_hdr', 'vp8' or 'vp9'. Optional, set to 'auto' by default which defaults to 'h264'.\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, " 'hevc_hdr' and 'av1_hdr' option is not available on X11 nor when using the portal capture option.\n"); + fprintf(stderr, " 'hevc_10bit' and 'av1_10bit' options allow you to select 10 bit color depth which can reduce banding and improve quality in darker areas, but not all video players support 10 bit color depth\n"); + fprintf(stderr, " and if you upload the video to a website the website might reduce 10 bit to 8 bit.\n"); + fprintf(stderr, " Note that when using 'hevc_hdr' or 'av1_hdr' the color depth is also 10 bits.\n"); fprintf(stderr, "\n"); fprintf(stderr, " -ac Audio codec to use. Should be either 'aac', 'opus' or 'flac'. Optional, set to 'opus' for .mp4/.mkv files, otherwise set to 'aac'.\n"); fprintf(stderr, " 'opus' and 'flac' is only supported by .mp4/.mkv files. 'opus' is recommended for best performance and smallest audio size.\n"); @@ -1068,7 +1119,8 @@ static void usage_full() { fprintf(stderr, "\n"); fprintf(stderr, " -cr Color range. Should be either 'limited' (aka mpeg) or 'full' (aka jpeg). Optional, set to 'limited' by default.\n"); fprintf(stderr, " Limited color range means that colors are in range 16-235 (4112-60395 for hdr) while full color range means that colors are in range 0-255 (0-65535 for hdr).\n"); - fprintf(stderr, " Note that some buggy video players (such as vlc) are unable to correctly display videos in full color range.\n"); + fprintf(stderr, " Note that some buggy video players (such as vlc) are unable to correctly display videos in full color range and when upload the video to websites the website\n"); + fprintf(stderr, " might re-encoder the video to make the video limited color range.\n"); fprintf(stderr, "\n"); fprintf(stderr, " -df Organise replays in folders based on the current date.\n"); fprintf(stderr, "\n"); @@ -1100,7 +1152,7 @@ static void usage_full() { 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, av1, av1_hdr, vp8, vp9, (if supported)).\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"); @@ -1692,12 +1744,14 @@ static void list_supported_video_codecs(gsr_egl *egl, bool wayland) { if(find_hevc_encoder(egl->gpu_info.vendor, egl->card_path)) { puts("hevc"); if(wayland) - puts("hevc_hdr"); + puts("hevc_hdr"); // TODO: Verify if it's actually supported + puts("hevc_10bit"); // TODO: Verify if it's actually supported } if(find_av1_encoder(egl->gpu_info.vendor, egl->card_path)) { puts("av1"); if(wayland) - puts("av1_hdr"); + puts("av1_hdr"); // TODO: Verify if it's actually supported + puts("av1_10bit"); // TODO: Verify if it's actually supported } if(find_vp8_encoder(egl->gpu_info.vendor, egl->card_path)) puts("vp8"); @@ -1837,7 +1891,10 @@ static void list_audio_devices_command() { _exit(0); } -static gsr_capture* create_capture_impl(const char *window_str, const char *screen_region, bool wayland, gsr_egl *egl, int fps, bool overclock, VideoCodec video_codec, gsr_color_range color_range, bool record_cursor, bool track_damage, bool use_software_video_encoder, bool restore_portal_session, const char *portal_session_token_filepath) { +static gsr_capture* create_capture_impl(const char *window_str, const char *screen_region, bool wayland, gsr_egl *egl, int fps, bool overclock, VideoCodec video_codec, gsr_color_range color_range, + bool record_cursor, bool track_damage, bool use_software_video_encoder, 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; @@ -1879,7 +1936,7 @@ static gsr_capture* create_capture_impl(const char *window_str, const char *scre gsr_capture_portal_params portal_params; portal_params.egl = egl; - portal_params.hdr = video_codec_is_hdr(video_codec); + portal_params.color_depth = color_depth; portal_params.color_range = color_range; portal_params.record_cursor = record_cursor; portal_params.restore_portal_session = restore_portal_session; @@ -1955,7 +2012,7 @@ static gsr_capture* create_capture_impl(const char *window_str, const char *scre nvfbc_params.size = { 0, 0 }; nvfbc_params.direct_capture = direct_capture; nvfbc_params.overclock = overclock; - nvfbc_params.hdr = video_codec_is_hdr(video_codec); + nvfbc_params.color_depth = color_depth; nvfbc_params.color_range = color_range; nvfbc_params.record_cursor = record_cursor; nvfbc_params.use_software_video_encoder = use_software_video_encoder; @@ -1966,9 +2023,10 @@ static gsr_capture* create_capture_impl(const char *window_str, const char *scre gsr_capture_kms_params kms_params; kms_params.egl = egl; kms_params.display_to_capture = window_str; - kms_params.hdr = video_codec_is_hdr(video_codec); + kms_params.color_depth = color_depth; kms_params.color_range = color_range; kms_params.record_cursor = record_cursor; + kms_params.hdr = video_codec_is_hdr(video_codec); capture = gsr_capture_kms_create(&kms_params); if(!capture) _exit(1); @@ -1996,6 +2054,7 @@ static gsr_capture* create_capture_impl(const char *window_str, const char *scre xcomposite_params.color_range = color_range; xcomposite_params.record_cursor = record_cursor; xcomposite_params.track_damage = track_damage; + xcomposite_params.color_depth = color_depth; capture = gsr_capture_xcomposite_create(&xcomposite_params); if(!capture) _exit(1); @@ -2004,13 +2063,13 @@ static gsr_capture* create_capture_impl(const char *window_str, const char *scre return capture; } -static gsr_video_encoder* create_video_encoder(gsr_egl *egl, bool overclock, bool hdr, bool use_software_video_encoder) { +static gsr_video_encoder* create_video_encoder(gsr_egl *egl, bool overclock, gsr_color_depth color_depth, bool use_software_video_encoder) { gsr_video_encoder *video_encoder = nullptr; if(use_software_video_encoder) { gsr_video_encoder_software_params params; params.egl = egl; - params.hdr = hdr; + params.color_depth = color_depth; video_encoder = gsr_video_encoder_software_create(¶ms); return video_encoder; } @@ -2020,7 +2079,7 @@ static gsr_video_encoder* create_video_encoder(gsr_egl *egl, bool overclock, boo case GSR_GPU_VENDOR_INTEL: { gsr_video_encoder_vaapi_params params; params.egl = egl; - params.hdr = hdr; + params.color_depth = color_depth; video_encoder = gsr_video_encoder_vaapi_create(¶ms); break; } @@ -2028,7 +2087,7 @@ static gsr_video_encoder* create_video_encoder(gsr_egl *egl, bool overclock, boo gsr_video_encoder_cuda_params params; params.egl = egl; params.overclock = overclock; - params.hdr = hdr; + params.color_depth = color_depth; video_encoder = gsr_video_encoder_cuda_create(¶ms); break; } @@ -2116,6 +2175,208 @@ static std::vector parse_audio_inputs(const AudioDevices &aud return requested_audio_inputs; } +static AudioCodec select_audio_codec_with_fallback(AudioCodec audio_codec, const std::string &file_extension,bool uses_amix) { + switch(audio_codec) { + case AudioCodec::AAC: { + if(file_extension == "webm") { + //audio_codec_to_use = "opus"; + audio_codec = AudioCodec::OPUS; + fprintf(stderr, "Warning: .webm files only support opus audio codec, changing audio codec from aac to opus\n"); + } + break; + } + case AudioCodec::OPUS: { + // TODO: Also check mpegts? + if(file_extension != "mp4" && file_extension != "mkv" && file_extension != "webm") { + //audio_codec_to_use = "aac"; + audio_codec = AudioCodec::AAC; + fprintf(stderr, "Warning: opus audio codec is only supported by .mp4, .mkv and .webm files, falling back to aac instead\n"); + } + break; + } + case AudioCodec::FLAC: { + // TODO: Also check mpegts? + if(file_extension == "webm") { + //audio_codec_to_use = "opus"; + audio_codec = AudioCodec::OPUS; + fprintf(stderr, "Warning: .webm files only support opus audio codec, changing audio codec from flac to opus\n"); + } else if(file_extension != "mp4" && file_extension != "mkv") { + //audio_codec_to_use = "aac"; + audio_codec = AudioCodec::AAC; + fprintf(stderr, "Warning: flac audio codec is only supported by .mp4 and .mkv files, falling back to aac instead\n"); + } else if(uses_amix) { + // TODO: remove this? is it true anymore? + //audio_codec_to_use = "opus"; + audio_codec = AudioCodec::OPUS; + fprintf(stderr, "Warning: flac audio codec is not supported when mixing audio sources, falling back to opus instead\n"); + } + break; + } + } + return audio_codec; +} + +static const AVCodec* select_video_codec_with_fallback(VideoCodec video_codec, const char *video_codec_to_use, const char *file_extension, bool use_software_video_encoder, const gsr_egl *egl) { + const bool video_codec_auto = strcmp(video_codec_to_use, "auto") == 0; + if(video_codec_auto) { + if(strcmp(file_extension, "webm") == 0) { + fprintf(stderr, "Info: using vp8 encoder because a codec was not specified and the file extension is .webm\n"); + video_codec_to_use = "vp8"; + video_codec = VideoCodec::VP8; + } else { + fprintf(stderr, "Info: using h264 encoder because a codec was not specified\n"); + video_codec_to_use = "h264"; + video_codec = VideoCodec::H264; + } + } + + // TODO: Allow hevc, vp9 and av1 in (enhanced) flv (supported since ffmpeg 6.1) + const bool is_flv = strcmp(file_extension, "flv") == 0; + if(is_flv) { + if(video_codec != VideoCodec::H264) { + video_codec_to_use = "h264"; + video_codec = VideoCodec::H264; + fprintf(stderr, "Warning: hevc/av1 is not compatible with flv, falling back to h264 instead.\n"); + } + + // if(audio_codec != AudioCodec::AAC) { + // audio_codec_to_use = "aac"; + // audio_codec = AudioCodec::AAC; + // fprintf(stderr, "Warning: flv only supports aac, falling back to aac instead.\n"); + // } + } + + const bool is_hls = strcmp(file_extension, "m3u8") == 0; + if(is_hls) { + if(video_codec_is_av1(video_codec)) { + video_codec_to_use = "hevc"; + video_codec = VideoCodec::HEVC; + fprintf(stderr, "Warning: av1 is not compatible with hls (m3u8), falling back to hevc instead.\n"); + } + + // if(audio_codec != AudioCodec::AAC) { + // audio_codec_to_use = "aac"; + // audio_codec = AudioCodec::AAC; + // fprintf(stderr, "Warning: hls (m3u8) only supports aac, falling back to aac instead.\n"); + // } + } + + if(use_software_video_encoder && video_codec != VideoCodec::H264) { + fprintf(stderr, "Error: \"-encoder cpu\" option is currently only available when using h264 codec option (-k)\n"); + usage(); + } + + const AVCodec *video_codec_f = nullptr; + switch(video_codec) { + case VideoCodec::H264: { + if(use_software_video_encoder) { + video_codec_f = find_h264_software_encoder(); + } else { + video_codec_f = find_h264_encoder(egl->gpu_info.vendor, egl->card_path); + } + break; + } + case VideoCodec::HEVC: + case VideoCodec::HEVC_HDR: + case VideoCodec::HEVC_10BIT: + // TODO: software encoder + video_codec_f = find_hevc_encoder(egl->gpu_info.vendor, egl->card_path); + break; + case VideoCodec::AV1: + case VideoCodec::AV1_HDR: + case VideoCodec::AV1_10BIT: + // TODO: software encoder + video_codec_f = find_av1_encoder(egl->gpu_info.vendor, egl->card_path); + break; + case VideoCodec::VP8: + // TODO: software encoder + video_codec_f = find_vp8_encoder(egl->gpu_info.vendor, egl->card_path); + break; + case VideoCodec::VP9: + // TODO: software encoder + video_codec_f = find_vp9_encoder(egl->gpu_info.vendor, egl->card_path); + break; + } + + if(!video_codec_auto && !video_codec_f && !is_flv) { + switch(video_codec) { + case VideoCodec::H264: { + fprintf(stderr, "Warning: selected video codec h264 is not supported, trying hevc instead\n"); + video_codec_to_use = "hevc"; + video_codec = VideoCodec::HEVC; + video_codec_f = find_hevc_encoder(egl->gpu_info.vendor, egl->card_path); + break; + } + case VideoCodec::HEVC: + case VideoCodec::HEVC_HDR: + case VideoCodec::HEVC_10BIT: { + fprintf(stderr, "Warning: selected video codec hevc is not supported, trying h264 instead\n"); + video_codec_to_use = "h264"; + video_codec = VideoCodec::H264; + video_codec_f = find_h264_encoder(egl->gpu_info.vendor, egl->card_path); + break; + } + case VideoCodec::AV1: + case VideoCodec::AV1_HDR: + case VideoCodec::AV1_10BIT: { + fprintf(stderr, "Warning: selected video codec av1 is not supported, trying h264 instead\n"); + video_codec_to_use = "h264"; + video_codec = VideoCodec::H264; + video_codec_f = find_h264_encoder(egl->gpu_info.vendor, egl->card_path); + break; + } + case VideoCodec::VP8: + case VideoCodec::VP9: + // TODO: + break; + } + } + + if(!video_codec_f) { + const char *video_codec_name = ""; + switch(video_codec) { + case VideoCodec::H264: { + video_codec_name = "h264"; + break; + } + case VideoCodec::HEVC: + case VideoCodec::HEVC_HDR: + case VideoCodec::HEVC_10BIT: { + video_codec_name = "hevc"; + break; + } + case VideoCodec::AV1: + case VideoCodec::AV1_HDR: + case VideoCodec::AV1_10BIT: { + video_codec_name = "av1"; + break; + } + case VideoCodec::VP8: { + video_codec_name = "vp8"; + break; + } + case VideoCodec::VP9: { + video_codec_name = "vp9"; + break; + } + } + + fprintf(stderr, "Error: your gpu does not support '%s' video codec. If you are sure that your gpu does support '%s' video encoding and you are using an AMD/Intel GPU,\n" + " then make sure you have installed the GPU specific vaapi packages (intel-media-driver, libva-intel-driver, libva-mesa-driver and linux-firmware).\n" + " It's also possible that your distro has disabled hardware accelerated video encoding for '%s' video codec.\n" + " This may be the case on corporate distros such as Manjaro, Fedora or OpenSUSE.\n" + " You can test this by running 'vainfo | grep VAEntrypointEncSlice' to see if it matches any H264/HEVC profile.\n" + " On such distros, you need to manually install mesa from source to enable H264/HEVC hardware acceleration, or use a more user friendly distro. Alternatively record with AV1 if supported by your GPU.\n" + " You can alternatively use the flatpak version of GPU Screen Recorder (https://flathub.org/apps/com.dec05eba.gpu_screen_recorder) which bypasses system issues with patented H264/HEVC codecs.\n" + " Make sure you have mesa-extra freedesktop runtime installed when using the flatpak (this should be the default), which can be installed with this command:\n" + " flatpak install --system org.freedesktop.Platform.GL.default//23.08-extra\n" + " If your GPU doesn't support hardware accelerated video encoding then you can use '-encoder cpu' option to encode with your cpu instead.\n", video_codec_name, video_codec_name, video_codec_name); + _exit(2); + } + + return video_codec_f; +} + int main(int argc, char **argv) { signal(SIGINT, stop_handler); signal(SIGUSR1, save_replay_handler); @@ -2218,16 +2479,20 @@ int main(int argc, char **argv) { video_codec = VideoCodec::HEVC; } else if(strcmp(video_codec_to_use, "hevc_hdr") == 0) { video_codec = VideoCodec::HEVC_HDR; + } else if(strcmp(video_codec_to_use, "hevc_10bit") == 0) { + video_codec = VideoCodec::HEVC_10BIT; } else if(strcmp(video_codec_to_use, "av1") == 0) { video_codec = VideoCodec::AV1; } else if(strcmp(video_codec_to_use, "av1_hdr") == 0) { video_codec = VideoCodec::AV1_HDR; + } else if(strcmp(video_codec_to_use, "av1_10bit") == 0) { + video_codec = VideoCodec::AV1_10BIT; } else if(strcmp(video_codec_to_use, "vp8") == 0) { video_codec = VideoCodec::VP8; } else if(strcmp(video_codec_to_use, "vp9") == 0) { video_codec = VideoCodec::VP9; } else if(strcmp(video_codec_to_use, "auto") != 0) { - fprintf(stderr, "Error: -k should either be either 'auto', 'h264', 'hevc', 'hevc_hdr', 'av1', 'av1_hdr', 'vp8' or 'vp9', got: '%s'\n", video_codec_to_use); + fprintf(stderr, "Error: -k should either be either 'auto', 'h264', 'hevc', 'av1', 'vp8', 'vp9', 'hevc_hdr', 'av1_hdr', 'hevc_10bit' or 'av1_10bit', got: '%s'\n", video_codec_to_use); usage(); } @@ -2638,199 +2903,13 @@ int main(int argc, char **argv) { } const bool force_no_audio_offset = is_livestream || is_output_piped || (file_extension != "mp4" && file_extension != "mkv" && file_extension != "webm"); - - switch(audio_codec) { - case AudioCodec::AAC: { - if(file_extension == "webm") { - audio_codec_to_use = "opus"; - audio_codec = AudioCodec::OPUS; - fprintf(stderr, "Warning: .webm files only support opus audio codec, changing audio codec from aac to opus\n"); - } - break; - } - case AudioCodec::OPUS: { - // TODO: Also check mpegts? - if(file_extension != "mp4" && file_extension != "mkv" && file_extension != "webm") { - audio_codec_to_use = "aac"; - audio_codec = AudioCodec::AAC; - fprintf(stderr, "Warning: opus audio codec is only supported by .mp4, .mkv and .webm files, falling back to aac instead\n"); - } - break; - } - case AudioCodec::FLAC: { - // TODO: Also check mpegts? - if(file_extension == "webm") { - audio_codec_to_use = "opus"; - audio_codec = AudioCodec::OPUS; - fprintf(stderr, "Warning: .webm files only support opus audio codec, changing audio codec from flac to opus\n"); - } else if(file_extension != "mp4" && file_extension != "mkv") { - audio_codec_to_use = "aac"; - audio_codec = AudioCodec::AAC; - fprintf(stderr, "Warning: flac audio codec is only supported by .mp4 and .mkv files, falling back to aac instead\n"); - } else if(uses_amix) { - // TODO: remove this? is it true anymore? - audio_codec_to_use = "opus"; - audio_codec = AudioCodec::OPUS; - fprintf(stderr, "Warning: flac audio codec is not supported when mixing audio sources, falling back to opus instead\n"); - } - break; - } - } - const double target_fps = 1.0 / (double)fps; - const bool video_codec_auto = strcmp(video_codec_to_use, "auto") == 0; - if(video_codec_auto) { - if(strcmp(file_extension.c_str(), "webm") == 0) { - fprintf(stderr, "Info: using vp8 encoder because a codec was not specified and the file extension is .webm\n"); - video_codec_to_use = "vp8"; - video_codec = VideoCodec::VP8; - } else { - fprintf(stderr, "Info: using h264 encoder because a codec was not specified\n"); - video_codec_to_use = "h264"; - video_codec = VideoCodec::H264; - } - } - - // TODO: Allow hevc, vp9 and av1 in (enhanced) flv (supported since ffmpeg 6.1) - const bool is_flv = strcmp(file_extension.c_str(), "flv") == 0; - if(is_flv) { - if(video_codec != VideoCodec::H264) { - video_codec_to_use = "h264"; - video_codec = VideoCodec::H264; - fprintf(stderr, "Warning: hevc/av1 is not compatible with flv, falling back to h264 instead.\n"); - } - - if(audio_codec != AudioCodec::AAC) { - audio_codec_to_use = "aac"; - audio_codec = AudioCodec::AAC; - fprintf(stderr, "Warning: flv only supports aac, falling back to aac instead.\n"); - } - } - - const bool is_hls = strcmp(file_extension.c_str(), "m3u8") == 0; - if(is_hls) { - if(video_codec == VideoCodec::AV1 || video_codec == VideoCodec::AV1_HDR) { - video_codec_to_use = "hevc"; - video_codec = VideoCodec::HEVC; - fprintf(stderr, "Warning: av1 is not compatible with hls (m3u8), falling back to hevc instead.\n"); - } - - if(audio_codec != AudioCodec::AAC) { - audio_codec_to_use = "aac"; - audio_codec = AudioCodec::AAC; - fprintf(stderr, "Warning: hls (m3u8) only supports aac, falling back to aac instead.\n"); - } - } - - if(use_software_video_encoder && video_codec != VideoCodec::H264) { - fprintf(stderr, "Error: \"-encoder cpu\" option is currently only available when using h264 codec option (-k)\n"); - usage(); - } - - const AVCodec *video_codec_f = nullptr; - switch(video_codec) { - case VideoCodec::H264: { - if(use_software_video_encoder) { - video_codec_f = find_h264_software_encoder(); - } else { - video_codec_f = find_h264_encoder(egl.gpu_info.vendor, egl.card_path); - } - break; - } - case VideoCodec::HEVC: - case VideoCodec::HEVC_HDR: - // TODO: software encoder - video_codec_f = find_hevc_encoder(egl.gpu_info.vendor, egl.card_path); - break; - case VideoCodec::AV1: - case VideoCodec::AV1_HDR: - // TODO: software encoder - video_codec_f = find_av1_encoder(egl.gpu_info.vendor, egl.card_path); - break; - case VideoCodec::VP8: - // TODO: software encoder - video_codec_f = find_vp8_encoder(egl.gpu_info.vendor, egl.card_path); - break; - case VideoCodec::VP9: - // TODO: software encoder - video_codec_f = find_vp9_encoder(egl.gpu_info.vendor, egl.card_path); - break; - } - - if(!video_codec_auto && !video_codec_f && !is_flv) { - switch(video_codec) { - case VideoCodec::H264: { - fprintf(stderr, "Warning: selected video codec h264 is not supported, trying hevc instead\n"); - video_codec_to_use = "hevc"; - video_codec = VideoCodec::HEVC; - video_codec_f = find_hevc_encoder(egl.gpu_info.vendor, egl.card_path); - break; - } - case VideoCodec::HEVC: - case VideoCodec::HEVC_HDR: { - fprintf(stderr, "Warning: selected video codec hevc is not supported, trying h264 instead\n"); - video_codec_to_use = "h264"; - video_codec = VideoCodec::H264; - video_codec_f = find_h264_encoder(egl.gpu_info.vendor, egl.card_path); - break; - } - case VideoCodec::AV1: - case VideoCodec::AV1_HDR: { - fprintf(stderr, "Warning: selected video codec av1 is not supported, trying h264 instead\n"); - video_codec_to_use = "h264"; - video_codec = VideoCodec::H264; - video_codec_f = find_h264_encoder(egl.gpu_info.vendor, egl.card_path); - break; - } - case VideoCodec::VP8: - case VideoCodec::VP9: - // TODO: - break; - } - } - - if(!video_codec_f) { - const char *video_codec_name = ""; - switch(video_codec) { - case VideoCodec::H264: { - video_codec_name = "h264"; - break; - } - case VideoCodec::HEVC: - case VideoCodec::HEVC_HDR: { - video_codec_name = "hevc"; - break; - } - case VideoCodec::AV1: - case VideoCodec::AV1_HDR: { - video_codec_name = "av1"; - break; - } - case VideoCodec::VP8: { - video_codec_name = "vp8"; - break; - } - case VideoCodec::VP9: { - video_codec_name = "vp9"; - break; - } - } - - fprintf(stderr, "Error: your gpu does not support '%s' video codec. If you are sure that your gpu does support '%s' video encoding and you are using an AMD/Intel GPU,\n" - " then make sure you have installed the GPU specific vaapi packages (intel-media-driver, libva-intel-driver, libva-mesa-driver and linux-firmware).\n" - " It's also possible that your distro has disabled hardware accelerated video encoding for '%s' video codec.\n" - " This may be the case on corporate distros such as Manjaro, Fedora or OpenSUSE.\n" - " You can test this by running 'vainfo | grep VAEntrypointEncSlice' to see if it matches any H264/HEVC profile.\n" - " On such distros, you need to manually install mesa from source to enable H264/HEVC hardware acceleration, or use a more user friendly distro. Alternatively record with AV1 if supported by your GPU.\n" - " You can alternatively use the flatpak version of GPU Screen Recorder (https://flathub.org/apps/com.dec05eba.gpu_screen_recorder) which bypasses system issues with patented H264/HEVC codecs.\n" - " Make sure you have mesa-extra freedesktop runtime installed when using the flatpak (this should be the default), which can be installed with this command:\n" - " flatpak install --system org.freedesktop.Platform.GL.default//23.08-extra\n" - " If your GPU doesn't support hardware accelerated video encoding then you can use '-encoder cpu' option to encode with your cpu instead.\n", video_codec_name, video_codec_name, video_codec_name); - _exit(2); - } + 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); - gsr_capture *capture = create_capture_impl(window_str, screen_region, wayland, &egl, fps, overclock, video_codec, color_range, record_cursor, framerate_mode == FramerateMode::CONTENT, use_software_video_encoder, restore_portal_session, portal_session_token_filepath); + 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, overclock, video_codec, color_range, record_cursor, framerate_mode == FramerateMode::CONTENT, 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. @@ -2875,7 +2954,7 @@ int main(int argc, char **argv) { _exit(capture_result); } - gsr_video_encoder *video_encoder = create_video_encoder(&egl, overclock, hdr, use_software_video_encoder); + gsr_video_encoder *video_encoder = create_video_encoder(&egl, overclock, color_depth, use_software_video_encoder); if(!video_encoder) { fprintf(stderr, "Error: failed to create video encoder\n"); _exit(1); @@ -2903,9 +2982,9 @@ int main(int argc, char **argv) { gsr_color_conversion_clear(&color_conversion); if(use_software_video_encoder) { - open_video_software(video_codec_context, quality, pixel_format, hdr); + open_video_software(video_codec_context, quality, pixel_format, hdr, color_depth); } else { - open_video_hardware(video_codec_context, quality, very_old_gpu, egl.gpu_info.vendor, pixel_format, hdr); + open_video_hardware(video_codec_context, quality, very_old_gpu, egl.gpu_info.vendor, pixel_format, hdr, color_depth); } if(video_stream) avcodec_parameters_from_context(video_stream->codecpar, video_codec_context); -- cgit v1.2.3