diff --git a/tools/chatgui/src/stream_publish_engine.cpp b/tools/chatgui/src/stream_publish_engine.cpp index 518657bc..da1e9ebc 100644 --- a/tools/chatgui/src/stream_publish_engine.cpp +++ b/tools/chatgui/src/stream_publish_engine.cpp @@ -406,6 +406,7 @@ void StreamPublishEngine::audioLoop() { void StreamPublishEngine::videoLoop() { #ifdef HAVE_STREAM_CAPTURE std::unique_ptr encoder; + StreamPublishConfig encoding; QImage lastImage; uint64_t current = 0; uint32_t revision = 0; int64_t lastPts = -1, lastKey = -1000000, next = 0, report = 0; unsigned count = 0, skipped = 0; while (m->running) { @@ -416,6 +417,17 @@ void StreamPublishEngine::videoLoop() { if (m->frameGeneration == generation) { frame = m->frame; capture = m->framePts; m->frame = {}; } else capture = -1; } if (generation != current) { + auto comparable = config.video; comparable.bitrate = encoding.video.bitrate; + bool onlyBitrate = encoder && comparable == encoding.video && config.source.kind == encoding.source.kind + && config.source.id == encoding.source.id && config.source.window == encoding.source.window; + if (onlyBitrate && encoder->setBitrate(config.video.bitrate)) { + current = generation; encoding = config; + { std::lock_guard guard(m->lock); + if (m->generation == generation) { m->good = config; m->hasGood = true; m->confirmedGeneration = generation; } } + QMetaObject::invokeMethod(this, [this, generation] { + if (generation == m->generation) { emit configurationChanged(); emit stateChanged(text("Live")); } + }); + } else { auto candidate = std::make_unique(); if (!candidate->open(config.video)) { QString error = candidate->error(); @@ -424,9 +436,10 @@ void StreamPublishEngine::videoLoop() { m->wake.wait_for(lock, std::chrono::milliseconds(100), [this, generation] { return !m->running || m->generation != generation; }); continue; } - encoder = std::move(candidate); current = generation; revision++; lastImage = {}; lastKey = -1000000; + encoder = std::move(candidate); current = generation; encoding = config; revision++; lastImage = {}; lastKey = -1000000; DEBUG_INFO(DEBUG_CATEGORY_VIDEO, "desktop video prepared generation=%llu revision=%u encoder=%s", (unsigned long long)generation, revision, qPrintable(encoder->name())); + } } bool screen = config.source.kind != StreamVideoSource::Camera; if (frame.isValid()) { diff --git a/tools/chatgui/src/stream_video_encoder.cpp b/tools/chatgui/src/stream_video_encoder.cpp index 8266d6c7..3115c919 100644 --- a/tools/chatgui/src/stream_video_encoder.cpp +++ b/tools/chatgui/src/stream_video_encoder.cpp @@ -47,7 +47,8 @@ QList encoders(int codec) { case STREAM_MEDIA_H264: return {"h264_nvenc", "h264_qsv", "h264_amf", "h264_vaapi", "libx264", "libopenh264"}; case STREAM_MEDIA_H265: return {"hevc_nvenc", "hevc_qsv", "hevc_amf", "hevc_vaapi", "libx265"}; case STREAM_MEDIA_VP9: return {"vp9_qsv", "vp9_vaapi", "libvpx-vp9"}; - case STREAM_MEDIA_AV1: return {"av1_nvenc", "av1_qsv", "av1_amf", "av1_vaapi", "libsvtav1", "libaom-av1"}; + // FFmpeg/SVT не принимает произвольный KEY при GOP>1; libaom поддерживает нужный live-контракт. + case STREAM_MEDIA_AV1: return {"av1_nvenc", "av1_qsv", "av1_amf", "av1_vaapi", "libaom-av1"}; case STREAM_MEDIA_H266: return {"libvvenc"}; default: return {}; } @@ -120,9 +121,6 @@ bool StreamVideoEncoder::open(const StreamVideoConfig& config) { } else if (name == "libvpx-vp9" || name == "libaom-av1") { av_dict_set(&options, "deadline", "realtime", 0); av_dict_set(&options, "cpu-used", "8", 0); av_dict_set(&options, "lag-in-frames", "0", 0); av_dict_set(&options, "row-mt", "1", 0); - } else if (name == "libsvtav1") { - c->rc_max_rate = 0; - av_dict_set(&options, "preset", "11", 0); av_dict_set(&options, "svtav1-params", "pred-struct=1:lookahead=0:lp=2", 0); } else if (name.endsWith("_qsv")) { av_dict_set(&options, "async_depth", "1", 0); av_dict_set(&options, "look_ahead", "0", 0); } else if (name.endsWith("_vaapi")) { @@ -194,7 +192,8 @@ bool StreamVideoEncoder::encode(const QImage& image, uint64_t pts, bool key, QLi if (result >= 0) { const int* coefficients = sws_getCoefficients(SWS_CS_ITU709); sws_setColorspaceDetails(m->scale, coefficients, 1, coefficients, 0, 0, 1 << 16, 1 << 16); - const uint8_t* input[] = {canvas.constBits()}; int stride[] = {static_cast(canvas.bytesPerLine())}; + const uint8_t* input[4] = {canvas.constBits(), nullptr, nullptr, nullptr}; + int stride[4] = {static_cast(canvas.bytesPerLine()), 0, 0, 0}; result = sws_scale(m->scale, input, stride, 0, canvas.height(), m->frame->data, m->frame->linesize); } m->frame->pts = static_cast(pts); m->frame->pict_type = key ? AV_PICTURE_TYPE_I : AV_PICTURE_TYPE_NONE; @@ -250,8 +249,25 @@ bool StreamVideoEncoder::encode(const QImage& image, uint64_t pts, bool key, QLi QList StreamVideoEncoder::available() { QList result; for (int codec = 1; codec <= 5; codec++) { +#ifdef HAVE_FFMPEG + bool installed = false; + for (const auto& name : encoders(codec)) if (avcodec_find_encoder_by_name(name.constData())) { installed = true; break; } + if (!installed) { DEBUG_DEBUG(DEBUG_CATEGORY_VIDEO, "desktop codec has no installed encoder codec=%d", codec); continue; } +#endif StreamVideoEncoder encoder; StreamVideoConfig config; config.width = 320; config.height = 180; config.codec = codec; if (encoder.open(config)) result.push_back(codec); } return result; } + +bool StreamVideoEncoder::setBitrate(int bitrate) { + if (bitrate < 64000 || bitrate > 16000000) { DEBUG_ERROR(DEBUG_CATEGORY_VIDEO, "desktop live bitrate invalid value=%d", bitrate); return false; } +#ifdef HAVE_FFMPEG + // libx264 перечитывает эти три значения через reconfig_encoder при подаче следующего кадра. + if (m->codec && m->name == "libx264") { + m->codec->bit_rate = m->codec->rc_max_rate = bitrate; m->codec->rc_buffer_size = bitrate / 4; m->config.bitrate = bitrate; + DEBUG_INFO(DEBUG_CATEGORY_VIDEO, "desktop encoder bitrate updated in place name=libx264 bitrate=%d", bitrate); return true; + } +#endif + DEBUG_DEBUG(DEBUG_CATEGORY_VIDEO, "desktop bitrate needs encoder replacement name=%s", qPrintable(m->name)); return false; +} diff --git a/tools/chatgui/src/stream_video_encoder.h b/tools/chatgui/src/stream_video_encoder.h index 81dbe4d4..11578696 100644 --- a/tools/chatgui/src/stream_video_encoder.h +++ b/tools/chatgui/src/stream_video_encoder.h @@ -19,6 +19,7 @@ public: StreamVideoEncoder(); ~StreamVideoEncoder(); bool open(const StreamVideoConfig& config); + bool setBitrate(int bitrate); // true — изменение на следующем кадре; false — нужна смена видеоконтекста void close(); bool encode(const QImage& image, uint64_t pts, bool key, QList& output); QString error() const;