|
|
|
|
@ -64,7 +64,7 @@ struct StreamPublishEngine::Impl {
|
|
|
|
|
std::mutex lock; |
|
|
|
|
std::condition_variable wake; |
|
|
|
|
std::thread videoThread, audioThread, joinThread; |
|
|
|
|
std::thread imageThread, imageJoinThread; |
|
|
|
|
std::unique_ptr<QThread> imageThread; |
|
|
|
|
std::atomic<bool> workersJoined{false}, videoExited{false}, audioExited{false}, imageJoined{false}; |
|
|
|
|
bool stopping = false, finSent = false, destroying = false; |
|
|
|
|
int64_t stopStarted = 0, stopReport = 0; |
|
|
|
|
@ -156,13 +156,14 @@ StreamPublishEngine::StreamPublishEngine(QObject* parent) : QObject(parent), m(s
|
|
|
|
|
m_capture.close(); // Контекст нельзя освободить до завершения фонового закрытия устройства.
|
|
|
|
|
}); |
|
|
|
|
#ifdef HAVE_STREAM_CAPTURE |
|
|
|
|
m->imageThread = std::thread([this] { |
|
|
|
|
m->imageThread.reset(QThread::create([this] { |
|
|
|
|
DEBUG_INFO(DEBUG_CATEGORY_DEBUG, "desktop image worker started tid=%llu", (unsigned long long)(quintptr)QThread::currentThreadId()); |
|
|
|
|
imageLoop(); |
|
|
|
|
m->imageProgress("release-thread-resources"); |
|
|
|
|
DEBUG_INFO(DEBUG_CATEGORY_DEBUG, "desktop image loop returned; releasing thread resources frames=%llu tid=%llu", |
|
|
|
|
(unsigned long long)m->imageFrames.load(), (unsigned long long)(quintptr)QThread::currentThreadId()); |
|
|
|
|
}); |
|
|
|
|
})); |
|
|
|
|
m->imageThread->start(); |
|
|
|
|
#else |
|
|
|
|
m->imageJoined = true; |
|
|
|
|
#endif |
|
|
|
|
@ -177,15 +178,8 @@ StreamPublishEngine::~StreamPublishEngine() {
|
|
|
|
|
#ifdef HAVE_STREAM_CAPTURE |
|
|
|
|
{ std::lock_guard<std::mutex> guard(m->lock); m->imageStop = true; m->imagePending = false; m->imageInput = {}; } |
|
|
|
|
m->imageWake.notify_all(); |
|
|
|
|
m->imageJoinThread = std::thread([this] { |
|
|
|
|
DEBUG_INFO(DEBUG_CATEGORY_DEBUG, "desktop image join begin stage=%s stage_age_ms=%lld", |
|
|
|
|
m->imageStage.load(), (long long)((monotonicUs() - m->imageStageAt.load()) / 1000)); |
|
|
|
|
m->imageThread.join(); |
|
|
|
|
DEBUG_INFO(DEBUG_CATEGORY_DEBUG, "desktop image join complete; native thread resources released"); |
|
|
|
|
m->imageJoined = true; |
|
|
|
|
}); |
|
|
|
|
#endif |
|
|
|
|
if (!finishStop() || !m->imageJoined) { |
|
|
|
|
if (!finishStop() || !finishImageStop()) { |
|
|
|
|
// Qt освобождает thread-local графику через GUI. Даже при выходе приложения нельзя блокировать его join().
|
|
|
|
|
DEBUG_INFO(DEBUG_CATEGORY_VIDEO, "desktop publisher destruction waits for workers stopping=%d image_joined=%d image_stage=%s", |
|
|
|
|
m->stopping, m->imageJoined.load(), m->imageStage.load()); |
|
|
|
|
@ -196,7 +190,8 @@ StreamPublishEngine::~StreamPublishEngine() {
|
|
|
|
|
if (firstPoll) { |
|
|
|
|
DEBUG_INFO(DEBUG_CATEGORY_DEBUG, "desktop publisher destruction GUI timer active"); firstPoll = false; |
|
|
|
|
} |
|
|
|
|
if (finishStop() && m->imageJoined) { loop.quit(); return; } |
|
|
|
|
const bool imageDone = finishImageStop(); |
|
|
|
|
if (finishStop() && imageDone) { loop.quit(); return; } |
|
|
|
|
const int64_t now = monotonicUs(); |
|
|
|
|
if (now - reportAt < 5000000) return; |
|
|
|
|
reportAt = now; |
|
|
|
|
@ -213,7 +208,6 @@ StreamPublishEngine::~StreamPublishEngine() {
|
|
|
|
|
if (m->stopping || !m->imageJoined) |
|
|
|
|
DEBUG_ERROR(DEBUG_CATEGORY_DEBUG, "desktop publisher destruction GUI loop exited before worker cleanup completed"); |
|
|
|
|
} |
|
|
|
|
if (m->imageJoinThread.joinable()) m->imageJoinThread.join(); |
|
|
|
|
DEBUG_INFO(DEBUG_CATEGORY_DEBUG, "desktop publisher destruction complete elapsed_ms=%lld", (long long)((monotonicUs() - destroyAt) / 1000)); |
|
|
|
|
} |
|
|
|
|
bool StreamPublishEngine::supported() { |
|
|
|
|
@ -664,6 +658,21 @@ bool StreamPublishEngine::finishStop() {
|
|
|
|
|
return true; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/* Проверка QThread с нулевым таймаутом сохраняет GUI event loop доступным для native cleanup. */ |
|
|
|
|
bool StreamPublishEngine::finishImageStop() { |
|
|
|
|
if (m->imageJoined) return true; |
|
|
|
|
if (!m->imageThread) { |
|
|
|
|
DEBUG_ERROR(DEBUG_CATEGORY_DEBUG, "desktop image worker missing before native thread completion"); |
|
|
|
|
return false; |
|
|
|
|
} |
|
|
|
|
if (!m->imageThread->wait(0)) return false; |
|
|
|
|
m->imageThread.reset(); |
|
|
|
|
m->imageJoined = true; |
|
|
|
|
DEBUG_INFO(DEBUG_CATEGORY_DEBUG, "desktop image native thread cleanup completed frames=%llu", |
|
|
|
|
(unsigned long long)m->imageFrames.load()); |
|
|
|
|
return true; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
uint64_t StreamPublishEngine::softwareEncodingRequest() const { |
|
|
|
|
std::lock_guard<std::mutex> guard(m->lock); return m->softwareRequest; |
|
|
|
|
} |
|
|
|
|
|