Continue streaming without audio after a 300 ms microphone timeout

This commit is contained in:
evgeny committed 2026-11-01 21:54:47 +01:00
1 parent 83183f9885
commit f7096bba7d
2 files changed
+23 -16

No files matched your search

+21 -14
View File
@@ -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<std::mutex> 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<std::mutex> 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<std::mutex> 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<std::mutex> guard(m->lock); confirmed = m->confirmedGeneration == m->generation; started = m->started;
waitingSelection = m->wanted.source.id == "portal" && m->framePts < 0; }
+2 -2
View File
@@ -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.