Document sequential presentation diagnostics and simplify GUI frame ownership

This commit is contained in:
evgeny committed 2026-10-19 17:39:54 +02:00
1 parent 4ed2c4bbab
commit a9b91145a6
3 files changed
+81 -92

No files matched your search

+19 -28
View File
@@ -32,17 +32,14 @@ struct CallVideoEngine::Impl {
QByteArray cameraId;
bool captureActive = false;
uint64_t captured = 0;
QImage shown;
int64_t shownPts = -1;
uint64_t publicationEpoch = 0;
bool publicationPending = false;
bool presentationDriven = false;
uint64_t publicationSerial = 0;
unsigned publicationOverwritten = 0; // Только под lock: выбранные кадры, заменённые до GUI callback.
int64_t publicationQueuedAt = 0;
int64_t shownAt = 0;
int64_t paintWaitMax = 0; // Только под lock: время до подтверждения рисования.
int64_t uiReportAt = 0, uiLastAt = 0, uiWaitMax = 0, uiGapMax = 0; // Только GUI-поток.
unsigned uiPublished = 0, uiOverwritten = 0;
unsigned uiPublished = 0;
int64_t previewReportAt = 0, previewMaxUs = 0;
unsigned previewFrames = 0;
QString status;
@@ -95,56 +92,50 @@ void CallVideoEngine::setPresentationDriven(bool enabled) {
}
void CallVideoEngine::imagePresented(quint64 serial) {
{ std::lock_guard<std::mutex> guard(m->lock);
if (serial != m->publicationSerial) return;
if (!m->publicationPending || serial != m->publicationSerial) return;
m->paintWaitMax = std::max(m->paintWaitMax, nowUs() - m->publicationQueuedAt);
m->publicationPending = false; }
m->wake.notify_all();
}
// Только один кадр до подтверждения рисования: GUI не заменяет ещё не показанное изображение.
void CallVideoEngine::publishImage(const QImage& image, int64_t pts) {
uint64_t epoch, serial;
uint64_t epoch, serial; int64_t queued;
{ std::unique_lock<std::mutex> guard(m->lock);
m->wake.wait(guard, [this] { return !m->running || !m->publicationPending; });
if (!m->running) return;
m->shown = image; m->shownPts = pts; m->shownAt = nowUs();
m->publicationPending = true; m->publicationQueuedAt = m->shownAt; epoch = m->publicationEpoch;
m->publicationPending = true; queued = m->publicationQueuedAt = nowUs(); epoch = m->publicationEpoch;
serial = ++m->publicationSerial; }
QMetaObject::invokeMethod(this, [this, epoch, serial] {
QImage image; int64_t pts, selected, queued; unsigned overwritten;
QMetaObject::invokeMethod(this, [this, epoch, serial, image, pts, queued] {
{ std::lock_guard<std::mutex> guard(m->lock);
if (epoch != m->publicationEpoch) return;
image = m->shown; pts = m->shownPts; selected = m->shownAt; queued = m->publicationQueuedAt;
overwritten = m->publicationOverwritten; m->publicationOverwritten = 0; }
}
const int64_t now = nowUs(), wait = now - queued;
++m->uiPublished; m->uiWaitMax = std::max(m->uiWaitMax, wait);
if (m->uiLastAt) m->uiGapMax = std::max(m->uiGapMax, now - m->uiLastAt);
m->uiLastAt = now;
if (overwritten && !m->uiOverwritten)
DEBUG_WARN(DEBUG_CATEGORY_CALL, "video GUI coalesced call=%llu overwritten=%u notification_wait_us=%lld "
"selected_age_us=%lld pts=%lld mono_us=%lld", (unsigned long long)m->id, overwritten,
(long long)wait, (long long)(now - selected), (long long)pts, (long long)now);
m->uiOverwritten += overwritten;
emit imageChanged(image, serial);
bool driven;
{ std::lock_guard<std::mutex> guard(m->lock); driven = m->presentationDriven; }
if (!driven || image.isNull()) imagePresented(serial);
if (!m->uiReportAt) m->uiReportAt = now;
if (now - m->uiReportAt >= 1000000) {
DEBUG_DEBUG(DEBUG_CATEGORY_CALL, "video GUI delivery call=%llu interval_us=%lld published=%u overwritten=%u "
"notification_wait_max_us=%lld delivery_gap_max_us=%lld mono_us=%lld",
(unsigned long long)m->id, (long long)(now - m->uiReportAt), m->uiPublished, m->uiOverwritten,
(long long)m->uiWaitMax, (long long)m->uiGapMax, (long long)now);
m->uiReportAt = now; m->uiPublished = m->uiOverwritten = 0; m->uiWaitMax = m->uiGapMax = 0;
int64_t paintWait;
{ std::lock_guard<std::mutex> guard(m->lock); paintWait = m->paintWaitMax; m->paintWaitMax = 0; }
DEBUG_DEBUG(DEBUG_CATEGORY_CALL, "video GUI delivery call=%llu interval_us=%lld published=%u "
"notification_wait_max_us=%lld delivery_gap_max_us=%lld paint_wait_max_us=%lld mono_us=%lld",
(unsigned long long)m->id, (long long)(now - m->uiReportAt), m->uiPublished,
(long long)m->uiWaitMax, (long long)m->uiGapMax, (long long)paintWait, (long long)now);
m->uiReportAt = now; m->uiPublished = 0; m->uiWaitMax = m->uiGapMax = 0;
}
if (!image.isNull() && pts >= 0 && pts % 1000000 < 34000)
DEBUG_DEBUG(DEBUG_CATEGORY_CALL, "video timing stage=publish call=%llu pts=%lld mono_us=%lld ui_wait_us=%lld "
"selected_age_us=%lld overwritten=%u", (unsigned long long)m->id, (long long)pts,
(long long)now, (long long)wait, (long long)(now - selected), overwritten);
DEBUG_DEBUG(DEBUG_CATEGORY_CALL, "video timing stage=publish call=%llu pts=%lld mono_us=%lld ui_wait_us=%lld",
(unsigned long long)m->id, (long long)pts, (long long)now, (long long)wait);
}, Qt::QueuedConnection);
}
void CallVideoEngine::start(quint64 id) {
if (m->running && m->id == id) return;
stop(); m->id = id; m->running = true; m_timer.start();
m->uiReportAt = m->uiLastAt = m->uiWaitMax = m->uiGapMax = 0; m->uiPublished = m->uiOverwritten = 0;
m->uiReportAt = m->uiLastAt = m->uiWaitMax = m->uiGapMax = 0; m->uiPublished = 0;
try { m->tx = std::thread(&CallVideoEngine::transmit, this); m->rx = std::thread(&CallVideoEngine::receive, this); }
catch (const std::exception& error) { DEBUG_ERROR(DEBUG_CATEGORY_CALL, "video worker start: %s", error.what()); stop(); emit statusChanged(error.what()); }
}
@@ -163,7 +154,7 @@ void CallVideoEngine::stop() {
if (m->rx.joinable()) m->rx.join();
if (m->id) call_video_port_close(m->id);
{ std::lock_guard<std::mutex> guard(m->lock);
++m->publicationEpoch; m->publicationPending = false; m->publicationOverwritten = 0; m->shown = {}; m->shownPts = -1; }
++m->publicationEpoch; m->publicationPending = false; m->paintWaitMax = 0; }
m->id = 0; m->enabled = false; emit imageChanged({}, 0);
}
void CallVideoEngine::setVideo(bool enabled, const QByteArray& cameraId, int width, int height, int codec) {