|
|
|
|
@ -12,7 +12,6 @@
|
|
|
|
|
#include <QJsonDocument> |
|
|
|
|
#include <QEventLoop> |
|
|
|
|
#include <thread> |
|
|
|
|
#include <future> |
|
|
|
|
#include <mutex> |
|
|
|
|
#include <condition_variable> |
|
|
|
|
#include <chrono> |
|
|
|
|
@ -62,8 +61,8 @@ struct StreamPublishEngine::Impl {
|
|
|
|
|
struct Audio { int64_t pts = 0, created = 0; uint64_t sequence = 0; unsigned frames = 0; int16_t pcm[1920]{}; }; |
|
|
|
|
std::mutex lock; |
|
|
|
|
std::condition_variable wake; |
|
|
|
|
std::thread videoThread, audioThread; |
|
|
|
|
std::future<void> videoExited, audioExited; |
|
|
|
|
std::thread videoThread, audioThread, joinThread; |
|
|
|
|
std::atomic<bool> workersJoined{false}, videoExited{false}, audioExited{false}; |
|
|
|
|
bool stopping = false, destroying = false; |
|
|
|
|
int64_t stopStarted = 0, stopReport = 0; |
|
|
|
|
std::atomic<bool> running{false}, muted{false}; |
|
|
|
|
@ -415,20 +414,25 @@ bool StreamPublishEngine::start(uint64_t group, const StreamPublishConfig& confi
|
|
|
|
|
m->id = m->group = 0; m_microphone.release(); emit errorOccurred(text("Could not start the stream")); return false; |
|
|
|
|
} |
|
|
|
|
m->running = true; |
|
|
|
|
m->workersJoined = m->videoExited = m->audioExited = false; |
|
|
|
|
try { |
|
|
|
|
std::promise<void> videoDone; m->videoExited = videoDone.get_future(); |
|
|
|
|
m->videoThread = std::thread([this, config, done = std::move(videoDone)]() mutable { |
|
|
|
|
// Отдельный join ждёт и native TLS Qt; отметки выхода из функции/thread_local для этого недостаточно.
|
|
|
|
|
m->joinThread = std::thread([this] { |
|
|
|
|
{ std::unique_lock<std::mutex> lock(m->lock); m->wake.wait(lock, [this] { return !m->running; }); } |
|
|
|
|
if (m->videoThread.joinable()) m->videoThread.join(); |
|
|
|
|
m->videoExited = true; |
|
|
|
|
DEBUG_INFO(DEBUG_CATEGORY_VIDEO, "desktop stop video thread exited"); |
|
|
|
|
if (m->audioThread.joinable()) m->audioThread.join(); |
|
|
|
|
m->audioExited = true; |
|
|
|
|
DEBUG_INFO(DEBUG_CATEGORY_STREAM, "desktop stop audio thread exited"); |
|
|
|
|
m->workersJoined = true; |
|
|
|
|
}); |
|
|
|
|
m->videoThread = std::thread([this, config] { |
|
|
|
|
videoLoop(config); |
|
|
|
|
m->videoProgress("release-thread-resources"); |
|
|
|
|
DEBUG_INFO(DEBUG_CATEGORY_VIDEO, "desktop video loop returned; releasing thread resources"); |
|
|
|
|
done.set_value_at_thread_exit(); |
|
|
|
|
}); |
|
|
|
|
if (config.audio) { |
|
|
|
|
std::promise<void> audioDone; m->audioExited = audioDone.get_future(); |
|
|
|
|
m->audioThread = std::thread([this, done = std::move(audioDone)]() mutable { |
|
|
|
|
audioLoop(); done.set_value_at_thread_exit(); |
|
|
|
|
}); |
|
|
|
|
} |
|
|
|
|
if (config.audio) m->audioThread = std::thread(&StreamPublishEngine::audioLoop, this); |
|
|
|
|
} catch (const std::system_error& error) { publicationFailed(m->id, QString::fromUtf8(error.what())); return false; } |
|
|
|
|
if (!openSource(config)) { |
|
|
|
|
sourceFailed(m->generation, text("Could not open the selected capture device")); return false; |
|
|
|
|
@ -505,26 +509,18 @@ void StreamPublishEngine::stop() {
|
|
|
|
|
emit stateChanged(text("Finishing stream…")); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/* Future готов только после TLS destructors. FIN не обгоняет принятые access units; owner жив до обоих join. */ |
|
|
|
|
/* FIN не обгоняет принятые access units; owner жив до обоих join, включая native TLS destructors. */ |
|
|
|
|
bool StreamPublishEngine::finishStop() { |
|
|
|
|
if (!m->stopping) return true; |
|
|
|
|
if (monotonicUs() - m->stopReport >= 5000000) { |
|
|
|
|
m->stopReport = monotonicUs(); |
|
|
|
|
DEBUG_WARN(DEBUG_CATEGORY_VIDEO, "desktop stop still waiting elapsed_ms=%lld video_stage=%s video=%d audio=%d microphone=%d", |
|
|
|
|
(long long)((m->stopReport - m->stopStarted) / 1000), m->videoStage.load(), m->videoThread.joinable(), |
|
|
|
|
m->audioThread.joinable(), m_capture.closing()); |
|
|
|
|
} |
|
|
|
|
if (m->videoThread.joinable()) { |
|
|
|
|
if (m->videoExited.wait_for(std::chrono::seconds(0)) != std::future_status::ready) return false; |
|
|
|
|
m->videoThread.join(); |
|
|
|
|
DEBUG_INFO(DEBUG_CATEGORY_VIDEO, "desktop stop video thread exited elapsed_ms=%lld", |
|
|
|
|
(long long)((monotonicUs() - m->stopStarted) / 1000)); |
|
|
|
|
(long long)((m->stopReport - m->stopStarted) / 1000), m->videoStage.load(), !m->videoExited.load(), |
|
|
|
|
!m->audioExited.load(), m_capture.closing()); |
|
|
|
|
} |
|
|
|
|
if (m->audioThread.joinable()) { |
|
|
|
|
if (m->audioExited.wait_for(std::chrono::seconds(0)) != std::future_status::ready) return false; |
|
|
|
|
m->audioThread.join(); |
|
|
|
|
DEBUG_INFO(DEBUG_CATEGORY_STREAM, "desktop stop audio thread exited elapsed_ms=%lld", |
|
|
|
|
(long long)((monotonicUs() - m->stopStarted) / 1000)); |
|
|
|
|
if (m->joinThread.joinable()) { |
|
|
|
|
if (!m->workersJoined) return false; |
|
|
|
|
m->joinThread.join(); |
|
|
|
|
} |
|
|
|
|
if (m_capture.closing()) return false; |
|
|
|
|
m_microphone.release(); m->stopping = false; |
|
|
|
|
|