From c7a72553c4b61590b7ceffb49b04bddcd887e694 Mon Sep 17 00:00:00 2001 From: loki Date: Tue, 14 Apr 2020 00:15:24 +0300 Subject: [PATCH] Configure settings nvenc --- assets/sunshine.conf | 60 ++++- sunshine/config.cpp | 153 ++++++++++- sunshine/config.h | 18 +- sunshine/main.cpp | 4 +- sunshine/nvhttp.cpp | 8 +- sunshine/platform/windows_dxgi.cpp | 19 -- sunshine/rtsp.cpp | 6 +- sunshine/video.cpp | 395 +++++++++++++++++------------ sunshine/video.h | 2 +- 9 files changed, 454 insertions(+), 211 deletions(-) diff --git a/assets/sunshine.conf b/assets/sunshine.conf index daaf000b..3fb5cf17 100644 --- a/assets/sunshine.conf +++ b/assets/sunshine.conf @@ -105,20 +105,64 @@ # Increasing the value slightly reduces encoding efficiency, but the tradeoff is usually # worth it to gain the use of more CPU cores for encoding. The ideal value is the lowest # value that can reliably encode at your desired streaming settings on your hardware. -# min_threads = 2 +# min_threads = 1 # Allows the client to request HEVC Main or HEVC Main10 video streams. -# HEVC is more CPU-intensive to encode, so enabling this may reduce performance. -# If set to 0 (default), Sunshine will not advertise support for HEVC -# If set to 1, Sunshine will advertise support for HEVC Main profile -# If set to 2, Sunshine will advertise support for HEVC Main and Main10 (HDR) profiles -# hevc_mode = 2 +# HEVC is more CPU-intensive to encode, so enabling this may reduce performance when using software encoding. +# If set to 0 (default), Sunshine will specify support for HEVC based on encoder +# If set to 1, Sunshine will not advertise support for HEVC +# If set to 2, Sunshine will advertise support for HEVC Main profile +# If set to 3, Sunshine will advertise support for HEVC Main and Main10 (HDR) profiles +# hevc_mode = 0 +# Force a specific encoder, otherwise Sunshine will use the first encoder that is available +# supported encoders: +# nvenc +# software +# +# encoder = nvenc +##################################### Software ##################################### # See x264 --fullhelp for the different presets -# preset = superfast -# tune = zerolatency +# sw_preset = superfast +# sw_tune = zerolatency # + +##################################### NVENC ##################################### +###### presets ########### +# default +# hp -- high performance +# hq -- high quality +# slow -- hq 2 passes +# medium -- hq 1 pass +# fast -- hp 1 pass +# bd +# ll -- low latency +# llhq +# llhp +# lossless +# losslesshp +########################## +# nv_preset = llhq # +####### rate control ##### +# auto -- let ffmpeg decide rate control +# constqp -- constant QP mode +# vbr -- variable bitrate +# cbr -- constant bitrate +# cbr_hq -- cbr high quality +# cbr_ld_hq -- cbr low delay high quality +# vbr_hq -- vbr high quality +########################## +# nv_rc = auto + +###### h264 entropy ###### +# auto -- let ffmpeg nvenc decide the entropy encoding +# cabac +# cavlc +########################## +# nv_coder = auto + + ############################################## # Some configurable parameters, are merely toggles for specific features # The first occurrence turns it on, the second occurence turns it off, the third occurence turns it on again, etc, etc diff --git a/sunshine/config.cpp b/sunshine/config.cpp index 5eeabf71..2b027e1c 100644 --- a/sunshine/config.cpp +++ b/sunshine/config.cpp @@ -15,17 +15,97 @@ #define APPS_JSON_PATH SUNSHINE_ASSETS_DIR "/" APPS_JSON namespace config { using namespace std::literals; + +namespace nv { +enum preset_e : int { + _default = 0, + slow, + medium, + fast, + hp, + hq, + bd, + ll_default, + llhq, + llhp, + lossless_default, // lossless presets must be the last ones + lossless_hp, +}; + +enum rc_e : int { + constqp = 0x0, /**< Constant QP mode */ + vbr = 0x1, /**< Variable bitrate mode */ + cbr = 0x2, /**< Constant bitrate mode */ + cbr_ld_hq = 0x8, /**< low-delay CBR, high quality */ + cbr_hq = 0x10, /**< CBR, high quality (slower) */ + vbr_hq = 0x20 /**< VBR, high quality (slower) */ +}; + +enum coder_e : int { + _auto = 0, + cabac, + cavlc +}; + +std::optional preset_from_view(const std::string_view &preset) { +#define _CONVERT_(x) if(preset == #x##sv) return x + _CONVERT_(slow); + _CONVERT_(medium); + _CONVERT_(fast); + _CONVERT_(hp); + _CONVERT_(bd); + _CONVERT_(ll_default); + _CONVERT_(llhq); + _CONVERT_(llhp); + _CONVERT_(lossless_default); + _CONVERT_(lossless_hp); + if(preset == "default"sv) return _default; +#undef _CONVERT_ + return std::nullopt; +} + +std::optional rc_from_view(const std::string_view &rc) { +#define _CONVERT_(x) if(rc == #x##sv) return x + _CONVERT_(constqp); + _CONVERT_(vbr); + _CONVERT_(cbr); + _CONVERT_(cbr_hq); + _CONVERT_(vbr_hq); + _CONVERT_(cbr_ld_hq); +#undef _CONVERT_ + return std::nullopt; +} + +int coder_from_view(const std::string_view &coder) { + if(coder == "auto"sv) return _auto; + if(coder == "cabac"sv || coder == "ac"sv) return cabac; + if(coder == "cavlc"sv || coder == "vlc"sv) return cavlc; + + return -1; +} +} + video_t video { 0, // crf 28, // qp - 2, // min_threads - 0, // hevc_mode - "superfast"s, // preset - "zerolatency"s, // tune + + 1, // min_threads + { + "superfast"s, // preset + "zerolatency"s, // tune + }, // software + + { + nv::llhq, + std::nullopt, + -1 + }, // nv + + {}, // encoder {}, // adapter_name - {} // output_name + {} // output_name }; audio_t audio {}; @@ -138,6 +218,37 @@ void int_f(std::unordered_map &vars, const std::string vars.erase(it); } +void int_f(std::unordered_map &vars, const std::string &name, std::optional &input) { + auto it = vars.find(name); + + if(it == std::end(vars)) { + return; + } + + auto &val = it->second; + input = util::from_chars(&val[0], &val[0] + val.size()); + + vars.erase(it); +} + +template +void int_f(std::unordered_map &vars, const std::string &name, int &input, F &&f) { + std::string tmp; + string_f(vars, name, tmp); + if(!tmp.empty()) { + input = f(tmp); + } +} + +template +void int_f(std::unordered_map &vars, const std::string &name, std::optional &input, F &&f) { + std::string tmp; + string_f(vars, name, tmp); + if(!tmp.empty()) { + input = f(tmp); + } +} + void int_between_f(std::unordered_map &vars, const std::string &name, int &input, const std::pair &range) { int temp = input; @@ -149,6 +260,28 @@ void int_between_f(std::unordered_map &vars, const std } } +bool to_bool(std::string &boolean) { + std::for_each(std::begin(boolean), std::end(boolean), [](char ch) { return (char)std::tolower(ch); }); + + return + boolean == "true"sv || + boolean == "yes"sv || + boolean == "enable"sv || + (std::find(std::begin(boolean), std::end(boolean), '1') != std::end(boolean)); +} +void bool_f(std::unordered_map &vars, const std::string &name, int &input) { + std::string tmp; + string_restricted_f(vars, name, tmp, { + "enable"sv, "dis" + }); + + if(tmp.empty()) { + return; + } + + input = to_bool(tmp) ? 1 : 0; +} + void print_help(const char *name) { std::cout << "Usage: "sv << name << " [options] [/path/to/configuration_file]"sv << std::endl << @@ -190,10 +323,14 @@ void apply_config(std::unordered_map &&vars) { int_f(vars, "qp", video.qp); int_f(vars, "min_threads", video.min_threads); int_between_f(vars, "hevc_mode", video.hevc_mode, { - 0, 2 + 0, 3 }); - string_f(vars, "preset", video.preset); - string_f(vars, "tune", video.tune); + string_f(vars, "sw_preset", video.sw.preset); + string_f(vars, "sw_tune", video.sw.tune); + int_f(vars, "nv_preset", video.nv.preset, nv::preset_from_view); + int_f(vars, "nv_rc", video.nv.preset, nv::rc_from_view); + int_f(vars, "nv_coder", video.nv.coder, nv::coder_from_view); + string_f(vars, "encoder", video.encoder); string_f(vars, "adapter_name", video.adapter_name); string_f(vars, "output_name", video.output_name); diff --git a/sunshine/config.h b/sunshine/config.h index 93af19ee..419a5933 100644 --- a/sunshine/config.h +++ b/sunshine/config.h @@ -4,6 +4,7 @@ #include #include #include +#include namespace config { struct video_t { @@ -11,12 +12,21 @@ struct video_t { int crf; // higher == more compression and less quality int qp; // higher == more compression and less quality, ignored if crf != 0 - int min_threads; // Minimum number of threads/slices for CPU encoding - int hevc_mode; - std::string preset; - std::string tune; + int min_threads; // Minimum number of threads/slices for CPU encoding + struct { + std::string preset; + std::string tune; + } sw; + + struct { + std::optional preset; + std::optional rc; + int coder; + } nv; + + std::string encoder; std::string adapter_name; std::string output_name; }; diff --git a/sunshine/main.cpp b/sunshine/main.cpp index 6d0e6e65..c21f81fc 100644 --- a/sunshine/main.cpp +++ b/sunshine/main.cpp @@ -140,7 +140,9 @@ int main(int argc, char *argv[]) { auto deinit_guard = platf::init(); input::init(); reed_solomon_init(); - video::init(); + if(video::init()) { + return 2; + } task_pool.start(1); diff --git a/sunshine/nvhttp.cpp b/sunshine/nvhttp.cpp index fcbd69bb..261ec05d 100644 --- a/sunshine/nvhttp.cpp +++ b/sunshine/nvhttp.cpp @@ -464,13 +464,13 @@ void serverinfo(std::shared_ptr::Response> res tree.put("root.GfeVersion", GFE_VERSION); tree.put("root.uniqueid", unique_id); tree.put("root.mac", platf::get_mac_address(request->local_endpoint_address())); - tree.put("root.MaxLumaPixelsHEVC", config::video.hevc_mode > 0 ? "1869449984" : "0"); + tree.put("root.MaxLumaPixelsHEVC", config::video.hevc_mode > 1 ? "1869449984" : "0"); tree.put("root.LocalIP", request->local_endpoint_address()); - if(config::video.hevc_mode == 2) { + if(config::video.hevc_mode == 3) { tree.put("root.ServerCodecModeSupport", "3843"); } - else if(config::video.hevc_mode == 1) { + else if(config::video.hevc_mode == 2) { tree.put("root.ServerCodecModeSupport", "259"); } else { @@ -522,7 +522,7 @@ void applist(resp_https_t response, req_https_t request) { for(auto &proc : proc::proc.get_apps()) { pt::ptree app; - app.put("IsHdrSupported"s, config::video.hevc_mode == 2 ? 1 : 0); + app.put("IsHdrSupported"s, config::video.hevc_mode == 3 ? 1 : 0); app.put("AppTitle"s, proc.name); app.put("ID"s, ++x); diff --git a/sunshine/platform/windows_dxgi.cpp b/sunshine/platform/windows_dxgi.cpp index 3988b344..2278a148 100644 --- a/sunshine/platform/windows_dxgi.cpp +++ b/sunshine/platform/windows_dxgi.cpp @@ -888,7 +888,6 @@ public: img->texture.reset(tex_p); img->height = height; img->width = width; - img->data = (std::uint8_t*)tex_p; img->pixel_pitch = 4; return 0; @@ -916,24 +915,6 @@ public: return hwdevice; } - - int init() { - if(display_base_t::init()) { - return -1; - } - - multithread_t::pointer multithread_p {}; - auto status = device->QueryInterface(__uuidof(multithread_t::element_type), (void**)&multithread_p); - multithread_t multithread { multithread_p }; - - if(FAILED(status)) { - BOOST_LOG(error) << "Couldn't query Multithread interface [0x"sv << util::hex(status).to_string_view() << ']'; - return -1; - } - multithread->SetMultithreadProtected(true); - - return 0; - } }; const char *format_str[] = { diff --git a/sunshine/rtsp.cpp b/sunshine/rtsp.cpp index 565b3cde..70aac786 100644 --- a/sunshine/rtsp.cpp +++ b/sunshine/rtsp.cpp @@ -97,7 +97,7 @@ public: std::vector full_payload; auto old_msg = std::move(_queue_packet); - TUPLE_2D_REF(_, old_packet, old_msg); + auto &old_packet = old_msg.second; std::string_view new_payload{(char *) packet->data, packet->dataLength}; std::string_view old_payload{(char *) old_packet->data, old_packet->dataLength}; @@ -274,7 +274,7 @@ void cmd_describe(rtsp_server_t *server, net::peer_t peer, msg_t&& req) { option.content = const_cast(seqn_str.c_str()); std::string_view payload; - if(config::video.hevc_mode == 0) { + if(config::video.hevc_mode == 1) { payload = "surround-params=NONE"sv; } else { @@ -404,7 +404,7 @@ void cmd_announce(rtsp_server_t *server, net::peer_t peer, msg_t &&req) { return; } - if(config.monitor.videoFormat != 0 && config::video.hevc_mode == 0) { + if(config.monitor.videoFormat != 0 && config::video.hevc_mode == 1) { BOOST_LOG(warning) << "HEVC is disabled, yet the client requested HEVC"sv; respond(server->host(), peer, &option, 400, "BAD REQUEST", req->sequenceNumber, {}); diff --git a/sunshine/video.cpp b/sunshine/video.cpp index d4bbc936..6e6933c9 100644 --- a/sunshine/video.cpp +++ b/sunshine/video.cpp @@ -38,20 +38,6 @@ void free_packet(AVPacket *packet) { } namespace nv { -enum class preset_e : int { - _default = 0, - slow, - medium, - fast, - hp, - hq, - bd, - ll_default, - llhq, - llhp, - lossless_default, // lossless presets must be the last ones - lossless_hp, -}; enum class profile_h264_e : int { baseline, @@ -79,16 +65,23 @@ void nv_d3d_img_to_frame(sws_t &sws, const platf::img_t &img, frame_t &frame); util::Either nv_d3d_make_hwdevice_ctx(platf::hwdevice_ctx_t *hwdevice_ctx); struct encoder_t { + std::string_view name; enum flag_e { PASSED, // Is supported REF_FRAMES_RESTRICT, // Set maximum reference frames REF_FRAMES_AUTOSELECT, // Allow encoder to select maximum reference frames (If !REF_FRAMES_RESTRICT --> REF_FRAMES_AUTOSELECT) + DYNAMIC_RANGE, MAX_FLAGS }; struct option_t { + KITTY_DEFAULT_CONSTR(option_t) + option_t(const option_t &) = default; + std::string name; - std::variant value; + std::variant*, std::string, std::string*> value; + + option_t(std::string &&name, decltype(value) &&value) : name { std::move(name) }, value { std::move(value) } {} }; struct { @@ -105,6 +98,8 @@ struct encoder_t { struct { std::vector options; + std::optional crf, qp; + std::string name; std::bitset capabilities; @@ -118,12 +113,27 @@ struct encoder_t { } hevc, h264; bool system_memory; + bool hevc_mode; std::function img_to_frame; std::function(platf::hwdevice_ctx_t *hwdevice)> make_hwdevice_ctx; }; struct session_t { + session_t() = default; + session_t(session_t&&) = default; + + // Ensure objects are destroyed in the correct order + session_t &operator=(session_t &&other) { + sws_color_format = other.sws_color_format; + sw_format = other.sw_format; + frame = std::move(other.frame); + ctx = std::move(other.ctx); + hwdevice = std::move(other.hwdevice); + + return *this; + } + buffer_t hwdevice; ctx_t ctx; @@ -159,66 +169,6 @@ struct encode_session_t { using encode_session_ctx_queue_t = safe::queue_t; using encode_e = platf::capture_e; -struct capture_synced_ctx_t { - encode_session_ctx_queue_t encode_session_ctx_queue; -}; - -int start_capture_sync(capture_synced_ctx_t &ctx); -void end_capture_sync(capture_synced_ctx_t &ctx); -auto capture_thread_sync = safe::make_shared(start_capture_sync, end_capture_sync); - -static encoder_t nvenc { - { (int)nv::profile_h264_e::high, (int)nv::profile_hevc_e::main, (int)nv::profile_hevc_e::main_10 }, - AV_HWDEVICE_TYPE_D3D11VA, - AV_PIX_FMT_D3D11, - AV_PIX_FMT_NV12, AV_PIX_FMT_NV12, - { - { {"forced-idr"s, 1} }, "hevc_nvenc"s - }, - { - { - { "forced-idr"s, 1}, - { "preset"s , (int)nv::preset_e::llhq }, - }, "h264_nvenc"s - }, - false, - - nv_d3d_img_to_frame, - nv_d3d_make_hwdevice_ctx -}; - -static encoder_t software { - { FF_PROFILE_H264_HIGH, FF_PROFILE_HEVC_MAIN, FF_PROFILE_HEVC_MAIN_10 }, - AV_HWDEVICE_TYPE_NONE, - AV_PIX_FMT_NONE, - AV_PIX_FMT_YUV420P, AV_PIX_FMT_YUV420P10, - { - // x265's Info SEI is so long that it causes the IDR picture data to be - // kicked to the 2nd packet in the frame, breaking Moonlight's parsing logic. - // It also looks like gop_size isn't passed on to x265, so we have to set - // 'keyint=-1' in the parameters ourselves. - { - { "x265-params"s, "info=0:keyint=-1"s }, - { "preset"s, &config::video.preset }, - { "tune"s, &config::video.tune } - }, "libx265"s - }, - { - { - { "preset"s, &config::video.preset }, - { "tune"s, &config::video.tune } - }, "libx264"s - }, - true, - - sw_img_to_frame, - nullptr -}; - -static std::vector encoders { - nvenc, software -}; - struct capture_ctx_t { img_event_t images; std::chrono::nanoseconds delay; @@ -233,6 +183,91 @@ struct capture_thread_ctx_t { util::sync_t> display_wp; }; +struct capture_synced_ctx_t { + encode_session_ctx_queue_t encode_session_ctx_queue; +}; + +int start_capture_sync(capture_synced_ctx_t &ctx); +void end_capture_sync(capture_synced_ctx_t &ctx); +int start_capture(capture_thread_ctx_t &ctx); +void end_capture(capture_thread_ctx_t &ctx); + +// Keep a reference counter to ensure the capture thread only runs when other threads have a reference to the capture thread +auto capture_thread = safe::make_shared(start_capture, end_capture); +auto capture_thread_sync = safe::make_shared(start_capture_sync, end_capture_sync); + +static encoder_t nvenc { + "nvenc"sv, + { (int)nv::profile_h264_e::high, (int)nv::profile_hevc_e::main, (int)nv::profile_hevc_e::main_10 }, + AV_HWDEVICE_TYPE_D3D11VA, + AV_PIX_FMT_D3D11, + AV_PIX_FMT_NV12, AV_PIX_FMT_NV12, + { + { + { "forced-idr"s, 1 }, + { "zerolatency"s, 1 }, + { "preset"s, &config::video.nv.preset }, + { "rc"s, &config::video.nv.rc } + }, + std::nullopt, std::nullopt, + "hevc_nvenc"s, + }, + { + { + { "forced-idr"s, 1 }, + { "zerolatency"s, 1 }, + { "preset"s, &config::video.nv.preset }, + { "rc"s, &config::video.nv.rc }, + { "coder"s, &config::video.nv.coder } + }, + std::nullopt, std::make_optional({"qp"s, &config::video.qp}), + "h264_nvenc"s + }, + false, + true, + + nv_d3d_img_to_frame, + nv_d3d_make_hwdevice_ctx +}; + +static encoder_t software { + "software"sv, + { FF_PROFILE_H264_HIGH, FF_PROFILE_HEVC_MAIN, FF_PROFILE_HEVC_MAIN_10 }, + AV_HWDEVICE_TYPE_NONE, + AV_PIX_FMT_NONE, + AV_PIX_FMT_YUV420P, AV_PIX_FMT_YUV420P10, + { + // x265's Info SEI is so long that it causes the IDR picture data to be + // kicked to the 2nd packet in the frame, breaking Moonlight's parsing logic. + // It also looks like gop_size isn't passed on to x265, so we have to set + // 'keyint=-1' in the parameters ourselves. + { + { "x265-params"s, "info=0:keyint=-1"s }, + { "preset"s, &config::video.sw.preset }, + { "tune"s, &config::video.sw.tune } + }, + std::make_optional("crf"s, &config::video.crf), std::make_optional("qp"s, &config::video.qp), + "libx265"s + }, + { + { + { "preset"s, &config::video.sw.preset }, + { "tune"s, &config::video.sw.tune } + }, + std::make_optional("crf"s, &config::video.crf), std::make_optional("qp"s, &config::video.qp), + "libx264"s + }, + true, + false, + + sw_img_to_frame, + nullptr +}; + +static std::vector encoders { + nvenc, software +}; + platf::dev_type_e map_dev_type(AVHWDeviceType type) { switch(type) { case AV_HWDEVICE_TYPE_D3D11VA: @@ -502,7 +537,15 @@ std::optional make_session(const encoder_t &encoder, const config_t bool hardware = encoder.dev_type != AV_HWDEVICE_TYPE_NONE; auto &video_format = config.videoFormat == 0 ? encoder.h264 : encoder.hevc; - assert(video_format[encoder_t::PASSED]); + if(!video_format[encoder_t::PASSED]) { + BOOST_LOG(error) << encoder.name << ": "sv << video_format.name << " mode not supported"sv; + return std::nullopt; + } + + if(config.dynamicRange && !video_format[encoder_t::DYNAMIC_RANGE]) { + BOOST_LOG(error) << video_format.name << ": dynamic range not supported"sv; + return std::nullopt; + } auto codec = avcodec_find_encoder_by_name(video_format.name.c_str()); if(!codec) { @@ -606,18 +649,24 @@ std::optional make_session(const encoder_t &encoder, const config_t // most efficient encode, but we may want to provide more slices than // requested to ensure we have enough parallelism for good performance. ctx->slices = std::max(config.slicesPerFrame, config::video.min_threads); - ctx->thread_type = FF_THREAD_SLICE; - ctx->thread_count = ctx->slices; } + ctx->thread_type = FF_THREAD_SLICE; + ctx->thread_count = ctx->slices; + AVDictionary *options {nullptr}; - for(auto &option : video_format.options) { + auto handle_option = [&options](const encoder_t::option_t &option) { std::visit(util::overloaded { [&](int v) { av_dict_set_int(&options, option.name.c_str(), v, 0); }, [&](int *v) { av_dict_set_int(&options, option.name.c_str(), *v, 0); }, + [&](std::optional *v) { if(*v) av_dict_set_int(&options, option.name.c_str(), **v, 0); }, [&](const std::string &v) { av_dict_set(&options, option.name.c_str(), v.c_str(), 0); }, - [&](std::string *v) { av_dict_set(&options, option.name.c_str(), v->c_str(), 0); } + [&](std::string *v) { if(!v->empty()) av_dict_set(&options, option.name.c_str(), v->c_str(), 0); } }, option.value); + }; + + for(auto &option : video_format.options) { + handle_option(option); } if(config.bitrate > 500) { @@ -627,11 +676,15 @@ std::optional make_session(const encoder_t &encoder, const config_t ctx->bit_rate = bitrate; ctx->rc_min_rate = bitrate; } - else if(config::video.crf != 0) { - av_dict_set_int(&options, "crf", config::video.crf, 0); + else if(video_format.crf && config::video.crf != 0) { + handle_option(*video_format.crf); + } + else if(video_format.qp) { + handle_option(*video_format.qp); } else { - av_dict_set_int(&options, "qp", config::video.qp, 0); + BOOST_LOG(error) << "Couldn't set video quality: encoder "sv << encoder.name << " doesn't support either crf or qp"sv; + return std::nullopt; } avcodec_open2(ctx.get(), codec, &options); @@ -694,8 +747,11 @@ void encode_run( session->frame->pict_type = AV_PICTURE_TYPE_I; auto event = idr_events->pop(); - TUPLE_2D_REF(_, end, *event); + if(!event) { + return; + } + auto end = event->second; frame_nr = end; key_frame_nr = end + config.framerate; } @@ -750,9 +806,8 @@ void encode_run( } if(encode(frame_nr++, session->ctx, session->frame, packets, channel_data)) { - BOOST_LOG(fatal) << "Could not encode video packet"sv; - log_flush(); - std::abort(); + BOOST_LOG(error) << "Could not encode video packet"sv; + return; } session->frame->pict_type = AV_PICTURE_TYPE_NONE; @@ -856,18 +911,12 @@ encode_e encode_run_sync(std::vector> &enc auto now = std::chrono::steady_clock::now(); next_frame = now + 1s; - {auto pos = std::begin(encode_sessions);while( pos != std::end(encode_sessions)) { + KITTY_WHILE_LOOP(auto pos = std::begin(encode_sessions), pos != std::end(encode_sessions), { auto ctx = pos->ctx; if(ctx->shutdown_event->peek()) { // Let waiting thread know it can delete shutdown_event ctx->join_event->raise(true); - - //FIXME: Causes segfault even if (pos + 1) != std::end() - // *pos = std::move(*(pos + 1)); - - {encode_session_t t { std::move(*pos) };} - //FIXME: encode_session_t = std::move(encode_session_t) <=> segfault pos = encode_sessions.erase(pos); encode_session_ctxs.erase(std::find_if(std::begin(encode_session_ctxs), std::end(encode_session_ctxs), [&ctx_p=ctx](auto &ctx) { return ctx.get() == ctx_p; @@ -918,15 +967,16 @@ encode_e encode_run_sync(std::vector> &enc } if(encode(ctx->frame_nr++, pos->session.ctx, pos->session.frame, ctx->packets, ctx->channel_data)) { - BOOST_LOG(fatal) << "Could not encode video packet"sv; - log_flush(); - std::abort(); + BOOST_LOG(error) << "Could not encode video packet"sv; + ctx->shutdown_event->raise(true); + + continue; } pos->session.frame->pict_type = AV_PICTURE_TYPE_NONE; ++pos; - }} + }) img_tmp = nullptr; } @@ -964,28 +1014,11 @@ int start_capture_sync(capture_synced_ctx_t &ctx) { void end_capture_sync(capture_synced_ctx_t &ctx) {} -void capture( - safe::signal_t *shutdown_event, - packet_queue_t packets, - idr_event_t idr_events, - config_t config, - void *channel_data) { - - safe::signal_t join_event; - auto ref = capture_thread_sync.ref(); - ref->encode_session_ctx_queue.raise(encode_session_ctx_t { - shutdown_event, &join_event, packets, idr_events, config, 1, 1, channel_data - }); - - // Wait for join signal - join_event.view(); -} - void capture_async( safe::signal_t *shutdown_event, - packet_queue_t packets, - idr_event_t idr_events, - config_t config, + packet_queue_t &packets, + idr_event_t &idr_events, + config_t &config, void *channel_data) { auto images = std::make_shared(); @@ -994,8 +1027,6 @@ void capture_async( shutdown_event->raise(true); }); - // Keep a reference counter to ensure the Fcapture thread only runs when other threads have a reference to the capture thread - static auto capture_thread = safe::make_shared(start_capture, end_capture); auto ref = capture_thread.ref(); if(!ref) { return; @@ -1040,6 +1071,28 @@ void capture_async( } } +void capture( + safe::signal_t *shutdown_event, + packet_queue_t packets, + idr_event_t idr_events, + config_t config, + void *channel_data) { + + if(encoders.front().system_memory) { + capture_async(shutdown_event, packets, idr_events, config, channel_data); + } + else { + safe::signal_t join_event; + auto ref = capture_thread_sync.ref(); + ref->encode_session_ctx_queue.raise(encode_session_ctx_t { + shutdown_event, &join_event, packets, idr_events, config, 1, 1, channel_data + }); + + // Wait for join signal + join_event.view(); + } +} + bool validate_config(std::shared_ptr &disp, const encoder_t &encoder, const config_t &config) { reset_display(disp, encoder.dev_type); if(!disp) { @@ -1099,31 +1152,15 @@ bool validate_config(std::shared_ptr &disp, const encoder_t &e bool validate_encoder(encoder_t &encoder) { std::shared_ptr disp; + auto force_hevc = config::video.hevc_mode >= 2; + auto test_hevc = force_hevc || (config::video.hevc_mode == 0 && encoder.hevc_mode); + encoder.h264.capabilities.set(); encoder.hevc.capabilities.set(); // First, test encoder viability - config_t config_max_ref_frames { - 1920, 1080, - 60, - 1000, - 1, - 1, - 1, - 0, - 0 - }; - - config_t config_autoselect { - 1920, 1080, - 60, - 1000, - 1, - 0, - 1, - 0, - 0 - }; + config_t config_max_ref_frames { 1920, 1080, 60, 1000, 1, 1, 1, 0, 0 }; + config_t config_autoselect { 1920, 1080, 60, 1000, 1, 0, 1, 0, 0 }; auto max_ref_frames_h264 = validate_config(disp, encoder, config_max_ref_frames); auto autoselect_h264 = validate_config(disp, encoder, config_autoselect); @@ -1132,20 +1169,30 @@ bool validate_encoder(encoder_t &encoder) { return false; } - config_max_ref_frames.videoFormat = 1; - config_autoselect.videoFormat = 1; - - auto max_ref_frames_hevc = validate_config(disp, encoder, config_max_ref_frames); - auto autoselect_hevc = validate_config(disp, encoder, config_autoselect); - encoder.h264[encoder_t::REF_FRAMES_RESTRICT] = max_ref_frames_h264; encoder.h264[encoder_t::REF_FRAMES_AUTOSELECT] = autoselect_h264; encoder.h264[encoder_t::PASSED] = true; - encoder.hevc[encoder_t::REF_FRAMES_RESTRICT] = max_ref_frames_hevc; - encoder.hevc[encoder_t::REF_FRAMES_AUTOSELECT] = autoselect_hevc; - encoder.hevc[encoder_t::PASSED] = max_ref_frames_hevc || autoselect_hevc; - std::vector> configs; + if(test_hevc) { + config_max_ref_frames.videoFormat = 1; + config_autoselect.videoFormat = 1; + + auto max_ref_frames_hevc = validate_config(disp, encoder, config_max_ref_frames); + auto autoselect_hevc = validate_config(disp, encoder, config_autoselect); + + // If HEVC must be supported, but it is not supported + if(force_hevc && !max_ref_frames_hevc && !autoselect_hevc) { + return false; + } + + encoder.hevc[encoder_t::REF_FRAMES_RESTRICT] = max_ref_frames_hevc; + encoder.hevc[encoder_t::REF_FRAMES_AUTOSELECT] = autoselect_hevc; + } + encoder.hevc[encoder_t::PASSED] = test_hevc; + + std::vector> configs { + { encoder_t::DYNAMIC_RANGE, { 1920, 1080, 60, 1000, 1, 0, 1, 1, 1 } } + }; for(auto &[flag, config] : configs) { auto h264 = config; auto hevc = config; @@ -1154,26 +1201,53 @@ bool validate_encoder(encoder_t &encoder) { hevc.videoFormat = 1; encoder.h264[flag] = validate_config(disp, encoder, h264); - encoder.hevc[flag] = validate_config(disp, encoder, hevc); + if(test_hevc && encoder.hevc[encoder_t::PASSED]) { + encoder.hevc[flag] = validate_config(disp, encoder, hevc); + } } - + return true; } -void init() { +int init() { KITTY_WHILE_LOOP(auto pos = std::begin(encoders), pos != std::end(encoders), { - if(!validate_encoder(*pos)) { + if( + (!config::video.encoder.empty() && pos->name != config::video.encoder) || + !validate_encoder(*pos) || + (config::video.hevc_mode == 3 && !pos->hevc[encoder_t::DYNAMIC_RANGE]) + ) { pos = encoders.erase(pos); continue; } - ++pos; + break; }) - for(auto &encoder : encoders) { - BOOST_LOG(info) << "Found encoder ["sv << encoder.h264.name << ", "sv << encoder.hevc.name << ']'; + if(encoders.empty()) { + if(config::video.encoder.empty()) { + BOOST_LOG(fatal) << "Couldn't find any encoder"sv; + } + else { + BOOST_LOG(fatal) << "Couldn't find any encoder matching ["sv << config::video.encoder << ']'; + } + + return -1; } + + auto &encoder = encoders.front(); + if(encoder.hevc[encoder_t::PASSED]) { + BOOST_LOG(info) << "Found encoder "sv << encoder.name << ": ["sv << encoder.h264.name << ", "sv << encoder.hevc.name << ']'; + } + else { + BOOST_LOG(info) << "Found encoder "sv << encoder.name << ": ["sv << encoder.h264.name << ']'; + } + + if(config::video.hevc_mode == 0) { + config::video.hevc_mode = encoder.hevc[encoder_t::PASSED] ? (encoder.hevc[encoder_t::DYNAMIC_RANGE] ? 3 : 2) : 1; + } + + return 0; } void sw_img_to_frame(sws_t &sws, const platf::img_t &img, frame_t &frame) { @@ -1211,11 +1285,6 @@ void nv_d3d_img_to_frame(sws_t &sws, const platf::img_t &img, frame_t &frame) { frame->width = img.width; } -void nvenc_lock(void *lock_p) { -} -void nvenc_unlock(void *lock_p) { -} - util::Either nv_d3d_make_hwdevice_ctx(platf::hwdevice_ctx_t *hwdevice_ctx) { buffer_t ctx_buf { av_hwdevice_ctx_alloc(AV_HWDEVICE_TYPE_D3D11VA) }; auto ctx = (AVD3D11VADeviceContext*)((AVHWDeviceContext*)ctx_buf->data)->hwctx; diff --git a/sunshine/video.h b/sunshine/video.h index fc3c1426..e4560e05 100644 --- a/sunshine/video.h +++ b/sunshine/video.h @@ -57,7 +57,7 @@ void capture( config_t config, void *channel_data); -void init(); +int init(); } #endif //SUNSHINE_VIDEO_H