Browse Source

Close failed stream capture without blocking the GUI

master
evgeny 2 days ago
parent
commit
3ba7601ba4
  1. 33
      tools/chatgui/src/audio_capture.cpp
  2. 7
      tools/chatgui/src/audio_capture.h

33
tools/chatgui/src/audio_capture.cpp

@ -98,6 +98,7 @@ AudioCapture::~AudioCapture() { close(); }
bool AudioCapture::open(const QString& input, Role role, int channels, uint64_t tag, Ready ready,
std::function<void()> stopped, Callback callback) {
if (closing()) { DEBUG_WARN(DEBUG_CATEGORY_CALL, "capture: previous device is still closing"); return false; }
close();
static std::vector<std::weak_ptr<Session>> sessions; // Только GUI; владение у подписчиков.
QString key = input;
@ -129,13 +130,39 @@ bool AudioCapture::open(const QString& input, Role role, int channels, uint64_t
return running();
}
void AudioCapture::close() {
if (!m_session) return;
std::shared_ptr<AudioCapture::Session> AudioCapture::detach() {
if (!m_session) return {};
auto& slot = m_session->consumers[m_role == Role::Stream ? 0 : 1];
m_session->detach(slot); slot.client = nullptr;
DEBUG_INFO(DEBUG_CATEGORY_CALL, "capture: detached role=%s remaining=%d", m_role == Role::Stream ? "stream" : "radio",
int(m_session->consumers[0].client != nullptr) + int(m_session->consumers[1].client != nullptr));
m_session.reset(); m_callback = {}; m_ready = {}; m_stopped = {};
m_callback = {}; m_ready = {}; m_stopped = {};
return std::move(m_session);
}
void AudioCapture::close() {
if (m_closeThread.joinable()) m_closeThread.join();
detach().reset();
}
/* Callback отсоединён в GUI; session и контекст должны жить до завершения ma_device_uninit. */
void AudioCapture::closeAsync() {
if (closing()) return;
if (m_closeThread.joinable()) m_closeThread.join();
auto session = detach();
if (!session) return;
m_closeDone.store(false, std::memory_order_release);
DEBUG_INFO(DEBUG_CATEGORY_CALL, "capture: asynchronous close started shared=%d", session.use_count() > 1);
try {
m_closeThread = std::thread([this, session = std::move(session)]() mutable {
session.reset();
DEBUG_INFO(DEBUG_CATEGORY_CALL, "capture: asynchronous close completed");
m_closeDone.store(true, std::memory_order_release);
});
} catch (const std::system_error& error) {
DEBUG_ERROR(DEBUG_CATEGORY_CALL, "capture: close worker failed: %s", error.what());
m_closeDone.store(true, std::memory_order_release);
}
}
void AudioCapture::recover() { if (m_session) m_session->recover(); }

7
tools/chatgui/src/audio_capture.h

@ -3,6 +3,8 @@
#include <cstdint>
#include <functional>
#include <memory>
#include <atomic>
#include <thread>
/* GUI владеет подпиской, сессия — устройством. Одинаковый выбранный вход открывается один раз.
* Профиль трансляции имеет приоритет и сохраняется для рации после её остановки.
@ -28,12 +30,17 @@ public:
bool open(const QString& input, Role role, int channels, uint64_t tag, Ready ready,
std::function<void()> stopped, Callback callback);
void close();
void closeAsync(); // GUI отсоединяет подписчика; последний backend закрывается в worker.
bool closing() const { return !m_closeDone.load(std::memory_order_acquire); }
void recover();
bool subscribed() const { return bool(m_session); }
bool running() const;
private:
struct Session;
std::shared_ptr<Session> detach();
std::shared_ptr<Session> m_session;
std::thread m_closeThread;
std::atomic<bool> m_closeDone{true};
Ready m_ready;
std::function<void()> m_stopped;
Callback m_callback;

Loading…
Cancel
Save