From f7096bba7de75926e9edd88139ae554d82ea744d Mon Sep 17 00:00:00 2001 From: evgeny Date: Sun, 1 Nov 2026 21:54:47 +0100 Subject: [PATCH] Continue streaming without audio after a 300 ms microphone timeout --- tools/chatgui/src/stream_publish_engine.cpp | 35 ++++++++++++--------- tools/chatgui/src/stream_publish_engine.h | 4 +-- 2 files changed, 23 insertions(+), 16 deletions(-) diff --git a/tools/chatgui/src/stream_publish_engine.cpp b/tools/chatgui/src/stream_publish_engine.cpp index 4917a3372..b245ca1c3 100644 --- a/tools/chatgui/src/stream_publish_engine.cpp +++ b/tools/chatgui/src/stream_publish_engine.cpp @@ -23,6 +23,7 @@ extern "C" { } namespace { +constexpr int64_t MicrophoneTimeoutUs = 300000; int64_t monotonicUs() { return call_video_playout_now_us(); } QString text(const char* value) { return QCoreApplication::translate("ChatGui", value); } @@ -344,21 +345,19 @@ bool StreamPublishEngine::start(uint64_t group, const StreamPublishConfig& reque emit configurationChanged(); return true; } -bool StreamPublishEngine::reconfigure(const StreamPublishConfig& config) { +bool StreamPublishEngine::reconfigure(const StreamPublishConfig& requested) { if (!m->running) return false; + auto config = requested; + StreamPublishConfig previous = this->config(); + config.audio = previous.audio; // Аудио меняется независимо от video rollback и отложенного уточнения размера screen. 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"); emit errorOccurred(text("Change cursor visibility before starting screen sharing")); return false; } - if (config.audio != previous.audio) { - DEBUG_WARN(DEBUG_CATEGORY_STREAM, "desktop audio format changed during live publication"); - emit errorOccurred(text("Change the audio source before starting the stream")); return false; - } if (config.showCursor != previous.showCursor) DEBUG_INFO(DEBUG_CATEGORY_VIDEO, "desktop cursor selected enabled=%d supported=%d", config.showCursor, cursorSupported(config.source)); bool sourceChange = !config.source.sameSource(previous.source) || config.video.fps != previous.video.fps @@ -497,8 +496,8 @@ void StreamPublishEngine::captureAudio(const AudioCapture::Frame& frame) { void StreamPublishEngine::audioLoop() { const uint64_t id = m->id; { std::unique_lock lock(m->lock); - m->wake.wait(lock, [this] { return !m->running || m->videoReady; }); } - if (!m->running) return; + m->wake.wait(lock, [this] { return !m->running || !m->wanted.audio || m->videoReady; }); } + if (!m->running || !config().audio) return; auto* encoder = opus_codec_encoder_create(48000, 2); auto* compressor = audio_compressor_create(); int result = encoder && compressor ? opus_codec_encoder_application_set(encoder, OPUS_CODEC_APP_AUDIO) : -1; if (result >= 0) result = opus_codec_encoder_bitrate_set(encoder, 128000); @@ -508,7 +507,7 @@ void StreamPublishEngine::audioLoop() { } else { int16_t pcm[1920]{}; unsigned used = 0; uint64_t sequence = 0; int64_t pts = 0, expected = -1, lastSent = -1; int settings[3] = {-1, -1, -1}; - while (m->running) { + while (m->running && config().audio) { Impl::Audio record; if (!m->audio.pop(record)) { std::unique_lock lock(m->lock); m->wake.wait_for(lock, std::chrono::milliseconds(5)); continue; } if (monotonicUs() - record.created > 250000) { m->audioDropped++; used = 0; continue; } @@ -698,7 +697,7 @@ void StreamPublishEngine::videoLoop(StreamPublishConfig initial) { 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}; + (uint8_t)config.video.codec, (uint8_t)(initial.audio ? STREAM_MEDIA_OPUS_STEREO : 0), revision}; for (const auto& encoded : output) { frameBytes += encoded.data.size(); m->encodedBytes += encoded.data.size(); @@ -784,11 +783,19 @@ void StreamPublishEngine::tick() { } if (m->running) { int64_t audioAt = m->lastAudioAt.load(std::memory_order_acquire); - if (m->wanted.audio && monotonicUs() - (audioAt ? audioAt : m->microphoneStarted) >= 1000000) { - DEBUG_ERROR(DEBUG_CATEGORY_STREAM, "desktop microphone has no PCM id=%llu received=%d idle_us=%lld", - (unsigned long long)m->id, audioAt != 0, (long long)(monotonicUs() - (audioAt ? audioAt : m->microphoneStarted))); - publicationFailed(m->id, text("The microphone is not delivering audio. Select another microphone or disable stream audio.")); return; + if (m->wanted.audio && monotonicUs() - (audioAt ? audioAt : m->microphoneStarted) >= MicrophoneTimeoutUs) { + DEBUG_WARN(DEBUG_CATEGORY_STREAM, "desktop microphone timeout id=%llu received=%d idle_us=%lld limit_us=%lld; continuing without audio", + (unsigned long long)m->id, audioAt != 0, (long long)(monotonicUs() - (audioAt ? audioAt : m->microphoneStarted)), + (long long)MicrophoneTimeoutUs); + if (!gui_stream_publish_audio(m->id, false)) { + publicationFailed(m->id, text("Could not change stream audio")); return; + } + { std::lock_guard guard(m->lock); m->wanted.audio = false; } + m_capture.closeAsync(); m->wake.notify_all(); + if (m->audioThread.joinable()) m->audioThread.join(); + emit configurationChanged(); } + if (!m->wanted.audio && !m_capture.closing()) m_microphone.release(); bool confirmed, waitingSelection; int64_t started; { std::lock_guard guard(m->lock); confirmed = m->confirmedGeneration == m->generation; started = m->started; waitingSelection = m->wanted.source.id == "portal" && m->framePts < 0; } diff --git a/tools/chatgui/src/stream_publish_engine.h b/tools/chatgui/src/stream_publish_engine.h index 2403d715c..aabd47483 100644 --- a/tools/chatgui/src/stream_publish_engine.h +++ b/tools/chatgui/src/stream_publish_engine.h @@ -33,7 +33,7 @@ struct StreamVideoSource { struct StreamPublishConfig { StreamVideoSource source; // Выбранный capture; camera-режим должен поддерживать video.width/height/fps. LiveVideoConfig video; // Параметры encoder; поддерживаемый codec, fps 1..60, размеры в допустимом диапазоне encoder. - bool audio = true; // Микрофон и stereo Opus 48кГц/128кбит/с; при отказе открытия стартует без аудио, live меняется только mute. + bool audio = true; // Микрофон и stereo Opus 48кГц/128кбит/с; отказ открытия/нет PCM 300мс отключает аудио, видео продолжается. bool startMuted = false; // Начальный mute применяется до запуска capture; дальнейшее переключение через setMuted(). bool showCursor = true; // Учитывается поддерживаемым screen backend; portal разрешает выбор только до открытия сессии. bool nativeSize = false, automaticBitrate = false; // Screen: исходные размеры до 4096 и пересчёт bitrate при уточнении размера. @@ -45,7 +45,7 @@ public: explicit StreamPublishEngine(QObject* parent = nullptr); ~StreamPublishEngine() override; bool start(uint64_t group, const StreamPublishConfig& config); // true — запуск принят; результат/ошибки ещё асинхронны. false может начать stop. - bool reconfigure(const StreamPublishConfig& config); // true — принято, не подтверждение KEY; audio и cursor сессии portal менять нельзя. + bool reconfigure(const StreamPublishConfig& config); // true — принято, не подтверждение KEY; audio сохраняется текущий, cursor portal менять нельзя. void stop(); // Идемпотентный запрос остановки; освобождение workers/microphone и доотправка actor завершаются через tick. bool active() const; // Actor-id ещё принадлежит engine, включая запуск и остановку/дренирование. bool running() const; // Capture/encoder ещё работают либо ждут выбора portal/разрешения CPU; stop сразу даёт false.