diff --git a/tools/chatgui/src/stream_publish_window.cpp b/tools/chatgui/src/stream_publish_window.cpp index 217d9899a..92f06ebaf 100644 --- a/tools/chatgui/src/stream_publish_window.cpp +++ b/tools/chatgui/src/stream_publish_window.cpp @@ -196,7 +196,9 @@ StreamPublishWindow::StreamPublishWindow(StreamPublishEngine* engine, uint64_t g connect(menu, &QMenu::triggered, this, &StreamPublishWindow::apply); connect(m_microphone, &QMenu::triggered, this, [this](QAction* action) { if (m_updating || !action->isCheckable()) return; - if (action != m_noAudio) { + const bool audio = action != m_noAudio; + const bool audioChanged = m_engine->running() && audio != m_engine->config().audio; + if (audio) { auto* sound = SoundManager::instance(); const QString id = action->data().toString(); if (id != sound->captureDevice()) { @@ -204,6 +206,7 @@ StreamPublishWindow::StreamPublishWindow(StreamPublishEngine* engine, uint64_t g emit microphoneChanged(id); } } + if (audioChanged) m_engine->setAudioEnabled(audio); synchronize(); saveSettings(); }); for (auto* menu : {m_codec, m_maxQp}) connect(menu, &QMenu::triggered, this, [this] { if (!m_updating) synchronize(); }); @@ -398,7 +401,7 @@ void StreamPublishWindow::refreshMicrophones() { const QString input = SoundManager::instance()->captureDevice(); clearOptions(m_microphone, "Audio source"); m_noAudio = addOption(m_microphone, text("No audio"), QVariant()); - m_noAudio->setEnabled(!m_engine->active()); + m_noAudio->setEnabled(!m_engine->active() || m_engine->running()); addOption(m_microphone, text("Default microphone"), QString()); for (const auto& device : SoundManager::enumerateCaptureDevices()) addOption(m_microphone, device.name, device.id); if (!option(m_microphone, input)) addOption(m_microphone, text("Selected microphone unavailable"), input); @@ -448,8 +451,8 @@ void StreamPublishWindow::synchronize() { && selected(m_source) && selected(m_resolution) && selected(m_fps) && selected(m_codec))); for (auto* menu : {m_source, m_resolution, m_fps, m_codec, m_bitrate, m_rotation}) menu->menuAction()->setEnabled(!active || m_engine->running()); - m_microphone->menuAction()->setEnabled(!active || (m_engine->running() && m_engine->config().audio)); - m_noAudio->setEnabled(!active); + m_microphone->menuAction()->setEnabled(!active || m_engine->running()); + m_noAudio->setEnabled(!active || m_engine->running()); if (active) m_audioMuted = m_engine->muted(); bool audio = active ? m_engine->config().audio : selection().audio; m_sound->setEnabled(audio && (!active || m_engine->running())); diff --git a/tools/chatgui/tests/test_stream_publish.cpp b/tools/chatgui/tests/test_stream_publish.cpp index cd2ebfcff..3d683e54f 100644 --- a/tools/chatgui/tests/test_stream_publish.cpp +++ b/tools/chatgui/tests/test_stream_publish.cpp @@ -1194,7 +1194,7 @@ private slots: QTRY_VERIFY(stream_member_load(inst, group, inst->node_id, &before, nullptr) == STREAM_OK && before.published); StreamPublishWindow controls(&engine, group); auto* audio = menu(controls, "streamAudioSource"); QVERIFY(audio->menuAction()->isEnabled()); - QVERIFY(!choices(audio).front()->isEnabled()); + QVERIFY(choices(audio).front()->isEnabled()); const unsigned opened = captureOpens.load(), encoded = audioEncodes.load(); slowCaptureClose = true; auto restoreClose = qScopeGuard([] { slowCaptureClose = false; }); @@ -1244,12 +1244,35 @@ private slots: QVERIFY(gui_stream_open(210, group, inst->node_id, member.source.source_epoch)); auto closeViewer = qScopeGuard([] { gui_stream_stop(210); }); StreamRecord out; QVERIFY(record(210, StreamRecordKind::Opened, out)); QVERIFY(record(210, StreamRecordKind::Video, out)); - QCOMPARE(out.format.audio_codec, uint8_t(0)); QCOMPARE(audioEncodes.load(), 0u); + QCOMPARE(out.format.audio_codec, uint8_t(STREAM_MEDIA_OPUS_STEREO)); QCOMPARE(audioEncodes.load(), 0u); + QVERIFY(!member.audio_enabled); if (scenario == 2) QCOMPARE(captureOpens.load(), 0u); AudioCaptureLease microphone; QVERIFY(microphone.acquire()); microphone.release(); QVERIFY(engine.running()); QVERIFY(errors.isEmpty()); DEBUG_INFO(DEBUG_CATEGORY_STREAM, "fixture video without microphone scenario=%d audio_codec=%u capture_opens=%u", scenario, out.format.audio_codec, captureOpens.load()); + StreamPublishWindow controls(&engine, group); + auto* audio = menu(controls, "streamAudioSource"); + QVERIFY(audio->menuAction()->isEnabled()); QVERIFY(choices(audio).front()->isEnabled()); + failCaptureOpen = false; + choose(audio, QString()); // Включается и при неизменившемся общем ID микрофона. + QVERIFY(engine.config().audio); QTRY_VERIFY(audioEncodes.load() > 0); + stream_member_state enabled{}; + QTRY_VERIFY(stream_member_load(inst, group, inst->node_id, &enabled, nullptr) == STREAM_OK && enabled.audio_enabled); + QCOMPARE(enabled.source.source_epoch, member.source.source_epoch); + QVERIFY(record(210, StreamRecordKind::Audio, out)); + const auto audioPts = out.pts; + choose(audio, QVariant()); + QVERIFY(!engine.config().audio); QVERIFY(engine.running()); QVERIFY(audio->menuAction()->isEnabled()); + stream_member_state disabled{}; + QTRY_VERIFY(stream_member_load(inst, group, inst->node_id, &disabled, nullptr) == STREAM_OK && !disabled.audio_enabled); + QCOMPARE(disabled.source.source_epoch, member.source.source_epoch); + choose(audio, QString()); + QVERIFY(engine.config().audio); + do { QVERIFY(record(210, StreamRecordKind::Audio, out)); } while (out.pts <= audioPts); + QVERIFY(engine.running()); QVERIFY(errors.isEmpty()); + DEBUG_INFO(DEBUG_CATEGORY_STREAM, "fixture live audio selection scenario=%d toggles=3 epoch=%llx audio_PTS=%llu->%llu", + scenario, (unsigned long long)member.source.source_epoch, (unsigned long long)audioPts, (unsigned long long)out.pts); engine.stop(); QTRY_VERIFY_WITH_TIMEOUT(!engine.active(), 4000); #else QSKIP("Capture and null audio instrumentation unavailable"); @@ -1314,6 +1337,12 @@ private slots: DEBUG_INFO(DEBUG_CATEGORY_STREAM, "fixture microphone timeout initial=%d detected_ms=%lld video_PTS=%llu->%llu epoch=%llx audio_enabled=0", initialFailure, (long long)timeoutMs, (unsigned long long)firstPts, (unsigned long long)out.pts, (unsigned long long)after.source.source_epoch); + suppressMicrophone = false; + choose(menu(controls, "streamAudioSource"), QString()); + QVERIFY(engine.config().audio); + QTRY_VERIFY(stream_member_load(inst, group, inst->node_id, &after, nullptr) == STREAM_OK && after.audio_enabled); + QCOMPARE(after.source.source_epoch, before.source.source_epoch); + QVERIFY(record(211, StreamRecordKind::Audio, out)); QVERIFY(engine.running()); QVERIFY(errors.isEmpty()); engine.stop(); QTRY_VERIFY_WITH_TIMEOUT(!engine.active(), 4000); #else QSKIP("Capture and encoder instrumentation unavailable");