Trace stream image worker and GUI shutdown waits
This commit is contained in:
1 parent
a63af43e59
commit
1e21912dc6
2 files changed
+57
-5
No files matched your search
@@ -12,6 +12,7 @@
|
||||
#include <QJsonArray>
|
||||
#include <QJsonDocument>
|
||||
#include <QEventLoop>
|
||||
#include <QScopeGuard>
|
||||
#include <thread>
|
||||
#include <mutex>
|
||||
#include <condition_variable>
|
||||
@@ -86,10 +87,16 @@ struct StreamPublishEngine::Impl {
|
||||
int64_t captureReport = 0;
|
||||
std::atomic<const char*> videoStage{"stopped"};
|
||||
std::atomic<int64_t> videoStageAt{0};
|
||||
std::atomic<const char*> imageStage{"starting"};
|
||||
std::atomic<int64_t> imageStageAt{monotonicUs()};
|
||||
std::atomic<uint64_t> imageFrames{0};
|
||||
std::atomic<uint64_t> convertedFrames{0}, encodedFrames{0}, deliveredFrames{0}, repeatedFrames{0};
|
||||
void videoProgress(const char* stage) {
|
||||
videoStageAt.store(monotonicUs()); videoStage.store(stage);
|
||||
}
|
||||
void imageProgress(const char* stage) {
|
||||
imageStageAt.store(monotonicUs()); imageStage.store(stage);
|
||||
}
|
||||
bool hasGood = false, rollingBack = false, videoReady = false;
|
||||
uint64_t softwareRequest = 0;
|
||||
int softwareCodec = 0;
|
||||
@@ -149,28 +156,65 @@ StreamPublishEngine::StreamPublishEngine(QObject* parent) : QObject(parent), m(s
|
||||
m_capture.close(); // Контекст нельзя освободить до завершения фонового закрытия устройства.
|
||||
});
|
||||
#ifdef HAVE_STREAM_CAPTURE
|
||||
m->imageThread = std::thread(&StreamPublishEngine::imageLoop, this);
|
||||
m->imageThread = std::thread([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());
|
||||
});
|
||||
#else
|
||||
m->imageJoined = true;
|
||||
#endif
|
||||
}
|
||||
StreamPublishEngine::~StreamPublishEngine() {
|
||||
const int64_t destroyAt = monotonicUs();
|
||||
DEBUG_INFO(DEBUG_CATEGORY_DEBUG, "desktop publisher destruction begin id=%llu running=%d stopping=%d image_stage=%s image_frames=%llu",
|
||||
(unsigned long long)m->id, m->running.load(), m->stopping, m->imageStage.load(), (unsigned long long)m->imageFrames.load());
|
||||
m->destroying = true; blockSignals(true);
|
||||
stop(); m_timer.stop(); m_previewTimer.stop();
|
||||
closeSource();
|
||||
#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] { m->imageThread.join(); m->imageJoined = true; });
|
||||
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) {
|
||||
// Qt освобождает thread-local графику через GUI. Даже при выходе приложения нельзя блокировать его join().
|
||||
DEBUG_INFO(DEBUG_CATEGORY_VIDEO, "desktop publisher destruction waits for worker resource cleanup with GUI events enabled");
|
||||
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());
|
||||
QEventLoop loop; QTimer timer;
|
||||
connect(&timer, &QTimer::timeout, &loop, [this, &loop] { if (finishStop() && m->imageJoined) loop.quit(); });
|
||||
timer.start(10); loop.exec(QEventLoop::ExcludeUserInputEvents);
|
||||
int64_t reportAt = destroyAt;
|
||||
bool firstPoll = true;
|
||||
connect(&timer, &QTimer::timeout, &loop, [this, &loop, destroyAt, &reportAt, &firstPoll] {
|
||||
if (firstPoll) {
|
||||
DEBUG_INFO(DEBUG_CATEGORY_DEBUG, "desktop publisher destruction GUI timer active"); firstPoll = false;
|
||||
}
|
||||
if (finishStop() && m->imageJoined) { loop.quit(); return; }
|
||||
const int64_t now = monotonicUs();
|
||||
if (now - reportAt < 5000000) return;
|
||||
reportAt = now;
|
||||
DEBUG_WARN(DEBUG_CATEGORY_DEBUG, "desktop publisher destruction waiting elapsed_ms=%lld stopping=%d workers_joined=%d "
|
||||
"video_stage=%s image_joined=%d image_stage=%s image_stage_age_ms=%lld image_frames=%llu microphone_closing=%d",
|
||||
(long long)((now - destroyAt) / 1000), m->stopping, m->workersJoined.load(), m->videoStage.load(),
|
||||
m->imageJoined.load(), m->imageStage.load(), (long long)((now - m->imageStageAt.load()) / 1000),
|
||||
(unsigned long long)m->imageFrames.load(), m_capture.closing());
|
||||
});
|
||||
timer.start(10);
|
||||
const int result = loop.exec(QEventLoop::ExcludeUserInputEvents);
|
||||
DEBUG_INFO(DEBUG_CATEGORY_DEBUG, "desktop publisher destruction GUI loop exited result=%d stopping=%d image_joined=%d",
|
||||
result, m->stopping, m->imageJoined.load());
|
||||
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() {
|
||||
#ifdef HAVE_STREAM_CAPTURE
|
||||
@@ -433,6 +477,7 @@ void StreamPublishEngine::imageLoop() {
|
||||
#ifdef HAVE_STREAM_CAPTURE
|
||||
for (;;) {
|
||||
QVideoFrame frame; uint64_t generation; int64_t pts;
|
||||
m->imageProgress("wait-frame");
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(m->lock);
|
||||
m->imageWake.wait(lock, [this] { return m->imageStop || m->imagePending; });
|
||||
@@ -440,7 +485,12 @@ void StreamPublishEngine::imageLoop() {
|
||||
frame = std::move(m->imageInput); generation = m->imageGeneration; pts = m->imagePts; m->imagePending = false;
|
||||
}
|
||||
int64_t convertAt = monotonicUs();
|
||||
m->imageProgress("convert-frame");
|
||||
QImage image = frame.toImage();
|
||||
// Отметка ставится перед деструкторами image/frame, включая return/continue внутри итерации.
|
||||
auto releaseFrame = qScopeGuard([this] { m->imageProgress("release-frame"); });
|
||||
m->imageFrames++;
|
||||
m->imageProgress("transform-image");
|
||||
if (image.isNull()) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_VIDEO, "desktop captured frame conversion failed generation=%llu format=%d size=%dx%d",
|
||||
(unsigned long long)generation, frame.pixelFormat(), frame.width(), frame.height());
|
||||
@@ -453,6 +503,7 @@ void StreamPublishEngine::imageLoop() {
|
||||
#else
|
||||
if (frame.rotationAngle()) image = image.transformed(QTransform().rotate(frame.rotationAngle()));
|
||||
#endif
|
||||
m->imageProgress("publish-image");
|
||||
{
|
||||
std::lock_guard<std::mutex> guard(m->lock);
|
||||
if (m->imageStop) return;
|
||||
|
||||
@@ -162,6 +162,7 @@ private slots:
|
||||
debug_config_init(); debug_set_level(DEBUG_LEVEL_WARN); debug_set_category_level(DEBUG_CATEGORY_STREAM, DEBUG_LEVEL_DEBUG);
|
||||
debug_set_category_level(DEBUG_CATEGORY_SYS, DEBUG_LEVEL_ERROR);
|
||||
debug_set_category_level(DEBUG_CATEGORY_VIDEO, DEBUG_LEVEL_DEBUG);
|
||||
debug_set_category_level(DEBUG_CATEGORY_DEBUG, DEBUG_LEVEL_INFO);
|
||||
if (qEnvironmentVariableIsSet("UTUN_TEST_SCREEN_CAPTURE")) {
|
||||
qunsetenv("WAYLAND_DISPLAY"); qputenv("XDG_SESSION_TYPE", "x11");
|
||||
QLoggingCategory::setFilterRules("qt.multimedia.*=true");
|
||||
|
||||
Reference in new issue
Block a user