diff --git a/src/stream/stream_publisher.c b/src/stream/stream_publisher.c index dddad047..1e3463dc 100644 --- a/src/stream/stream_publisher.c +++ b/src/stream/stream_publisher.c @@ -73,6 +73,7 @@ static void publisher_close(struct stream_publisher *p, int reason) { p->handle = NULL; records_clear(&p->video); records_clear(&p->audio); p->status.state = reason ? STREAM_PUBLISHER_FAILED : STREAM_PUBLISHER_ENDED; p->status.reason = reason; p->status.video_bytes = p->status.audio_bytes = p->status.total_bytes = 0; + p->status.stream_bytes = p->status.transport_bytes = p->status.inflight_bytes = p->status.acked_1s = 0; DEBUG_INFO(DEBUG_CATEGORY_STREAM, "publisher closed id=%llu epoch=%llx reason=%d", (unsigned long long)p->status.id, (unsigned long long)p->status.epoch, reason); } diff --git a/tools/chatgui/src/audio_capture_lease.h b/tools/chatgui/src/audio_capture_lease.h index f28a737e..b17dfde8 100644 --- a/tools/chatgui/src/audio_capture_lease.h +++ b/tools/chatgui/src/audio_capture_lease.h @@ -1,20 +1,34 @@ -/* Единственный владелец desktop-микрофона: звонок, рация, запись либо трансляция. +/* Desktop-микрофон: publisher получает Exclusive, существующие voice-контуры — Shared. + * Сохраняется совместная работа звонка/рации/записи; publisher исключает остальные captures в обе стороны. * Lease берётся до открытия backend, сохраняется при recovery и освобождается после close/join. - * Inline состояние общее для translation units; callback не обращается к lease. */ + * Каждый lease использует управляющий поток, общее atomic состояние доступно из разных потоков. */ #pragma once #include class AudioCaptureLease { public: - AudioCaptureLease() = default; + enum class Mode { Shared, Exclusive }; + explicit AudioCaptureLease(Mode mode = Mode::Shared) : m_mode(mode) {} ~AudioCaptureLease() { release(); } AudioCaptureLease(const AudioCaptureLease&) = delete; AudioCaptureLease& operator=(const AudioCaptureLease&) = delete; bool acquire() { - const void* expected = nullptr; - return owner.compare_exchange_strong(expected, this) || expected == this; + if (m_held) return true; + int expected = 0; + if (m_mode == Mode::Exclusive) m_held = owners.compare_exchange_strong(expected, -1); + else { + expected = owners.load(); + while (expected >= 0) if (owners.compare_exchange_weak(expected, expected + 1)) { m_held = true; break; } + } + return m_held; } - void release() { const void* expected = this; owner.compare_exchange_strong(expected, nullptr); } - bool held() const { return owner.load() == this; } + void release() { + if (!m_held) return; + if (m_mode == Mode::Exclusive) owners.store(0); else owners.fetch_sub(1); + m_held = false; + } + bool held() const { return m_held; } private: - static inline std::atomic owner{nullptr}; + Mode m_mode; + bool m_held = false; + static inline std::atomic owners{0}; }; diff --git a/tools/chatgui/src/stream_publish_engine.h b/tools/chatgui/src/stream_publish_engine.h index 8e5a2909..29376b82 100644 --- a/tools/chatgui/src/stream_publish_engine.h +++ b/tools/chatgui/src/stream_publish_engine.h @@ -54,7 +54,7 @@ private: struct Impl; std::unique_ptr m; AudioDevice m_capture; - AudioCaptureLease m_microphone; + AudioCaptureLease m_microphone{AudioCaptureLease::Mode::Exclusive}; QTimer m_timer; bool openSource(const StreamPublishConfig& config); void closeSource();