From 95942382bc9c1a42e308ee386be97441f1b3470b Mon Sep 17 00:00:00 2001 From: evgeny Date: Fri, 16 Oct 2026 01:06:09 +0200 Subject: [PATCH] Share desktop publisher microphone with radio while preserving stream format --- tools/chatgui/CMakeLists.txt | 8 +++-- tools/chatgui/src/stream_publish_engine.cpp | 38 +++++++-------------- tools/chatgui/src/stream_publish_engine.h | 6 ++-- 3 files changed, 20 insertions(+), 32 deletions(-) diff --git a/tools/chatgui/CMakeLists.txt b/tools/chatgui/CMakeLists.txt index 5db4c428..faf3ecae 100644 --- a/tools/chatgui/CMakeLists.txt +++ b/tools/chatgui/CMakeLists.txt @@ -162,8 +162,10 @@ if(FFMPEG_FOUND) endif() add_library(chatgui_camera STATIC src/call_camera_capture.cpp src/call_camera_worker.cpp src/call_camera_source.h) target_link_libraries(chatgui_camera PUBLIC ${QT_LIBS} utun) +add_library(chatgui_audio_capture STATIC src/audio_capture.cpp) +target_link_libraries(chatgui_audio_capture PUBLIC ${QT_LIBS} utun) add_library(chatgui_stream_publish STATIC src/stream_publish_engine.cpp src/stream_publish_window.cpp src/call_video_engine.cpp) -target_link_libraries(chatgui_stream_publish PUBLIC chatgui_live_video chatgui_camera) +target_link_libraries(chatgui_stream_publish PUBLIC chatgui_live_video chatgui_camera chatgui_audio_capture) target_include_directories(chatgui_stream_publish PRIVATE ${CMAKE_SOURCE_DIR}/../../lib/libopus/include) set(GUI_CAPTURE_ENABLED 0) if(QT_PACKAGE STREQUAL "Qt6" AND FFMPEG_FOUND) @@ -635,7 +637,7 @@ if(CMAKE_SYSTEM_NAME STREQUAL "Linux") src/audiorecorder.cpp src/call_audio_engine.cpp src/radio_audio_engine.cpp transport/miniaudio_impl.c transport/audio_diagnostics.cpp transport/config_updater.cpp) target_include_directories(test_audio_recovery PRIVATE ${UTUN_INCLUDE_DIRS}) - target_link_libraries(test_audio_recovery PRIVATE ${QT_LIBS} utun_voice utun pthread dl) + target_link_libraries(test_audio_recovery PRIVATE chatgui_audio_capture ${QT_LIBS} utun_voice utun pthread dl) target_link_options(test_audio_recovery PRIVATE "-Wl,--wrap=ma_context_init" "-Wl,--wrap=ma_context_uninit" "-Wl,--wrap=ma_device_init" "-Wl,--wrap=ma_device_start" "-Wl,--wrap=ma_device_uninit" "-Wl,--wrap=audio_context_failed" "-Wl,--wrap=call_send_media") @@ -646,7 +648,7 @@ if(CMAKE_SYSTEM_NAME STREQUAL "Linux") src/sound_manager.cpp src/voice_audio_io.cpp src/audio_device.cpp transport/miniaudio_impl.c transport/audio_diagnostics.cpp transport/config_updater.cpp ${RADIO_MEMBER_TEST_SOURCES}) target_include_directories(test_radio_panel PRIVATE ${UTUN_INCLUDE_DIRS}) - target_link_libraries(test_radio_panel PRIVATE ${QT_LIBS} ${QT_TEST_LIB} utun_voice utun pthread dl ${X11_LIBRARIES}) + target_link_libraries(test_radio_panel PRIVATE chatgui_audio_capture ${QT_LIBS} ${QT_TEST_LIB} utun_voice utun pthread dl ${X11_LIBRARIES}) target_link_options(test_radio_panel PRIVATE "-Wl,--wrap=ma_context_init" "-Wl,--wrap=ma_device_start" "-Wl,--wrap=radio_talk_begin" "-Wl,--wrap=radio_talk_end" "-Wl,--wrap=radio_talk_send" "-Wl,--wrap=silero_vad_create_default" "-Wl,--wrap=silero_vad_destroy" "-Wl,--wrap=silero_vad_reset" diff --git a/tools/chatgui/src/stream_publish_engine.cpp b/tools/chatgui/src/stream_publish_engine.cpp index 832e598a..1a75666b 100644 --- a/tools/chatgui/src/stream_publish_engine.cpp +++ b/tools/chatgui/src/stream_publish_engine.cpp @@ -307,36 +307,21 @@ void StreamPublishEngine::stop() { } bool StreamPublishEngine::openMicrophone() { - m_capture.close(); - m->audioAnchor = -1; m->audioFrames = 0; - auto config = this->config(); auto* context = SoundManager::instance()->context(); - if (!context) { DEBUG_ERROR(DEBUG_CATEGORY_STREAM, "desktop stream audio context unavailable"); m_capture.retryLater(); return false; } - ma_device_id device; bool selected = SoundManager::deviceId(config.microphone, &device); - if (!config.microphone.isEmpty() && !selected) { - DEBUG_ERROR(DEBUG_CATEGORY_STREAM, "desktop selected microphone ID is unavailable"); m_capture.retryLater(); return false; - } - for (int channels : {2, 1}) { - ma_device_config capture = ma_device_config_init(ma_device_type_capture); - capture.capture.format = ma_format_s16; capture.capture.channels = channels; capture.sampleRate = 48000; - capture.capture.pDeviceID = selected ? &device : nullptr; - auto callback = [this](const void* in, void*, unsigned frames) { captureAudio(in, frames); }; - if (!m_capture.init(context, capture, "stream-capture", m->id, callback)) continue; - m->channels = channels; - if (!m_capture.start()) continue; - int native = m_capture.device()->capture.internalChannels; + return m_capture.open(config().microphone, AudioCapture::Role::Stream, 2, m->id, [this](int channels, int native) { + m->audioAnchor = -1; m->audioFrames = 0; m->channels = channels; if (native == 1) DEBUG_WARN(DEBUG_CATEGORY_STREAM, "desktop stream input is mono; stereo transport duplicates the input"); - DEBUG_INFO(DEBUG_CATEGORY_STREAM, "desktop microphone opened requested=%d backend_channels=%d rate=%u", channels, native, m_capture.device()->sampleRate); + DEBUG_INFO(DEBUG_CATEGORY_STREAM, "desktop microphone opened requested=%d backend_channels=%d rate=48000", channels, native); emit audioChannelsChanged(native == 1 ? 1 : channels); return true; - } - DEBUG_ERROR(DEBUG_CATEGORY_STREAM, "desktop stream cannot open microphone: %s", qPrintable(config.microphone)); return false; + }, [] {}, [this](const AudioCapture::Frame& frame) { captureAudio(frame); }); } /* Hardware callback копирует максимум 20ms за элемент, без codec/DSP/allocations. */ -void StreamPublishEngine::captureAudio(const void* input, unsigned frames) { - const auto* pcm = static_cast(input); +void StreamPublishEngine::captureAudio(const AudioCapture::Frame& frame) { + const auto* pcm = frame.pcm; + unsigned frames = frame.frames; if (!m->running) return; - int64_t first = m_capture.callbackTimeUs() - m->origin; - if (m_capture.timingMeasured() || m->audioAnchor < 0 || m_capture.captureMissing()) { + int64_t first = frame.timeUs - m->origin; + if (frame.measured || m->audioAnchor < 0 || frame.missing) { m->audioAnchor = first; m->audioFrames = 0; } first = m->audioAnchor + (int64_t)m->audioFrames * 1000000 / 48000; m->audioFrames += frames; @@ -540,8 +525,9 @@ void StreamPublishEngine::tick() { (unsigned long long)m->sourceGeneration, (unsigned long long)m->generation, (unsigned long long)m->confirmedGeneration, (unsigned long long)m->capturedFrames, (long long)m->framePts); } } - if (m->wanted.audio && m_capture.needsRecovery()) { m_capture.close(); m_capture.retryLater(); } - if (m->wanted.audio && m_capture.retryReady()) openMicrophone(); + if (m->wanted.audio) { + if (!m_capture.subscribed()) openMicrophone(); else m_capture.recover(); + } } unsigned dropped = m->audioDropped.exchange(0); if (dropped) DEBUG_WARN(DEBUG_CATEGORY_STREAM, "desktop audio capture discarded blocks=%u", dropped); diff --git a/tools/chatgui/src/stream_publish_engine.h b/tools/chatgui/src/stream_publish_engine.h index a855cabd..14677371 100644 --- a/tools/chatgui/src/stream_publish_engine.h +++ b/tools/chatgui/src/stream_publish_engine.h @@ -9,7 +9,7 @@ #include #include #include "live_video_encoder.h" -#include "audio_device.h" +#include "audio_capture.h" #include "audio_capture_lease.h" #include "../../../src/stream/stream_publisher.h" @@ -53,7 +53,7 @@ signals: private: struct Impl; std::unique_ptr m; - AudioDevice m_capture; + AudioCapture m_capture; AudioCaptureLease m_microphone{AudioCaptureLease::Mode::Exclusive}; QTimer m_timer; QTimer m_previewTimer; @@ -65,5 +65,5 @@ private: void tick(); void videoLoop(); void audioLoop(); - void captureAudio(const void* pcm, unsigned frames); + void captureAudio(const AudioCapture::Frame& frame); };