Diagnose Windows call video timing and queue losses

This commit is contained in:
evgeny committed 2026-10-19 17:20:12 +02:00
1 parent 21a20da5ee
commit 02a11f7d0a
10 files changed
+292 -29

No files matched your search

+54 -10
View File
@@ -36,7 +36,11 @@ struct CallVideoEngine::Impl {
int64_t shownPts = -1;
uint64_t publicationEpoch = 0;
bool publicationPending = false;
unsigned publicationOverwritten = 0; // Только под lock: выбранные кадры, заменённые до GUI callback.
int64_t publicationQueuedAt = 0;
int64_t shownAt = 0;
int64_t uiReportAt = 0, uiLastAt = 0, uiWaitMax = 0, uiGapMax = 0; // Только GUI-поток.
unsigned uiPublished = 0, uiOverwritten = 0;
int64_t previewReportAt = 0, previewMaxUs = 0;
unsigned previewFrames = 0;
QString status;
@@ -89,22 +93,42 @@ void CallVideoEngine::publishImage(const QImage& image, int64_t pts) {
uint64_t epoch;
{ std::lock_guard<std::mutex> guard(m->lock);
m->shown = image; m->shownPts = pts; m->shownAt = nowUs();
if (m->publicationPending) return;
m->publicationPending = true; epoch = m->publicationEpoch; }
if (m->publicationPending) { ++m->publicationOverwritten; return; }
m->publicationPending = true; m->publicationQueuedAt = m->shownAt; epoch = m->publicationEpoch; }
QMetaObject::invokeMethod(this, [this, epoch] {
QImage image; int64_t pts, selected;
QImage image; int64_t pts, selected, queued; unsigned overwritten;
{ std::lock_guard<std::mutex> guard(m->lock);
if (epoch != m->publicationEpoch) return;
image = m->shown; pts = m->shownPts; selected = m->shownAt; m->publicationPending = false; }
image = m->shown; pts = m->shownPts; selected = m->shownAt; queued = m->publicationQueuedAt;
overwritten = m->publicationOverwritten; m->publicationOverwritten = 0; m->publicationPending = false; }
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);
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;
}
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",
(unsigned long long)m->id, (long long)pts, (long long)nowUs(), (long long)(nowUs() - selected));
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);
}, 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;
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()); }
}
@@ -122,7 +146,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->shown = {}; m->shownPts = -1; }
++m->publicationEpoch; m->publicationPending = false; m->publicationOverwritten = 0; m->shown = {}; m->shownPts = -1; }
m->id = 0; m->enabled = false; emit imageChanged({});
}
void CallVideoEngine::setVideo(bool enabled, const QByteArray& cameraId, int width, int height, int codec) {
@@ -294,8 +318,11 @@ void CallVideoEngine::receive() {
CallVideoDecoder decoder;
call_video_playout playout; call_video_playout_init(&playout, m->id);
int64_t lastPictureAt = 0;
int64_t reportAt = nowUs(), decodeMax = 0, rxAgeMax = 0, wakeLateMax = 0, requestedWake = 0;
unsigned decoded = 0;
uint64_t reportedGeneration = 0, rejectedGeneration = 0;
while (m->running) {
if (requestedWake) wakeLateMax = std::max(wakeLateMax, nowUs() - requestedWake);
// Один кадр ждёт своего срока, второй позволяет пропустить устаревший показ после паузы CPU.
while (pictures.size() < 2 && m->running) {
call_video_frame packet{};
@@ -304,12 +331,16 @@ void CallVideoEngine::receive() {
QImage image;
const int64_t decodeStarted = nowUs();
if (decoder.decode(packet, image)) {
const int64_t decodedAt = nowUs();
++decoded; decodeMax = std::max(decodeMax, decodedAt - decodeStarted);
rxAgeMax = std::max(rxAgeMax, decodedAt - packet.received_us);
pictures.push_back({{packet.pts_us, packet.received_us}, image});
if (packet.pts_us % 1000000 < 34000)
DEBUG_DEBUG(DEBUG_CATEGORY_CALL,
"video timing stage=decode call=%llu frame=%llu pts=%lld mono_us=%lld decode_us=%lld queue=%zu",
"video timing stage=decode call=%llu frame=%llu pts=%lld mono_us=%lld decode_us=%lld rx_age_us=%lld queue=%zu",
(unsigned long long)m->id, (unsigned long long)packet.frame_id, (long long)packet.pts_us,
(long long)nowUs(), (long long)(nowUs() - decodeStarted), pictures.size());
(long long)decodedAt, (long long)(decodedAt - decodeStarted),
(long long)(decodedAt - packet.received_us), pictures.size());
if (reportedGeneration != packet.format.generation) {
reportedGeneration = packet.format.generation; call_video_port_decoded(m->id, reportedGeneration, 1);
}
@@ -321,6 +352,15 @@ void CallVideoEngine::receive() {
}
int64_t now = nowUs(); call_clock_snapshot clock{};
call_audio_get_clock(m->id, now, &clock);
if (now - reportAt >= 1000000) {
DEBUG_DEBUG(DEBUG_CATEGORY_CALL, "video RX worker call=%llu interval_us=%lld decoded=%u decode_max_us=%lld "
"rx_to_decode_max_us=%lld wake_late_max_us=%lld ready=%zu audio_valid=%d audio_measured=%d "
"audio_pts=%lld played=%llu written=%llu mono_us=%lld",
(unsigned long long)m->id, (long long)(now - reportAt), decoded, (long long)decodeMax, (long long)rxAgeMax,
(long long)wakeLateMax, pictures.size(), clock.valid, clock.measured, (long long)clock.pts_us,
(unsigned long long)clock.played_samples, (unsigned long long)clock.written_samples, (long long)now);
reportAt = now; decoded = 0; decodeMax = rxAgeMax = wakeLateMax = 0;
}
auto action = call_video_playout_poll(&playout, pictures.empty() ? nullptr : &pictures.front().timing,
pictures.size() > 1 ? &pictures[1].timing : nullptr, now, clock.valid, clock.pts_us);
if (action != CALL_VIDEO_PLAYOUT_WAIT) {
@@ -331,6 +371,7 @@ void CallVideoEngine::receive() {
(unsigned long long)m->id, (long long)f.timing.pts_us, (long long)now, (long long)clock.pts_us);
}
pictures.pop_front();
requestedWake = 0;
continue;
}
// VIDEO_OFF пока отсутствует в протоколе: убираем застывшую картинку после прекращения показа.
@@ -338,6 +379,9 @@ void CallVideoEngine::receive() {
publishImage({}, -1); lastPictureAt = 0;
DEBUG_INFO(DEBUG_CATEGORY_CALL, "video playback hidden: no playable frame for 5s");
}
std::this_thread::sleep_for(std::chrono::microseconds(std::max<int64_t>(100, std::min<int64_t>(5000, playout.deadline_us - nowUs()))));
const int64_t sleepAt = nowUs();
const int64_t waitUs = std::max<int64_t>(100, std::min<int64_t>(5000, playout.deadline_us - sleepAt));
requestedWake = sleepAt + waitUs;
std::this_thread::sleep_for(std::chrono::microseconds(waitUs));
}
}