Diagnose Windows call video timing and queue losses
This commit is contained in:
1 parent
21a20da5ee
commit
02a11f7d0a
10 files changed
+292
-29
No files matched your search
@@ -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));
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user