Browse Source

Adapt the stream QP ceiling from measured encoded bitrate

stream-multiview
evgeny 6 days ago
parent
commit
df02aa8170
  1. 48
      tools/chatgui/src/stream_publish_engine.cpp
  2. 3
      tools/chatgui/src/stream_publish_window.h

48
tools/chatgui/src/stream_publish_engine.cpp

@ -1,6 +1,7 @@
#include "stream_publish_engine.h"
#include "sound_manager.h"
#include "audio_event_queue.h"
#include "stream_qp_control.h"
#include "../transport/stream_bridge.h"
#include "debug_config.h"
#include <QGuiApplication>
@ -111,6 +112,7 @@ struct StreamPublishEngine::Impl {
std::atomic<uint64_t> imageFrames{0};
std::atomic<uint64_t> convertedFrames{0}, encodedFrames{0}, deliveredFrames{0}, repeatedFrames{0};
std::atomic<uint64_t> encodedBytes{0}, lastKeyBytes{0}; // Видео до сетевой отправки, без повторов транспорта.
std::atomic<int> videoMaxQp{-1};
uint64_t bitrateBytes = 0, videoBitrate = 0;
int64_t bitrateAt = 0;
void videoProgress(const char* stage) {
@ -240,6 +242,7 @@ bool StreamPublishEngine::running() const { return m->running.load(); }
uint64_t StreamPublishEngine::group() const { return m->group; }
StreamPublishConfig StreamPublishEngine::config() const { std::lock_guard<std::mutex> guard(m->lock); return m->wanted; }
uint64_t StreamPublishEngine::videoBitrate() const { return active() ? m->videoBitrate : 0; }
int StreamPublishEngine::videoMaxQp() const { return active() ? m->videoMaxQp.load() : -1; }
QVariant StreamPublishEngine::previewFrame() const {
#ifdef HAVE_STREAM_CAPTURE
std::lock_guard<std::mutex> guard(m->lock); return QVariant::fromValue(m->previewFrame);
@ -580,6 +583,10 @@ bool StreamPublishEngine::start(uint64_t group, const StreamPublishConfig& confi
DEBUG_WARN(DEBUG_CATEGORY_VIDEO, "desktop capture/encoder unavailable codec=%d", config.video.codec);
emit errorOccurred(text("Video capture or encoder is unavailable")); return false;
}
if (config.adaptiveQp && (config.video.maxQp < 0 || config.video.codec > 2)) {
DEBUG_ERROR(DEBUG_CATEGORY_VIDEO, "desktop invalid QP adaptation codec=%d max_qp=%d", config.video.codec, config.video.maxQp);
emit errorOccurred(text("Select a maximum QP before enabling adaptation")); return false;
}
if (config.audio && !m_microphone.acquire()) {
DEBUG_WARN(DEBUG_CATEGORY_STREAM, "desktop publication blocked: microphone owned by another operation");
emit errorOccurred(text("The microphone is used by another operation")); return false;
@ -590,6 +597,7 @@ bool StreamPublishEngine::start(uint64_t group, const StreamPublishConfig& confi
m->origin = m->started = monotonicUs(); m->stats = {}; }
m->convertedFrames = m->encodedFrames = m->deliveredFrames = m->repeatedFrames = 0;
m->encodedBytes = m->lastKeyBytes = 0; m->bitrateBytes = m->videoBitrate = 0; m->bitrateAt = m->origin;
m->videoMaxQp = config.video.maxQp;
m->videoProgress("starting"); m->captureReport = 0;
m->captureReportRaw = m->captureReportConverted = m->captureReportEncoded = m->captureReportDelivered = 0;
m->id = QRandomGenerator::global()->generate64(); if (!m->id) m->id = 1;
@ -633,6 +641,10 @@ bool StreamPublishEngine::start(uint64_t group, const StreamPublishConfig& confi
bool StreamPublishEngine::reconfigure(const StreamPublishConfig& config) {
if (!m->running) return false;
if (config.adaptiveQp && (config.video.maxQp < 0 || config.video.codec > 2)) {
DEBUG_ERROR(DEBUG_CATEGORY_VIDEO, "desktop invalid QP adaptation codec=%d max_qp=%d", config.video.codec, config.video.maxQp);
emit errorOccurred(text("Select a maximum QP before enabling adaptation")); return false;
}
StreamPublishConfig previous = this->config();
if (previous.source.id == "portal" && config.source.sameSource(previous.source) && config.showCursor != previous.showCursor) {
DEBUG_WARN(DEBUG_CATEGORY_VIDEO, "Wayland capture cursor mode cannot change during a portal session");
@ -852,6 +864,7 @@ void StreamPublishEngine::videoLoop(StreamPublishConfig initial) {
std::unique_ptr<LiveVideoEncoder> encoder;
StreamPublishConfig encoding;
QImage lastImage; uint64_t current = 0; uint32_t revision = 0;
StreamQpControl qp; uint64_t qpGeneration = 0;
int64_t lastPts = -1, lastKey = -1000000, next = 0, report = 0; unsigned count = 0, skipped = 0;
while (m->running) {
m->videoProgress("waiting-frame");
@ -892,7 +905,8 @@ void StreamPublishEngine::videoLoop(StreamPublishConfig initial) {
if (generation != current) {
auto comparable = config.video; comparable.bitrate = encoding.video.bitrate;
bool onlyBitrate = encoder && comparable == encoding.video && config.source.sameSource(encoding.source);
if (onlyBitrate && encoder->setBitrate(config.video.bitrate)) {
if (onlyBitrate && (!config.adaptiveQp || encoder->canSetMaxQp())
&& (config.video == encoding.video || encoder->setBitrate(config.video.bitrate))) {
current = generation; encoding = config;
{ std::lock_guard<std::mutex> guard(m->lock);
if (m->generation == generation) { m->good = config; m->hasGood = true; m->confirmedGeneration = generation; } }
@ -936,6 +950,13 @@ void StreamPublishEngine::videoLoop(StreamPublishConfig initial) {
});
continue;
}
if (config.adaptiveQp && !candidate->canSetMaxQp()) {
DEBUG_ERROR(DEBUG_CATEGORY_VIDEO, "desktop QP adaptation unavailable encoder=%s", qPrintable(candidate->name()));
QMetaObject::invokeMethod(this, [this, generation] {
sourceFailed(generation, text("QP adaptation is unavailable for this encoder. Disable adaptation or choose another codec."));
});
next = monotonicUs() + 100000; continue;
}
// Начальный формат publisher уже зарегистрирован; уточнение размера требует новой revision даже до первого KEY.
revision = revision ? revision + 1 : config.video == initial.video ? 1 : 2;
m->videoProgress("replace-encoder");
@ -948,6 +969,17 @@ void StreamPublishEngine::videoLoop(StreamPublishConfig initial) {
(unsigned long long)generation, revision, qPrintable(encoder->name()));
}
}
if (qpGeneration != current) {
qp.reset(config.video.maxQp, config.video.bitrate, config.adaptiveQp, monotonicUs());
if (encoder->canSetMaxQp() && !encoder->setMaxQp(config.video.maxQp < 0 ? 51 : config.video.maxQp)) {
QString error = encoder->error();
QMetaObject::invokeMethod(this, [this, generation, error] { sourceFailed(generation, error); });
next = monotonicUs() + 100000; continue;
}
qpGeneration = current; m->videoMaxQp = qp.limit();
DEBUG_INFO(DEBUG_CATEGORY_VIDEO, "desktop QP control encoder=%s base=%d adaptive=%d target_bps=%d step=1 interval_ms=2000",
qPrintable(encoder->name()), config.video.maxQp, config.adaptiveQp, config.video.bitrate);
}
bool screen = config.source.kind != StreamVideoSource::Camera;
if (!frame.isNull()) {
if (!screen && (capture <= lastPts || monotonicUs() - m->origin - capture > 250000)) { skipped++; continue; }
@ -968,9 +1000,11 @@ void StreamPublishEngine::videoLoop(StreamPublishConfig initial) {
}
m->encodedFrames += output.size();
m->videoProgress("publish-frame");
uint64_t frameBytes = 0;
stream_media_format format{(uint16_t)config.video.width, (uint16_t)config.video.height, (uint16_t)config.video.rotation,
(uint8_t)config.video.codec, (uint8_t)(config.audio ? STREAM_MEDIA_OPUS_STEREO : 0), revision};
for (const auto& encoded : output) {
frameBytes += encoded.data.size();
m->encodedBytes += encoded.data.size();
if (encoded.key) m->lastKeyBytes = encoded.data.size();
if (gui_stream_publish_feed(m->id, true, encoded.pts, encoded.key, encoded.data, &format) == 1) {
@ -994,6 +1028,18 @@ void StreamPublishEngine::videoLoop(StreamPublishConfig initial) {
}
} else skipped++;
}
const int previousQp = qp.limit(), selectedQp = qp.update(frameBytes, monotonicUs());
if (selectedQp != previousQp) {
if (!encoder->setMaxQp(selectedQp)) {
QString error = encoder->error();
QMetaObject::invokeMethod(this, [this, generation, error] { sourceFailed(generation, error); });
next = monotonicUs() + 100000; continue;
}
m->videoMaxQp = selectedQp;
DEBUG_INFO(DEBUG_CATEGORY_VIDEO, "desktop QP adapted encoder=%s previous=%d selected=%d base=%d actual_bps=%llu target_bps=%d",
qPrintable(encoder->name()), previousQp, selectedQp, config.video.maxQp,
(unsigned long long)qp.bitrate(), config.video.bitrate);
}
if (now - report >= 1000000) {
DEBUG_DEBUG(DEBUG_CATEGORY_VIDEO, "desktop capture generation=%llu encoded=%u skipped=%u frame_age=%lldus encoder=%s",
(unsigned long long)generation, count, skipped, (long long)(screen ? 0 : now - m->origin - capture), qPrintable(encoder->name()));

3
tools/chatgui/src/stream_publish_window.h

@ -8,6 +8,7 @@ class QLabel;
class QPushButton;
class QCheckBox;
class QButtonGroup;
class QSpinBox;
class StreamPublishWindow : public QWidget {
Q_OBJECT
public:
@ -21,6 +22,8 @@ private:
QList<StreamVideoMode> m_modes;
QComboBox *m_source, *m_microphone, *m_mode, *m_codec, *m_bitrate;
QCheckBox *m_audio, *m_mute, *m_cursor;
QSpinBox* m_maxQp;
QCheckBox* m_adaptiveQp;
QLabel *m_status, *m_stats, *m_inputChannels;
QPushButton* m_start;
QButtonGroup* m_rotation;

Loading…
Cancel
Save