diff --git a/assets/sunshine.conf b/assets/sunshine.conf index f0eb72c6..14e3dc58 100644 --- a/assets/sunshine.conf +++ b/assets/sunshine.conf @@ -250,6 +250,6 @@ # Here, you change the default state of any flag # # To set the initial state of flags -0 and -1 to on, set the following flags: -# flags = 01 +# flags = 012 # # See: sunshine --help for all options under the header: flags diff --git a/sunshine/cbs.cpp b/sunshine/cbs.cpp index c4f7b681..e4e24571 100644 --- a/sunshine/cbs.cpp +++ b/sunshine/cbs.cpp @@ -1,10 +1,12 @@ extern "C" { #include #include -#include +#include #include +#include } +#include "cbs.h" #include "main.h" #include "utility.h" @@ -46,19 +48,16 @@ public: } }; -util::buffer_t write(std::uint8_t nal, void *uh, AVCodecID codec_id) { +util::buffer_t write(const cbs::ctx_t &cbs_ctx, std::uint8_t nal, void *uh, AVCodecID codec_id) { cbs::frag_t frag; auto err = ff_cbs_insert_unit_content(&frag, -1, nal, uh, nullptr); if(err < 0) { char err_str[AV_ERROR_MAX_STRING_SIZE] { 0 }; - BOOST_LOG(error) << "Could not NAL unit SPS: "sv << av_make_error_string(err_str, AV_ERROR_MAX_STRING_SIZE, err); + BOOST_LOG(error) << "Could not insert NAL unit SPS: "sv << av_make_error_string(err_str, AV_ERROR_MAX_STRING_SIZE, err); return {}; } - cbs::ctx_t cbs_ctx; - ff_cbs_init(&cbs_ctx, codec_id, nullptr); - err = ff_cbs_write_fragment_data(cbs_ctx.get(), &frag); if(err < 0) { char err_str[AV_ERROR_MAX_STRING_SIZE] { 0 }; @@ -74,6 +73,13 @@ util::buffer_t write(std::uint8_t nal, void *uh, AVCodecID codec_i return data; } +util::buffer_t write(std::uint8_t nal, void *uh, AVCodecID codec_id) { + cbs::ctx_t cbs_ctx; + ff_cbs_init(&cbs_ctx, codec_id, nullptr); + + return write(cbs_ctx, nal, uh, codec_id); +} + util::buffer_t make_sps_h264(const AVCodecContext *ctx) { H264RawSPS sps {}; @@ -162,9 +168,83 @@ util::buffer_t make_sps_h264(const AVCodecContext *ctx) { return write(sps.nal_unit_header.nal_unit_type, (void *)&sps.nal_unit_header, AV_CODEC_ID_H264); } -util::buffer_t read_sps(const AVPacket *packet, int codec_id) { +hevc_t make_sps_hevc(const AVCodecContext *avctx, const AVPacket *packet) { cbs::ctx_t ctx; - if(ff_cbs_init(&ctx, (AVCodecID)codec_id, nullptr)) { + if(ff_cbs_init(&ctx, AV_CODEC_ID_H265, nullptr)) { + return {}; + } + + cbs::frag_t frag; + + int err = ff_cbs_read_packet(ctx.get(), &frag, packet); + if(err < 0) { + char err_str[AV_ERROR_MAX_STRING_SIZE] { 0 }; + BOOST_LOG(error) << "Couldn't read packet: "sv << av_make_error_string(err_str, AV_ERROR_MAX_STRING_SIZE, err); + + return {}; + } + + + auto vps_p = ((CodedBitstreamH265Context *)ctx->priv_data)->active_vps; + auto sps_p = ((CodedBitstreamH265Context *)ctx->priv_data)->active_sps; + + H265RawSPS sps { *sps_p }; + H265RawVPS vps { *vps_p }; + + vps.profile_tier_level.general_profile_compatibility_flag[4] = 1; + sps.profile_tier_level.general_profile_compatibility_flag[4] = 1; + + auto &vui = sps.vui; + std::memset(&vui, 0, sizeof(vui)); + + sps.vui_parameters_present_flag = 1; + + // skip sample aspect ratio + + vui.video_format = 5; + vui.colour_description_present_flag = 1; + vui.video_signal_type_present_flag = 1; + vui.video_full_range_flag = avctx->color_range == AVCOL_RANGE_JPEG; + vui.colour_primaries = avctx->color_primaries; + vui.transfer_characteristics = avctx->color_trc; + vui.matrix_coefficients = avctx->colorspace; + + + vui.vui_timing_info_present_flag = vps.vps_timing_info_present_flag; + vui.vui_num_units_in_tick = vps.vps_num_units_in_tick; + vui.vui_time_scale = vps.vps_time_scale; + vui.vui_poc_proportional_to_timing_flag = vps.vps_poc_proportional_to_timing_flag; + vui.vui_num_ticks_poc_diff_one_minus1 = vps.vps_num_ticks_poc_diff_one_minus1; + vui.vui_hrd_parameters_present_flag = 0; + + vui.bitstream_restriction_flag = 1; + vui.motion_vectors_over_pic_boundaries_flag = 1; + vui.restricted_ref_pic_lists_flag = 1; + vui.max_bytes_per_pic_denom = 0; + vui.max_bits_per_min_cu_denom = 0; + vui.log2_max_mv_length_horizontal = 15; + vui.log2_max_mv_length_vertical = 15; + + cbs::ctx_t write_ctx; + ff_cbs_init(&write_ctx, AV_CODEC_ID_H265, nullptr); + + + return hevc_t { + nal_t { + write(write_ctx, vps.nal_unit_header.nal_unit_type, (void *)&vps.nal_unit_header, AV_CODEC_ID_H265), + write(ctx, vps_p->nal_unit_header.nal_unit_type, (void *)&vps_p->nal_unit_header, AV_CODEC_ID_H265), + }, + + nal_t { + write(write_ctx, sps.nal_unit_header.nal_unit_type, (void *)&sps.nal_unit_header, AV_CODEC_ID_H265), + write(ctx, sps_p->nal_unit_header.nal_unit_type, (void *)&sps_p->nal_unit_header, AV_CODEC_ID_H265), + }, + }; +} + +util::buffer_t read_sps_h264(const AVPacket *packet) { + cbs::ctx_t ctx; + if(ff_cbs_init(&ctx, AV_CODEC_ID_H264, nullptr)) { return {}; } @@ -178,24 +258,15 @@ util::buffer_t read_sps(const AVPacket *packet, int codec_id) { return {}; } - if(codec_id == AV_CODEC_ID_H264) { - auto h264 = (H264RawNALUnitHeader *)((CodedBitstreamH264Context *)ctx->priv_data)->active_sps; - return write(h264->nal_unit_type, (void *)h264, AV_CODEC_ID_H264); - } - - auto hevc = (H264RawNALUnitHeader *)((CodedBitstreamH265Context *)ctx->priv_data)->active_sps; - return write(hevc->nal_unit_type, (void *)hevc, AV_CODEC_ID_H265); + auto h264 = (H264RawNALUnitHeader *)((CodedBitstreamH264Context *)ctx->priv_data)->active_sps; + return write(h264->nal_unit_type, (void *)h264, AV_CODEC_ID_H264); } -util::buffer_t make_sps(const AVCodecContext *ctx, int format) { - switch(format) { - case 0: - return make_sps_h264(ctx); - } - - BOOST_LOG(warning) << "make_sps: video format ["sv << format << "] not supported"sv; - - return {}; +h264_t make_sps_h264(const AVCodecContext *ctx, const AVPacket *packet) { + return h264_t { + make_sps_h264(ctx), + read_sps_h264(packet), + }; } bool validate_sps(const AVPacket *packet, int codec_id) { @@ -206,7 +277,7 @@ bool validate_sps(const AVPacket *packet, int codec_id) { cbs::frag_t frag; - int err = ff_cbs_read_packet(ctx.get(), &frag, &*packet); + int err = ff_cbs_read_packet(ctx.get(), &frag, packet); if(err < 0) { char err_str[AV_ERROR_MAX_STRING_SIZE] { 0 }; BOOST_LOG(error) << "Couldn't read packet: "sv << av_make_error_string(err_str, AV_ERROR_MAX_STRING_SIZE, err); diff --git a/sunshine/cbs.h b/sunshine/cbs.h index 721b24e0..dc772dd9 100644 --- a/sunshine/cbs.h +++ b/sunshine/cbs.h @@ -5,10 +5,25 @@ struct AVPacket; struct AVCodecContext; + namespace cbs { -util::buffer_t read_sps(const AVPacket *packet, int codec_id); -util::buffer_t make_sps(const AVCodecContext *ctx, int video_format); +struct nal_t { + util::buffer_t _new; + util::buffer_t old; +}; + +struct hevc_t { + nal_t vps; + nal_t sps; +}; + +struct h264_t { + nal_t sps; +}; + +hevc_t make_sps_hevc(const AVCodecContext *ctx, const AVPacket *packet); +h264_t make_sps_h264(const AVCodecContext *ctx, const AVPacket *packet); /** * Check if SPS->VUI is present diff --git a/sunshine/config.cpp b/sunshine/config.cpp index d0f5c07c..28bdc2fa 100644 --- a/sunshine/config.cpp +++ b/sunshine/config.cpp @@ -549,6 +549,9 @@ int apply_flags(const char *line) { case '1': config::sunshine.flags[config::flag::FRESH_STATE].flip(); break; + case '2': + config::sunshine.flags[config::flag::FORCE_VIDEO_HEADER_REPLACE].flip(); + break; case 'p': config::sunshine.flags[config::flag::CONST_PIN].flip(); break; @@ -721,6 +724,13 @@ int parse(int argc, char *argv[]) { return -1; } + TUPLE_2D_REF(name, val, *var); + + auto it = cmd_vars.find(name); + if(it != std::end(cmd_vars)) { + cmd_vars.erase(it); + } + cmd_vars.emplace(std::move(*var)); } } diff --git a/sunshine/config.h b/sunshine/config.h index a905a0fd..221df6f9 100644 --- a/sunshine/config.h +++ b/sunshine/config.h @@ -80,9 +80,10 @@ struct input_t { namespace flag { enum flag_e : std::size_t { - PIN_STDIN = 0, // Read PIN from stdin instead of http - FRESH_STATE, // Do not load or save state - CONST_PIN, // Use "universal" pin + PIN_STDIN = 0, // Read PIN from stdin instead of http + FRESH_STATE, // Do not load or save state + FORCE_VIDEO_HEADER_REPLACE, // force replacing headers inside video data + CONST_PIN, // Use "universal" pin FLAG_SIZE }; } diff --git a/sunshine/main.cpp b/sunshine/main.cpp index daf47cdc..293c5b11 100644 --- a/sunshine/main.cpp +++ b/sunshine/main.cpp @@ -63,7 +63,8 @@ void print_help(const char *name) { << " flags"sv << std::endl << " -0 | Read PIN from stdin"sv << std::endl << " -1 | Do not load previously saved state and do retain any state after shutdown"sv << std::endl - << " | Effectively starting as if for the first time without overwriting any pairings with your devices"sv << std::endl; + << " | Effectively starting as if for the first time without overwriting any pairings with your devices"sv << std::endl + << " -2 | Force replacement of headers in video stream" << std::endl; } namespace help { @@ -200,6 +201,11 @@ int main(int argc, char *argv[]) { return 3; } + //FIXME: Temporary workaround: Simple-Web_server needs to be updated or replaced + if(shutdown_event->peek()) { + return 0; + } + task_pool.start(1); std::thread httpThread { nvhttp::start, shutdown_event }; diff --git a/sunshine/stream.cpp b/sunshine/stream.cpp index b19b1fbe..b025e7e9 100644 --- a/sunshine/stream.cpp +++ b/sunshine/stream.cpp @@ -617,28 +617,14 @@ void videoBroadcastThread(safe::signal_t *shutdown_event, udp::socket &sock, vid payload = { (char *)payload_new.data(), payload_new.size() }; - // make sure moonlight recognizes the nalu code for IDR frames if(packet->flags & AV_PKT_FLAG_KEY) { - BOOST_LOG(debug) << "Sending IDR frame"sv; - // TODO: Not all encoders encode their IDR frames with the 4 byte NALU prefix - std::string_view frame_old = "\000\000\001e"sv; - std::string_view frame_new = "\000\000\000\001e"sv; - if(session->config.monitor.videoFormat != 0) { - frame_old = "\000\000\001("sv; - frame_new = "\000\000\000\001("sv; + for(auto &replacement : *packet->replacements) { + auto frame_old = replacement.old; + auto frame_new = replacement._new; + + payload_new = replace(payload, frame_old, frame_new); + payload = { (char *)payload_new.data(), payload_new.size() }; } - - payload_new = replace(payload, frame_old, frame_new); - payload = { (char *)payload_new.data(), payload_new.size() }; - } - - if(packet->flags & AV_PKT_FLAG_KEY && packet->sps.old.size()) { - BOOST_LOG(debug) << "Replacing SPS header"sv; - std::string_view frame_old = packet->sps.old; - std::string_view frame_new = packet->sps.replacement; - - payload_new = replace(payload, frame_old, frame_new); - payload = { (char *)payload_new.data(), payload_new.size() }; } // insert packet headers diff --git a/sunshine/video.cpp b/sunshine/video.cpp index e418a44a..6fc672cc 100644 --- a/sunshine/video.cpp +++ b/sunshine/video.cpp @@ -24,8 +24,11 @@ extern "C" { } #endif -namespace video { using namespace std::literals; +namespace video { + +constexpr auto hevc_nalu = "\000\000\000\001("sv; +constexpr auto h264_nalu = "\000\000\000\001e"sv; void free_ctx(AVCodecContext *ctx) { avcodec_free_context(&ctx); @@ -245,6 +248,7 @@ struct encoder_t { SLICE, // Allow frame to be partitioned into multiple slices DYNAMIC_RANGE, // hdr VUI_PARAMETERS, // AMD encoder with VAAPI doesn't add VUI parameters to SPS + NALU_PREFIX_5b, // libx264/libx265 have a 3-byte nalu prefix instead of 4-byte nalu prefix MAX_FLAGS }; @@ -259,6 +263,7 @@ struct encoder_t { _CONVERT(SLICE); _CONVERT(DYNAMIC_RANGE); _CONVERT(VUI_PARAMETERS); + _CONVERT(NALU_PREFIX_5b); _CONVERT(MAX_FLAGS); } #undef _CONVERT @@ -312,16 +317,20 @@ struct encoder_t { class session_t { public: session_t() = default; - session_t(ctx_t &&ctx, util::wrap_ptr &&device, util::buffer_t &&sps) : ctx { std::move(ctx) }, device { std::move(device) }, sps { std::move(sps) } {} + session_t(ctx_t &&ctx, util::wrap_ptr &&device, int inject) : ctx { std::move(ctx) }, device { std::move(device) }, inject { inject } {} - session_t(session_t &&other) noexcept : ctx { std::move(other.ctx) }, device { std::move(other.device) }, sps { std::move(sps) }, sps_old { std::move(sps_old) } {} + session_t(session_t &&other) noexcept = default; // Ensure objects are destroyed in the correct order session_t &operator=(session_t &&other) { - device = std::move(other.device); - ctx = std::move(other.ctx); - sps = std::move(other.sps); - sps_old = std::move(other.sps_old); + device = std::move(other.device); + ctx = std::move(other.ctx); + replacements = std::move(other.replacements); + sps = std::move(other.sps); + vps = std::move(other.vps); + pps = std::move(other.pps); + + inject = other.inject; return *this; } @@ -329,8 +338,14 @@ public: ctx_t ctx; util::wrap_ptr device; - util::buffer_t sps; - util::buffer_t sps_old; + std::vector replacements; + + cbs::nal_t sps; + cbs::nal_t vps; + cbs::nal_t pps; + + // inject sps/vps data into idr pictures + int inject; }; struct sync_session_ctx_t { @@ -684,8 +699,9 @@ int encode(int64_t frame_nr, session_t &session, frame_t::pointer frame, packet_ auto &ctx = session.ctx; - auto &sps = session.sps; - auto &sps_old = session.sps_old; + auto &sps = session.sps; + auto &vps = session.vps; + auto &pps = session.pps; /* send the frame to the encoder */ auto ret = avcodec_send_frame(ctx.get(), frame); @@ -707,12 +723,33 @@ int encode(int64_t frame_nr, session_t &session, frame_t::pointer frame, packet_ return ret; } - if(sps.size() && !sps_old.size()) { - sps_old = cbs::read_sps(packet.get(), AV_CODEC_ID_H264); + if(session.inject) { + if(session.inject == 1) { + auto h264 = cbs::make_sps_h264(ctx.get(), packet.get()); + + sps = std::move(h264.sps); + } + else { + auto hevc = cbs::make_sps_hevc(ctx.get(), packet.get()); + + sps = std::move(hevc.sps); + vps = std::move(hevc.vps); + + session.replacements.emplace_back( + std::string_view((char *)std::begin(vps.old), vps.old.size()), + std::string_view((char *)std::begin(vps._new), vps._new.size())); + } + + session.inject = 0; + + + session.replacements.emplace_back( + std::string_view((char *)std::begin(sps.old), sps.old.size()), + std::string_view((char *)std::begin(sps._new), sps._new.size())); } - packet->sps.old = std::string_view((char *)std::begin(sps_old), sps_old.size()); - packet->sps.replacement = std::string_view((char *)std::begin(sps), sps.size()); - packet->channel_data = channel_data; + + packet->replacements = &session.replacements; + packet->channel_data = channel_data; packets->raise(std::move(packet)); } @@ -819,6 +856,9 @@ std::optional make_session(const encoder_t &encoder, const config_t & sw_fmt = encoder.dynamic_pix_fmt; } + // Used by cbs::make_sps_hevc + ctx->sw_pix_fmt = sw_fmt; + buffer_t hwdevice_ctx; if(hardware) { ctx->pix_fmt = encoder.dev_pix_fmt; @@ -926,17 +966,21 @@ std::optional make_session(const encoder_t &encoder, const config_t & device->set_colorspace(sws_color_space, ctx->color_range); - if(video_format[encoder_t::VUI_PARAMETERS]) { - return std::make_optional( - std::move(ctx), - std::move(device), - util::buffer_t {}); - } - - return std::make_optional( + session_t session { std::move(ctx), std::move(device), - cbs::make_sps(ctx.get(), config.videoFormat)); + + // 0 ==> don't inject, 1 ==> inject for h264, 2 ==> inject for hevc + (1 - (int)video_format[encoder_t::VUI_PARAMETERS]) * (1 + config.videoFormat), + }; + + if(!video_format[encoder_t::NALU_PREFIX_5b]) { + auto nalu_prefix = config.videoFormat ? hevc_nalu : h264_nalu; + + session.replacements.emplace_back(nalu_prefix.substr(1), nalu_prefix); + } + + return std::make_optional(std::move(session)); } void encode_run( @@ -991,7 +1035,7 @@ void encode_run( next_frame += delay; // When Moonlight request an IDR frame, send frames even if there is no new captured frame - if(frame_nr > (key_frame_nr + config.framerate) || images->peek()) { + if(frame_nr > key_frame_nr || images->peek()) { if(auto img = images->pop(delay)) { session->device->convert(*img); } @@ -1311,29 +1355,34 @@ void capture( } } +enum validate_flag_e { + VUI_PARAMS = 0x01, + NALU_PREFIX_5b = 0x02, +}; + int validate_config(std::shared_ptr &disp, const encoder_t &encoder, const config_t &config) { reset_display(disp, encoder.dev_type); if(!disp) { - return 0; + return -1; } auto pix_fmt = config.dynamicRange == 0 ? map_pix_fmt(encoder.static_pix_fmt) : map_pix_fmt(encoder.dynamic_pix_fmt); auto hwdevice = disp->make_hwdevice(pix_fmt); if(!hwdevice) { - return 0; + return -1; } auto session = make_session(encoder, config, disp->width, disp->height, hwdevice.get()); if(!session) { - return 0; + return -1; } auto img = disp->alloc_img(); if(disp->dummy_img(img.get())) { - return 0; + return -1; } if(session->device->convert(*img)) { - return 0; + return -1; } auto frame = session->device->frame; @@ -1343,7 +1392,7 @@ int validate_config(std::shared_ptr &disp, const encoder_t &en auto packets = std::make_shared(30); while(!packets->peek()) { if(encode(1, *session, frame, packets, nullptr)) { - return false; + return -1; } } @@ -1351,14 +1400,21 @@ int validate_config(std::shared_ptr &disp, const encoder_t &en if(!(packet->flags & AV_PKT_FLAG_KEY)) { BOOST_LOG(error) << "First packet type is not an IDR frame"sv; - return 0; + return -1; } + int flag = 0; if(cbs::validate_sps(&*packet, config.videoFormat ? AV_CODEC_ID_H265 : AV_CODEC_ID_H264)) { - return 1; + flag |= VUI_PARAMS; } - return -1; + auto nalu_prefix = config.videoFormat ? hevc_nalu : h264_nalu; + std::string_view payload { (char *)packet->data, (std::size_t)packet->size }; + if(std::search(std::begin(payload), std::end(payload), std::begin(nalu_prefix), std::end(nalu_prefix)) != std::end(payload)) { + flag |= NALU_PREFIX_5b; + } + + return flag; } bool validate_encoder(encoder_t &encoder) { @@ -1384,16 +1440,21 @@ bool validate_encoder(encoder_t &encoder) { auto max_ref_frames_h264 = validate_config(disp, encoder, config_max_ref_frames); auto autoselect_h264 = validate_config(disp, encoder, config_autoselect); - if(!max_ref_frames_h264 && !autoselect_h264) { + if(max_ref_frames_h264 < 0 && autoselect_h264 < 0) { return false; } - if(max_ref_frames_h264 < 0 || autoselect_h264 < 0) { - encoder.h264[encoder_t::VUI_PARAMETERS] = false; + std::vector> packet_deficiencies { + { VUI_PARAMS, encoder_t::VUI_PARAMETERS }, + { NALU_PREFIX_5b, encoder_t::NALU_PREFIX_5b }, + }; + + for(auto [validate_flag, encoder_flag] : packet_deficiencies) { + encoder.h264[encoder_flag] = (max_ref_frames_h264 & validate_flag && autoselect_h264 & validate_flag); } - encoder.h264[encoder_t::REF_FRAMES_RESTRICT] = max_ref_frames_h264; - encoder.h264[encoder_t::REF_FRAMES_AUTOSELECT] = autoselect_h264; + encoder.h264[encoder_t::REF_FRAMES_RESTRICT] = max_ref_frames_h264 >= 0; + encoder.h264[encoder_t::REF_FRAMES_AUTOSELECT] = autoselect_h264 >= 0; encoder.h264[encoder_t::PASSED] = true; encoder.h264[encoder_t::SLICE] = validate_config(disp, encoder, config_max_ref_frames); @@ -1405,18 +1466,18 @@ bool validate_encoder(encoder_t &encoder) { 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) { + if(force_hevc && max_ref_frames_hevc < 0 && autoselect_hevc < 0) { return false; } - if(max_ref_frames_h264 < 0 || autoselect_h264 < 0) { - encoder.hevc[encoder_t::VUI_PARAMETERS] = false; + for(auto [validate_flag, encoder_flag] : packet_deficiencies) { + encoder.hevc[encoder_flag] = (max_ref_frames_hevc & validate_flag && autoselect_hevc & validate_flag); } - encoder.hevc[encoder_t::REF_FRAMES_RESTRICT] = max_ref_frames_hevc; - encoder.hevc[encoder_t::REF_FRAMES_AUTOSELECT] = autoselect_hevc; + encoder.hevc[encoder_t::REF_FRAMES_RESTRICT] = max_ref_frames_hevc >= 0; + encoder.hevc[encoder_t::REF_FRAMES_AUTOSELECT] = autoselect_hevc >= 0; - encoder.hevc[encoder_t::PASSED] = max_ref_frames_hevc || autoselect_hevc; + encoder.hevc[encoder_t::PASSED] = max_ref_frames_hevc >= 0 || autoselect_hevc >= 0; } std::vector> configs { @@ -1430,12 +1491,15 @@ bool validate_encoder(encoder_t &encoder) { h264.videoFormat = 0; hevc.videoFormat = 1; - encoder.h264[flag] = validate_config(disp, encoder, h264); + encoder.h264[flag] = validate_config(disp, encoder, h264) >= 0; if(encoder.hevc[encoder_t::PASSED]) { - encoder.hevc[flag] = validate_config(disp, encoder, hevc); + encoder.hevc[flag] = validate_config(disp, encoder, hevc) >= 0; } } + encoder.h264[encoder_t::VUI_PARAMETERS] = encoder.h264[encoder_t::VUI_PARAMETERS] && !config::sunshine.flags[config::flag::FORCE_VIDEO_HEADER_REPLACE]; + encoder.hevc[encoder_t::VUI_PARAMETERS] = encoder.hevc[encoder_t::VUI_PARAMETERS] && !config::sunshine.flags[config::flag::FORCE_VIDEO_HEADER_REPLACE]; + if(!encoder.h264[encoder_t::VUI_PARAMETERS]) { BOOST_LOG(warning) << encoder.name << ": h264 missing sps->vui parameters"sv; } @@ -1443,6 +1507,13 @@ bool validate_encoder(encoder_t &encoder) { BOOST_LOG(warning) << encoder.name << ": hevc missing sps->vui parameters"sv; } + if(!encoder.h264[encoder_t::NALU_PREFIX_5b]) { + BOOST_LOG(warning) << encoder.name << ": h264: replacing nalu prefix data"sv; + } + if(encoder.hevc[encoder_t::PASSED] && !encoder.hevc[encoder_t::NALU_PREFIX_5b]) { + BOOST_LOG(warning) << encoder.name << ": hevc: replacing nalu prefix data"sv; + } + fg.disable(); return true; } diff --git a/sunshine/video.h b/sunshine/video.h index 35371e00..e6b7dd21 100644 --- a/sunshine/video.h +++ b/sunshine/video.h @@ -42,10 +42,16 @@ struct packet_raw_t : public AVPacket { av_packet_unref(this); } - struct { + struct replace_t { std::string_view old; - std::string_view replacement; - } sps; + std::string_view _new; + + KITTY_DEFAULT_CONSTR(replace_t) + + replace_t(std::string_view old, std::string_view _new) noexcept : old { std::move(old) }, _new { std::move(_new) } {} + }; + + std::vector *replacements; void *channel_data; }; diff --git a/third-party/cbs/CMakeLists.txt b/third-party/cbs/CMakeLists.txt index 4266f17b..b30a8031 100644 --- a/third-party/cbs/CMakeLists.txt +++ b/third-party/cbs/CMakeLists.txt @@ -18,7 +18,7 @@ include/cbs/h2645_parse.h include/cbs/h264.h include/cbs/hevc.h include/cbs/sei.h -include/cbs/h264_levels.h +include/cbs/video_levels.h cbs.c cbs_h2645.c @@ -28,7 +28,7 @@ cbs_mpeg2.c cbs_jpeg.c cbs_sei.c h2645_parse.c -h264_levels.c +video_levels.c bytestream.h cbs_internal.h diff --git a/third-party/cbs/h264_levels.c b/third-party/cbs/h264_levels.c deleted file mode 100644 index 043f084c..00000000 --- a/third-party/cbs/h264_levels.c +++ /dev/null @@ -1,121 +0,0 @@ -/* - * This file is part of FFmpeg. - * - * FFmpeg is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2.1 of the License, or (at your option) any later version. - * - * FFmpeg is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with FFmpeg; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - */ - -#include - -#include "include/cbs/h264_levels.h" - -// H.264 table A-1. -static const H264LevelDescriptor h264_levels[] = { - // Name MaxMBPS MaxBR MinCR - // | level_idc | MaxFS | MaxCPB | MaxMvsPer2Mb - // | | cs3f | | MaxDpbMbs | | MaxVmvR | | - { "1", 10, 0, 1485, 99, 396, 64, 175, 64, 2, 0 }, - { "1b", 11, 1, 1485, 99, 396, 128, 350, 64, 2, 0 }, - { "1b", 9, 0, 1485, 99, 396, 128, 350, 64, 2, 0 }, - { "1.1", 11, 0, 3000, 396, 900, 192, 500, 128, 2, 0 }, - { "1.2", 12, 0, 6000, 396, 2376, 384, 1000, 128, 2, 0 }, - { "1.3", 13, 0, 11880, 396, 2376, 768, 2000, 128, 2, 0 }, - { "2", 20, 0, 11880, 396, 2376, 2000, 2000, 128, 2, 0 }, - { "2.1", 21, 0, 19800, 792, 4752, 4000, 4000, 256, 2, 0 }, - { "2.2", 22, 0, 20250, 1620, 8100, 4000, 4000, 256, 2, 0 }, - { "3", 30, 0, 40500, 1620, 8100, 10000, 10000, 256, 2, 32 }, - { "3.1", 31, 0, 108000, 3600, 18000, 14000, 14000, 512, 4, 16 }, - { "3.2", 32, 0, 216000, 5120, 20480, 20000, 20000, 512, 4, 16 }, - { "4", 40, 0, 245760, 8192, 32768, 20000, 25000, 512, 4, 16 }, - { "4.1", 41, 0, 245760, 8192, 32768, 50000, 62500, 512, 2, 16 }, - { "4.2", 42, 0, 522240, 8704, 34816, 50000, 62500, 512, 2, 16 }, - { "5", 50, 0, 589824, 22080, 110400, 135000, 135000, 512, 2, 16 }, - { "5.1", 51, 0, 983040, 36864, 184320, 240000, 240000, 512, 2, 16 }, - { "5.2", 52, 0, 2073600, 36864, 184320, 240000, 240000, 512, 2, 16 }, - { "6", 60, 0, 4177920, 139264, 696320, 240000, 240000, 8192, 2, 16 }, - { "6.1", 61, 0, 8355840, 139264, 696320, 480000, 480000, 8192, 2, 16 }, - { "6.2", 62, 0, 16711680, 139264, 696320, 800000, 800000, 8192, 2, 16 }, -}; - -// H.264 table A-2 plus values from A-1. -static const struct { - int profile_idc; - int cpb_br_vcl_factor; - int cpb_br_nal_factor; -} h264_br_factors[] = { - { 66, 1000, 1200 }, - { 77, 1000, 1200 }, - { 88, 1000, 1200 }, - { 100, 1250, 1500 }, - { 110, 3000, 3600 }, - { 122, 4000, 4800 }, - { 244, 4000, 4800 }, - { 44, 4000, 4800 }, -}; - -// We are only ever interested in the NAL bitrate factor. -static int h264_get_br_factor(int profile_idc) { - int i; - for(i = 0; i < FF_ARRAY_ELEMS(h264_br_factors); i++) { - if(h264_br_factors[i].profile_idc == profile_idc) - return h264_br_factors[i].cpb_br_nal_factor; - } - // Default to the non-high profile value if not specified. - return 1200; -} - -const H264LevelDescriptor *ff_h264_guess_level(int profile_idc, - int64_t bitrate, - int framerate, - int width, int height, - int max_dec_frame_buffering) { - int width_mbs = (width + 15) / 16; - int height_mbs = (height + 15) / 16; - int no_cs3f = !(profile_idc == 66 || - profile_idc == 77 || - profile_idc == 88); - int i; - - for(i = 0; i < FF_ARRAY_ELEMS(h264_levels); i++) { - const H264LevelDescriptor *level = &h264_levels[i]; - - if(level->constraint_set3_flag && no_cs3f) - continue; - - if(bitrate > (int64_t)level->max_br * h264_get_br_factor(profile_idc)) - continue; - - if(width_mbs * height_mbs > level->max_fs) - continue; - if(width_mbs * width_mbs > 8 * level->max_fs) - continue; - if(height_mbs * height_mbs > 8 * level->max_fs) - continue; - - if(width_mbs && height_mbs) { - int max_dpb_frames = - FFMIN(level->max_dpb_mbs / (width_mbs * height_mbs), 16); - if(max_dec_frame_buffering > max_dpb_frames) - continue; - - if(framerate > (level->max_mbps / (width_mbs * height_mbs))) - continue; - } - - return level; - } - - // No usable levels found - frame is too big or bitrate is too high. - return NULL; -} diff --git a/third-party/cbs/include/cbs/h264_levels.h b/third-party/cbs/include/cbs/video_levels.h similarity index 50% rename from third-party/cbs/include/cbs/h264_levels.h rename to third-party/cbs/include/cbs/video_levels.h index 525835ac..53ad277f 100644 --- a/third-party/cbs/include/cbs/h264_levels.h +++ b/third-party/cbs/include/cbs/video_levels.h @@ -22,6 +22,53 @@ #include +#include "cbs_h265.h" + +typedef struct H265LevelDescriptor { + const char *name; + uint8_t level_idc; + + // Table A.6. + uint32_t max_luma_ps; + uint32_t max_cpb_main; + uint32_t max_cpb_high; + uint16_t max_slice_segments_per_picture; + uint8_t max_tile_rows; + uint8_t max_tile_cols; + + // Table A.7. + uint32_t max_luma_sr; + uint32_t max_br_main; + uint32_t max_br_high; + uint8_t min_cr_base_main; + uint8_t min_cr_base_high; +} H265LevelDescriptor; + +typedef struct H265ProfileDescriptor { + const char *name; + uint8_t profile_idc; + uint8_t high_throughput; + + // Tables A.2, A.3 and A.5. + uint8_t max_14bit; + uint8_t max_12bit; + uint8_t max_10bit; + uint8_t max_8bit; + uint8_t max_422chroma; + uint8_t max_420chroma; + uint8_t max_monochrome; + uint8_t intra; + uint8_t one_picture_only; + uint8_t lower_bit_rate; + + // Table A.8. + uint16_t cpb_vcl_factor; + uint16_t cpb_nal_factor; + float format_capability_factor; + float min_cr_scale_factor; + uint8_t max_dpb_pic_buf; +} H265ProfileDescriptor; + typedef struct H264LevelDescriptor { const char *name; uint8_t level_idc; @@ -36,6 +83,20 @@ typedef struct H264LevelDescriptor { uint8_t max_mvs_per_2mb; } H264LevelDescriptor; +const H265ProfileDescriptor *ff_h265_get_profile(const H265RawProfileTierLevel *ptl); + +/** + * Guess the level of a stream from some parameters. + * + * Unknown parameters may be zero, in which case they are ignored. + */ +const H265LevelDescriptor *ff_h265_guess_level(const H265RawProfileTierLevel *ptl, + int64_t bitrate, + int width, int height, + int slice_segments, + int tile_rows, int tile_cols, + int max_dec_pic_buffering); + /** * Guess the level of a stream from some parameters. * diff --git a/third-party/cbs/video_levels.c b/third-party/cbs/video_levels.c new file mode 100644 index 00000000..24143dfc --- /dev/null +++ b/third-party/cbs/video_levels.c @@ -0,0 +1,349 @@ +/* + * This file is part of FFmpeg. + * + * FFmpeg is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * FFmpeg is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with FFmpeg; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include + +#include "include/cbs/video_levels.h" + +// H.264 table A-1. +static const H264LevelDescriptor h264_levels[] = { + // Name MaxMBPS MaxBR MinCR + // | level_idc | MaxFS | MaxCPB | MaxMvsPer2Mb + // | | cs3f | | MaxDpbMbs | | MaxVmvR | | + { "1", 10, 0, 1485, 99, 396, 64, 175, 64, 2, 0 }, + { "1b", 11, 1, 1485, 99, 396, 128, 350, 64, 2, 0 }, + { "1b", 9, 0, 1485, 99, 396, 128, 350, 64, 2, 0 }, + { "1.1", 11, 0, 3000, 396, 900, 192, 500, 128, 2, 0 }, + { "1.2", 12, 0, 6000, 396, 2376, 384, 1000, 128, 2, 0 }, + { "1.3", 13, 0, 11880, 396, 2376, 768, 2000, 128, 2, 0 }, + { "2", 20, 0, 11880, 396, 2376, 2000, 2000, 128, 2, 0 }, + { "2.1", 21, 0, 19800, 792, 4752, 4000, 4000, 256, 2, 0 }, + { "2.2", 22, 0, 20250, 1620, 8100, 4000, 4000, 256, 2, 0 }, + { "3", 30, 0, 40500, 1620, 8100, 10000, 10000, 256, 2, 32 }, + { "3.1", 31, 0, 108000, 3600, 18000, 14000, 14000, 512, 4, 16 }, + { "3.2", 32, 0, 216000, 5120, 20480, 20000, 20000, 512, 4, 16 }, + { "4", 40, 0, 245760, 8192, 32768, 20000, 25000, 512, 4, 16 }, + { "4.1", 41, 0, 245760, 8192, 32768, 50000, 62500, 512, 2, 16 }, + { "4.2", 42, 0, 522240, 8704, 34816, 50000, 62500, 512, 2, 16 }, + { "5", 50, 0, 589824, 22080, 110400, 135000, 135000, 512, 2, 16 }, + { "5.1", 51, 0, 983040, 36864, 184320, 240000, 240000, 512, 2, 16 }, + { "5.2", 52, 0, 2073600, 36864, 184320, 240000, 240000, 512, 2, 16 }, + { "6", 60, 0, 4177920, 139264, 696320, 240000, 240000, 8192, 2, 16 }, + { "6.1", 61, 0, 8355840, 139264, 696320, 480000, 480000, 8192, 2, 16 }, + { "6.2", 62, 0, 16711680, 139264, 696320, 800000, 800000, 8192, 2, 16 }, +}; + +// H.264 table A-2 plus values from A-1. +static const struct { + int profile_idc; + int cpb_br_vcl_factor; + int cpb_br_nal_factor; +} h264_br_factors[] = { + { 66, 1000, 1200 }, + { 77, 1000, 1200 }, + { 88, 1000, 1200 }, + { 100, 1250, 1500 }, + { 110, 3000, 3600 }, + { 122, 4000, 4800 }, + { 244, 4000, 4800 }, + { 44, 4000, 4800 }, +}; + +// We are only ever interested in the NAL bitrate factor. +static int h264_get_br_factor(int profile_idc) { + int i; + for(i = 0; i < FF_ARRAY_ELEMS(h264_br_factors); i++) { + if(h264_br_factors[i].profile_idc == profile_idc) + return h264_br_factors[i].cpb_br_nal_factor; + } + // Default to the non-high profile value if not specified. + return 1200; +} + +const H264LevelDescriptor *ff_h264_guess_level(int profile_idc, + int64_t bitrate, + int framerate, + int width, int height, + int max_dec_frame_buffering) { + int width_mbs = (width + 15) / 16; + int height_mbs = (height + 15) / 16; + int no_cs3f = !(profile_idc == 66 || + profile_idc == 77 || + profile_idc == 88); + int i; + + for(i = 0; i < FF_ARRAY_ELEMS(h264_levels); i++) { + const H264LevelDescriptor *level = &h264_levels[i]; + + if(level->constraint_set3_flag && no_cs3f) + continue; + + if(bitrate > (int64_t)level->max_br * h264_get_br_factor(profile_idc)) + continue; + + if(width_mbs * height_mbs > level->max_fs) + continue; + if(width_mbs * width_mbs > 8 * level->max_fs) + continue; + if(height_mbs * height_mbs > 8 * level->max_fs) + continue; + + if(width_mbs && height_mbs) { + int max_dpb_frames = + FFMIN(level->max_dpb_mbs / (width_mbs * height_mbs), 16); + if(max_dec_frame_buffering > max_dpb_frames) + continue; + + if(framerate > (level->max_mbps / (width_mbs * height_mbs))) + continue; + } + + return level; + } + + // No usable levels found - frame is too big or bitrate is too high. + return NULL; +} + +static const H265LevelDescriptor h265_levels[] = { + // Name CpbFactor-Main MaxSliceSegmentsPerPicture + // | level_idc | CpbFactor-High MaxLumaSr BrFactor-High + // | | MaxLumaPs | | | MaxTileRows | BrFactor-Main | MinCr-Main + // | | | | | | | MaxTileCols | | | MinCr-High + { "1", 30, 36864, 350, 0, 16, 1, 1, 552960, 128, 0, 2, 2 }, + { "2", 60, 122880, 1500, 0, 16, 1, 1, 3686400, 1500, 0, 2, 2 }, + { "2.1", 63, 245760, 3000, 0, 20, 1, 1, 7372800, 3000, 0, 2, 2 }, + { "3", 90, 552960, 6000, 0, 30, 2, 2, 16588800, 6000, 0, 2, 2 }, + { "3.1", 93, 983040, 10000, 0, 40, 3, 3, 33177600, 10000, 0, 2, 2 }, + { "4", 120, 2228224, 12000, 30000, 75, 5, 5, 66846720, 12000, 30000, 4, 4 }, + { "4.1", 123, 2228224, 20000, 50000, 75, 5, 5, 133693440, 20000, 50000, 4, 4 }, + { "5", 150, 8912896, 25000, 100000, 200, 11, 10, 267386880, 25000, 100000, 6, 4 }, + { "5.1", 153, 8912896, 40000, 160000, 200, 11, 10, 534773760, 40000, 160000, 8, 4 }, + { "5.2", 156, 8912896, 60000, 240000, 200, 11, 10, 1069547520, 60000, 240000, 8, 4 }, + { "6", 180, 35651584, 60000, 240000, 600, 22, 20, 1069547520, 60000, 240000, 8, 4 }, + { "6.1", 183, 35651584, 120000, 480000, 600, 22, 20, 2139095040, 120000, 480000, 8, 4 }, + { "6.2", 186, 35651584, 240000, 800000, 600, 22, 20, 4278190080, 240000, 800000, 6, 4 }, +}; + +static const H265ProfileDescriptor h265_profiles[] = { + // profile_idc 8bit one-picture + // HT-profile | 422chroma | lower-bit-rate + // | 14bit | | 420chroma | | CpbVclFactor MinCrScaleFactor + // | | 12bit | | | monochrome| | CpbNalFactor | maxDpbPicBuf + // | | | 10bit | | | intra | | | FormatCapabilityFactor + { "Monochrome", // | | | | | | | | | | | + 4, 0, 2, 1, 1, 1, 1, 1, 1, 0, 0, 1, 667, 733, 1.000, 1.0, 6 }, + { "Monochrome 10", + 4, 0, 2, 1, 1, 0, 1, 1, 1, 0, 0, 1, 833, 917, 1.250, 1.0, 6 }, + { "Monochrome 12", + 4, 0, 2, 1, 0, 0, 1, 1, 1, 0, 0, 1, 1000, 1100, 1.500, 1.0, 6 }, + { "Monochrome 16", + 4, 0, 2, 0, 0, 0, 1, 1, 1, 0, 0, 1, 1333, 1467, 2.000, 1.0, 6 }, + { "Main", + 1, 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1000, 1100, 1.500, 1.0, 6 }, + { "Screen-Extended Main", + 9, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1000, 1100, 1.500, 1.0, 7 }, + { "Main 10", + 2, 0, 2, 2, 2, 2, 2, 2, 2, 2, 0, 2, 1000, 1100, 1.875, 1.0, 6 }, + { "Screen-Extended Main 10", + 9, 0, 1, 1, 1, 0, 1, 1, 0, 0, 0, 1, 1000, 1100, 1.875, 1.0, 7 }, + { "Main 12", + 4, 0, 2, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1500, 1650, 2.250, 1.0, 6 }, + { "Main Still Picture", + 3, 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1000, 1100, 1.500, 1.0, 6 }, + { "Main 10 Still Picture", + 2, 0, 2, 2, 2, 2, 2, 2, 2, 2, 1, 2, 1000, 1100, 1.875, 1.0, 6 }, + { "Main 4:2:2 10", + 4, 0, 2, 1, 1, 0, 1, 0, 0, 0, 0, 1, 1667, 1833, 2.500, 0.5, 6 }, + { "Main 4:2:2 12", + 4, 0, 2, 1, 0, 0, 1, 0, 0, 0, 0, 1, 2000, 2200, 3.000, 0.5, 6 }, + { "Main 4:4:4", + 4, 0, 2, 1, 1, 1, 0, 0, 0, 0, 0, 1, 2000, 2200, 3.000, 0.5, 6 }, + { "High Throughput 4:4:4", + 5, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 1, 2000, 2200, 3.000, 0.5, 6 }, + { "Screen-Extended Main 4:4:4", + 9, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 1, 2000, 2200, 3.000, 0.5, 7 }, + { "Screen-Extended High Throughput 4:4:4", + 9, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 1, 2000, 2200, 3.000, 0.5, 7 }, + { "Main 4:4:4 10", + 4, 0, 2, 1, 1, 0, 0, 0, 0, 0, 0, 1, 2500, 2750, 3.750, 0.5, 6 }, + { "High Throughput 4:4:4 10", + 5, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 1, 2500, 2750, 3.750, 0.5, 6 }, + { "Screen-Extended Main 4:4:4 10", + 9, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 1, 2500, 2750, 3.750, 0.5, 7 }, + { "Screen-Extended High Throughput 4:4:4 10", + 9, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 1, 2500, 2750, 3.750, 0.5, 7 }, + { "Main 4:4:4 12", + 4, 0, 2, 1, 0, 0, 0, 0, 0, 0, 0, 1, 3000, 3300, 4.500, 0.5, 6 }, + { "High Throughput 4:4:4 14", + 5, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 3500, 3850, 5.250, 0.5, 6 }, + { "Screen-Extended High Throughput 4:4:4 14", + 9, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 3500, 3850, 5.250, 0.5, 7 }, + { "Main Intra", + 4, 0, 2, 1, 1, 1, 1, 1, 0, 1, 0, 2, 1000, 1100, 1.500, 1.0, 6 }, + { "Main 10 Intra", + 4, 0, 2, 1, 1, 0, 1, 1, 0, 1, 0, 2, 1000, 1100, 1.875, 1.0, 6 }, + { "Main 12 Intra", + 4, 0, 2, 1, 0, 0, 1, 1, 0, 1, 0, 2, 1500, 1650, 2.250, 1.0, 6 }, + { "Main 4:2:2 10 Intra", + 4, 0, 2, 1, 1, 0, 1, 0, 0, 1, 0, 2, 1667, 1833, 2.500, 0.5, 6 }, + { "Main 4:2:2 12 Intra", + 4, 0, 2, 1, 0, 0, 1, 0, 0, 1, 0, 2, 2000, 2200, 3.000, 0.5, 6 }, + { "Main 4:4:4 Intra", + 4, 0, 2, 1, 1, 1, 0, 0, 0, 1, 0, 2, 2000, 2200, 3.000, 0.5, 6 }, + { "Main 4:4:4 10 Intra", + 4, 0, 2, 1, 1, 0, 0, 0, 0, 1, 0, 2, 2500, 2750, 3.750, 0.5, 6 }, + { "Main 4:4:4 12 Intra", + 4, 0, 2, 1, 0, 0, 0, 0, 0, 1, 0, 2, 3000, 3300, 4.500, 0.5, 6 }, + { "Main 4:4:4 16 Intra", + 4, 0, 2, 0, 0, 0, 0, 0, 0, 1, 0, 2, 4000, 4400, 6.000, 0.5, 6 }, + { "Main 4:4:4 Still Picture", + 4, 0, 2, 1, 1, 1, 0, 0, 0, 1, 1, 2, 2000, 2200, 3.000, 0.5, 6 }, + { "Main 4:4:4 16 Still Picture", + 4, 0, 2, 0, 0, 0, 0, 0, 0, 1, 1, 2, 4000, 4400, 6.000, 0.5, 6 }, + { "High Throughput 4:4:4 16 Intra", + 5, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 2, 4000, 4400, 6.000, 0.5, 6 }, +}; + + +const H265ProfileDescriptor *ff_h265_get_profile(const H265RawProfileTierLevel *ptl) { + int i; + + if(ptl->general_profile_space) + return NULL; + + for(i = 0; i < FF_ARRAY_ELEMS(h265_profiles); i++) { + const H265ProfileDescriptor *profile = &h265_profiles[i]; + + if(ptl->general_profile_idc && + ptl->general_profile_idc != profile->profile_idc) + continue; + if(!ptl->general_profile_compatibility_flag[profile->profile_idc]) + continue; + +#define check_flag(name) \ + if(profile->name < 2) { \ + if(profile->name != ptl->general_##name##_constraint_flag) \ + continue; \ + } + check_flag(max_14bit); + check_flag(max_12bit); + check_flag(max_10bit); + check_flag(max_8bit); + check_flag(max_422chroma); + check_flag(max_420chroma); + check_flag(max_monochrome); + check_flag(intra); + check_flag(one_picture_only); + check_flag(lower_bit_rate); +#undef check_flag + + return profile; + } + + return NULL; +} + +const H265LevelDescriptor *ff_h265_guess_level(const H265RawProfileTierLevel *ptl, + int64_t bitrate, + int width, int height, + int slice_segments, + int tile_rows, int tile_cols, + int max_dec_pic_buffering) { + const H265ProfileDescriptor *profile; + int pic_size, tier_flag, lbr_flag, hbr_factor; + int i; + + if(ptl) + profile = ff_h265_get_profile(ptl); + else + profile = NULL; + if(!profile) { + // Default to using multiplication factors for Main profile. + profile = &h265_profiles[4]; + } + + pic_size = width * height; + + if(ptl) { + tier_flag = ptl->general_tier_flag; + lbr_flag = ptl->general_lower_bit_rate_constraint_flag; + } + else { + tier_flag = 0; + lbr_flag = profile->lower_bit_rate > 0; + } + if(profile->profile_idc == 1 || profile->profile_idc == 2) { + hbr_factor = 1; + } + else if(profile->high_throughput) { + if(profile->intra) + hbr_factor = 24 - 12 * lbr_flag; + else + hbr_factor = 6; + } + else { + hbr_factor = 2 - lbr_flag; + } + + for(i = 0; i < FF_ARRAY_ELEMS(h265_levels); i++) { + const H265LevelDescriptor *level = &h265_levels[i]; + int max_br, max_dpb_size; + + if(tier_flag && !level->max_br_high) + continue; + + if(pic_size > level->max_luma_ps) + continue; + if(width * width > 8 * level->max_luma_ps) + continue; + if(height * height > 8 * level->max_luma_ps) + continue; + + if(slice_segments > level->max_slice_segments_per_picture) + continue; + if(tile_rows > level->max_tile_rows) + continue; + if(tile_cols > level->max_tile_cols) + continue; + + if(tier_flag) + max_br = level->max_br_high; + else + max_br = level->max_br_main; + if(!max_br) + continue; + if(bitrate > (int64_t)profile->cpb_nal_factor * hbr_factor * max_br) + continue; + + if(pic_size <= (level->max_luma_ps >> 2)) + max_dpb_size = FFMIN(4 * profile->max_dpb_pic_buf, 16); + else if(pic_size <= (level->max_luma_ps >> 1)) + max_dpb_size = FFMIN(2 * profile->max_dpb_pic_buf, 16); + else if(pic_size <= (3 * level->max_luma_ps >> 2)) + max_dpb_size = FFMIN(4 * profile->max_dpb_pic_buf / 3, 16); + else + max_dpb_size = profile->max_dpb_pic_buf; + if(max_dec_pic_buffering > max_dpb_size) + continue; + + return level; + } + + return NULL; +} \ No newline at end of file