diff --git a/tools/chatgui/src/live_video_encoder.cpp b/tools/chatgui/src/live_video_encoder.cpp index 916d0f4f..17afb330 100644 --- a/tools/chatgui/src/live_video_encoder.cpp +++ b/tools/chatgui/src/live_video_encoder.cpp @@ -141,6 +141,7 @@ bool LiveVideoEncoder::open(const LiveVideoConfig& config, Backend backend) { || config.width % 2 || config.height % 2 || config.fps < 1 || config.fps > 60 || config.bitrate < 64000 || config.bitrate > 16000000 || config.codec < 1 || config.codec > 5 || config.vbvBits < 0 || config.vbvBits > 16000000 || config.references < 0 || config.references > 16 + || config.maxQp < -1 || config.maxQp > 51 || (config.maxQp >= 0 && (constrained || config.codec > STREAM_MEDIA_H265)) || (constrained && (config.level <= 0 || config.level > 255 || config.references != 1 || !config.vbvBits || !((config.codec == 1 && config.profile == LiveVideoProfile::H264ConstrainedBaseline) || (config.codec == 2 && config.profile == LiveVideoProfile::H265Main))))) { @@ -162,6 +163,7 @@ bool LiveVideoEncoder::open(const LiveVideoConfig& config, Backend backend) { c->width = config.width; c->height = config.height; c->time_base = {1, 1000000}; c->framerate = {config.fps, 1}; if (constrained && name.endsWith("_qsv")) c->time_base = {1, 90000}; c->bit_rate = config.bitrate; c->rc_max_rate = config.bitrate; + if (config.maxQp >= 0) { c->qmin = 0; c->qmax = config.maxQp; } c->rc_buffer_size = config.vbvBits ? config.vbvBits : config.bitrate / 4; c->gop_size = config.fps; c->max_b_frames = 0; c->thread_count = 2; if (config.references) c->refs = config.references; @@ -356,9 +358,10 @@ bool LiveVideoEncoder::open(const LiveVideoConfig& config, Backend backend) { c->qmin, c->qmax, c->time_base.num, c->time_base.den, c->framerate.num, c->framerate.den); } DEBUG_INFO(DEBUG_CATEGORY_VIDEO, - "desktop encoder ready name=%s codec=%d size=%dx%d fps=%d bitrate=%d level=%d refs=%d vbv=%d profile_verified=%d forced_keys=%d", + "desktop encoder ready name=%s codec=%d size=%dx%d fps=%d bitrate=%d level=%d refs=%d vbv=%d profile_verified=%d " + "forced_keys=%d max_qp=%d dynamic_qp=%d", name.constData(), config.codec, config.width, config.height, config.fps, config.bitrate, - config.level, config.references, c->rc_buffer_size, constrained, m->forcedKeys); + config.level, config.references, c->rc_buffer_size, constrained, m->forcedKeys, config.maxQp, canSetMaxQp()); return true; } } @@ -569,3 +572,33 @@ bool LiveVideoEncoder::setBitrate(int bitrate) { #endif DEBUG_DEBUG(DEBUG_CATEGORY_VIDEO, "desktop bitrate needs encoder replacement name=%s", qPrintable(m->name)); return false; } + +bool LiveVideoEncoder::canSetMaxQp() const { +#ifdef HAVE_FFMPEG + if (!m->codec || m->config.profile != LiveVideoProfile::Automatic || m->config.codec > STREAM_MEDIA_H265) return false; + return m->name.endsWith("_qsv") || (m->name.endsWith("_amf") + && av_opt_find(m->codec->priv_data, "utun_qp_max", nullptr, 0, 0)); +#else + return false; +#endif +} + +bool LiveVideoEncoder::setMaxQp(int qp) { + if (qp < 0 || qp > 51 || !canSetMaxQp()) { + m->error = "Live QP adjustment is unavailable"; + DEBUG_ERROR(DEBUG_CATEGORY_VIDEO, "desktop live QP ceiling unavailable name=%s qp=%d", qPrintable(m->name), qp); return false; + } +#ifdef HAVE_FFMPEG + int result = m->name.endsWith("_amf") ? av_opt_set_int(m->codec->priv_data, "utun_qp_max", qp, 0) : 0; + if (result < 0) { + m->error = ffError(result); + DEBUG_ERROR(DEBUG_CATEGORY_VIDEO, "desktop live QP ceiling failed name=%s qp=%d error=%s", + qPrintable(m->name), qp, qPrintable(m->error)); return false; + } + m->codec->qmin = 0; m->codec->qmax = qp; + DEBUG_DEBUG(DEBUG_CATEGORY_DEBUG, "desktop live QP ceiling requested name=%s qp=%d", qPrintable(m->name), qp); + return true; +#else + return false; +#endif +} diff --git a/tools/chatgui/src/stream_publish_engine.h b/tools/chatgui/src/stream_publish_engine.h index 264271ea..2dc17cd8 100644 --- a/tools/chatgui/src/stream_publish_engine.h +++ b/tools/chatgui/src/stream_publish_engine.h @@ -36,6 +36,7 @@ struct StreamPublishConfig { bool audio = true; bool showCursor = true; bool nativeSize = false, automaticBitrate = false; + bool adaptiveQp = false; // При перерасходе битрейта плавно повышать потолок выше video.maxQp, затем возвращать. }; class StreamPublishEngine : public QObject { Q_OBJECT @@ -50,6 +51,7 @@ public: uint64_t group() const; StreamPublishConfig config() const; uint64_t videoBitrate() const; // Фактический выход video encoder за последний интервал около секунды, бит/с; читать в GUI-потоке. + int videoMaxQp() const; // Текущий потолок QP с учётом адаптации; -1 — настройки encoder. QVariant previewFrame() const; QList codecs() const; static QList sources();