Диагностика задержек программного видео на desktop

This commit is contained in:
evgeny committed 2026-10-18 16:17:51 +02:00
1 parent 03ff431d78
commit 5a55bcb500
3 files changed
+51 -11

No files matched your search

+11 -4
View File
@@ -86,14 +86,21 @@ bool CallVideoEncoder::encode(const QImage& image, uint64_t pts, bool forceKey,
auto replacement = m_prepared.get(); auto& candidate = replacement.encoder; auto replacement = m_prepared.get(); auto& candidate = replacement.encoder;
QElapsedTimer timer; timer.start(); QElapsedTimer timer; timer.start();
bool ready = candidate && candidate->encode(image, pts, true, frames) bool ready = candidate && candidate->encode(image, pts, true, frames)
&& frames.size() == 1 && frames.front().key && frames.front().pts == pts && frames.size() == 1 && frames.front().key && frames.front().pts == pts;
&& !call_video_check_key(&m_format, reinterpret_cast<const uint8_t*>(frames.front().data.constData()), frames.front().data.size()); const qint64 encodedUs = timer.nsecsElapsed() / 1000;
if (ready) ready = !call_video_check_key(&m_format,
reinterpret_cast<const uint8_t*>(frames.front().data.constData()), frames.front().data.size());
const qint64 verifiedUs = timer.nsecsElapsed() / 1000;
if (ready) { if (ready) {
m_encoder = std::move(candidate); m_config = next; completion.bitrate = m_pendingBitrate; m_encoder = std::move(candidate); m_config = next; completion.bitrate = m_pendingBitrate;
const qint64 committedUs = timer.nsecsElapsed() / 1000;
DEBUG_INFO(DEBUG_CATEGORY_CALL, DEBUG_INFO(DEBUG_CATEGORY_CALL,
"call video bitrate committed generation=%llu request=%llu bitrate=%u pts=%llu background_ms=%lld commit_ms=%lld bytes=%lld", "call video bitrate committed generation=%llu request=%llu bitrate=%u pts=%llu background_ms=%lld "
"commit_ms=%lld encode_us=%lld verify_us=%lld retire_us=%lld bytes=%lld",
(unsigned long long)m_format.generation, (unsigned long long)m_request, completion.bitrate, (unsigned long long)m_format.generation, (unsigned long long)m_request, completion.bitrate,
(unsigned long long)pts, (long long)replacement.elapsed, (long long)timer.elapsed(), (long long)frames.front().data.size()); (unsigned long long)pts, (long long)replacement.elapsed, (long long)(committedUs / 1000),
(long long)encodedUs, (long long)(verifiedUs - encodedUs), (long long)(committedUs - verifiedUs),
(long long)frames.front().data.size());
} else { } else {
m_error = candidate ? candidate->error() : replacement.error; m_error = candidate ? candidate->error() : replacement.error;
if (m_error.isEmpty()) m_error = "Replacement encoder did not produce a compatible immediate IDR"; if (m_error.isEmpty()) m_error = "Replacement encoder did not produce a compatible immediate IDR";
+10 -7
View File
@@ -165,9 +165,9 @@ void CallVideoEngine::transmit() {
unsigned excluded = 0; unsigned excluded = 0;
int64_t lastPts = -1; int64_t lastPts = -1;
bool forceKey = true; bool forceKey = true;
int64_t reportAt = nowUs(), encodeMax = 0; int64_t reportAt = nowUs(), encodeMax = 0, inputAgeMax = 0;
uint64_t captureBase = 0; uint64_t captureBase = 0;
unsigned encodedCount = 0, keyCount = 0, sendBusy = 0, paced = 0; unsigned encodedCount = 0, keyCount = 0, sendBusy = 0, paced = 0, expired = 0;
CallVideoCadence cadence; CallVideoCadence cadence;
uint64_t wakeSerial = 0; uint64_t wakeSerial = 0;
auto idle = [this, &wakeSerial] { auto idle = [this, &wakeSerial] {
@@ -187,13 +187,14 @@ void CallVideoEngine::transmit() {
} }
int64_t now = nowUs(); int64_t now = nowUs();
if (now - reportAt >= 1000000) { if (now - reportAt >= 1000000) {
DEBUG_DEBUG(DEBUG_CATEGORY_CALL, DEBUG_DEBUG(DEBUG_CATEGORY_DEBUG,
"video capture flow call=%llu interval_us=%lld captured=%llu encoded=%u keys=%u send_busy=%u paced=%u " "video capture flow call=%llu interval_us=%lld captured=%llu encoded=%u keys=%u send_busy=%u paced=%u "
"encode_max_us=%lld capture_age_us=%lld pts=%lld clock_correction_us=%lld", "expired=%u encode_max_us=%lld input_age_max_us=%lld capture_age_us=%lld pts=%lld clock_correction_us=%lld",
(unsigned long long)m->id, (long long)(now - reportAt), (unsigned long long)(captured - captureBase), (unsigned long long)m->id, (long long)(now - reportAt), (unsigned long long)(captured - captureBase),
encodedCount, keyCount, sendBusy, paced, (long long)encodeMax, encodedCount, keyCount, sendBusy, paced, expired, (long long)encodeMax, (long long)inputAgeMax,
(long long)(pts >= 0 ? now - pts : 0), (long long)pts, (long long)clockCorrection); (long long)(pts >= 0 ? now - pts : 0), (long long)pts, (long long)clockCorrection);
reportAt = now; captureBase = captured; encodedCount = keyCount = sendBusy = paced = 0; encodeMax = 0; reportAt = now; captureBase = captured; encodedCount = keyCount = sendBusy = paced = expired = 0;
encodeMax = inputAgeMax = 0;
} }
if (revision != userRevision) { userRevision = revision; excluded = 0; } if (revision != userRevision) { userRevision = revision; excluded = 0; }
if (format.generation && state.decoded_generation == format.generation && !state.decoded_accepted if (format.generation && state.decoded_generation == format.generation && !state.decoded_accepted
@@ -238,10 +239,11 @@ void CallVideoEngine::transmit() {
if (!cadence.accept(pts)) { lastPts = pts; ++paced; idle(); continue; } if (!cadence.accept(pts)) { lastPts = pts; ++paced; idle(); continue; }
if (image.isNull() || nowUs() - pts > CallCameraMaxFrameAgeUs) { if (image.isNull() || nowUs() - pts > CallCameraMaxFrameAgeUs) {
DEBUG_WARN(DEBUG_CATEGORY_CALL, "video TX skips old capture age_us=%lld", (long long)(nowUs() - pts)); DEBUG_WARN(DEBUG_CATEGORY_CALL, "video TX skips old capture age_us=%lld", (long long)(nowUs() - pts));
lastPts = pts; continue; lastPts = pts; ++expired; continue;
} }
LiveEncodedFrame output; CallVideoRateCompletion completion; LiveEncodedFrame output; CallVideoRateCompletion completion;
int64_t encodeStarted = nowUs(); int64_t encodeStarted = nowUs();
inputAgeMax = std::max(inputAgeMax, encodeStarted - pts);
bool encoded = encoder.encode(image, pts, forceKey, output, completion); lastPts = pts; bool encoded = encoder.encode(image, pts, forceKey, output, completion); lastPts = pts;
encodeMax = std::max(encodeMax, nowUs() - encodeStarted); encodeMax = std::max(encodeMax, nowUs() - encodeStarted);
if (completion.request) { if (completion.request) {
@@ -259,6 +261,7 @@ void CallVideoEngine::transmit() {
if (!m->running || !m->enabled || m->revision != revision || nowUs() - pts > CallCameraMaxFrameAgeUs) { if (!m->running || !m->enabled || m->revision != revision || nowUs() - pts > CallCameraMaxFrameAgeUs) {
DEBUG_WARN(DEBUG_CATEGORY_CALL, "video TX discards obsolete encoded frame revision=%llu/%llu enabled=%d age_us=%lld", DEBUG_WARN(DEBUG_CATEGORY_CALL, "video TX discards obsolete encoded frame revision=%llu/%llu enabled=%d age_us=%lld",
(unsigned long long)revision, (unsigned long long)m->revision, m->enabled, (long long)(nowUs() - pts)); (unsigned long long)revision, (unsigned long long)m->revision, m->enabled, (long long)(nowUs() - pts));
++expired;
forceKey = true; continue; forceKey = true; continue;
} }
forceKey = call_video_port_send(&packet) != 1; } forceKey = call_video_port_send(&packet) != 1; }
+30
View File
@@ -1,5 +1,6 @@
#include "live_video_encoder.h" #include "live_video_encoder.h"
#include "debug_config.h" #include "debug_config.h"
#include <QElapsedTimer>
#include <QPainter> #include <QPainter>
#include <algorithm> #include <algorithm>
#include <cstring> #include <cstring>
@@ -18,6 +19,9 @@ struct LiveVideoEncoder::Impl {
QString error, name; QString error, name;
LiveVideoConfig config; LiveVideoConfig config;
int64_t submittedFrames = 0; int64_t submittedFrames = 0;
QElapsedTimer reportTimer;
unsigned inputs = 0, outputs = 0, keys = 0;
qint64 prepareMax = 0, submitMax = 0, drainMax = 0, keyMax = 0, deltaMax = 0;
#ifdef HAVE_FFMPEG #ifdef HAVE_FFMPEG
AVCodecContext* codec = nullptr; AVCodecContext* codec = nullptr;
AVBSFContext* filter = nullptr; AVBSFContext* filter = nullptr;
@@ -297,6 +301,9 @@ bool LiveVideoEncoder::encode(const QImage& image, uint64_t pts, bool key, QList
if (!m->codec || !m->frame || image.isNull() || pts > INT64_MAX) { if (!m->codec || !m->frame || image.isNull() || pts > INT64_MAX) {
m->error = "Invalid video frame"; DEBUG_ERROR(DEBUG_CATEGORY_VIDEO, "desktop encode invalid frame/state"); return false; m->error = "Invalid video frame"; DEBUG_ERROR(DEBUG_CATEGORY_VIDEO, "desktop encode invalid frame/state"); return false;
} }
QElapsedTimer timer; timer.start();
if (!m->reportTimer.isValid()) m->reportTimer.start();
const auto outputBase = output.size();
QImage canvas(m->config.width, m->config.height, QImage::Format_RGB32); canvas.fill(Qt::black); QImage canvas(m->config.width, m->config.height, QImage::Format_RGB32); canvas.fill(Qt::black);
QSize fitted = image.size().scaled(canvas.size(), Qt::KeepAspectRatio); QSize fitted = image.size().scaled(canvas.size(), Qt::KeepAspectRatio);
{ QPainter painter(&canvas); painter.drawImage(QRect(QPoint((canvas.width() - fitted.width()) / 2, { QPainter painter(&canvas); painter.drawImage(QRect(QPoint((canvas.width() - fitted.width()) / 2,
@@ -325,7 +332,9 @@ bool LiveVideoEncoder::encode(const QImage& image, uint64_t pts, bool key, QList
if (result >= 0) result = av_hwframe_transfer_data(hardware, m->frame, 0); if (result >= 0) result = av_hwframe_transfer_data(hardware, m->frame, 0);
if (result >= 0) result = av_frame_copy_props(hardware, m->frame); if (result >= 0) result = av_frame_copy_props(hardware, m->frame);
} }
const qint64 preparedUs = timer.nsecsElapsed() / 1000;
if (result >= 0) result = avcodec_send_frame(m->codec, hardware ? hardware : m->frame); if (result >= 0) result = avcodec_send_frame(m->codec, hardware ? hardware : m->frame);
const qint64 submittedUs = timer.nsecsElapsed() / 1000;
if (result >= 0) m->submittedFrames++; if (result >= 0) m->submittedFrames++;
av_frame_free(&hardware); av_frame_free(&hardware);
AVPacket* packet = av_packet_alloc(); AVPacket* packet = av_packet_alloc();
@@ -355,6 +364,27 @@ bool LiveVideoEncoder::encode(const QImage& image, uint64_t pts, bool key, QList
} }
av_packet_free(&packet); av_packet_free(&packet);
if (result == AVERROR(EAGAIN) || result == AVERROR_EOF) { if (result == AVERROR(EAGAIN) || result == AVERROR_EOF) {
// Сводка по этапам отличает конвертацию, синхронный encoder и получение/упаковку пакета.
const qint64 totalUs = timer.nsecsElapsed() / 1000;
++m->inputs; m->outputs += output.size() - outputBase;
bool producedKey = false;
for (auto i = outputBase; i < output.size(); ++i) if (output[i].key) { ++m->keys; producedKey = true; }
m->prepareMax = std::max(m->prepareMax, preparedUs);
m->submitMax = std::max(m->submitMax, submittedUs - preparedUs);
m->drainMax = std::max(m->drainMax, totalUs - submittedUs);
auto& frameMax = producedKey ? m->keyMax : m->deltaMax;
frameMax = std::max(frameMax, totalUs);
if (m->reportTimer.elapsed() >= 1000) {
DEBUG_DEBUG(DEBUG_CATEGORY_DEBUG,
"video encoder flow name=%s size=%dx%d bitrate=%d interval_us=%lld inputs=%u outputs=%u keys=%u "
"prepare_max_us=%lld submit_max_us=%lld drain_max_us=%lld key_max_us=%lld delta_max_us=%lld pts=%llu",
qPrintable(m->name), m->config.width, m->config.height, m->config.bitrate,
(long long)(m->reportTimer.nsecsElapsed() / 1000), m->inputs, m->outputs, m->keys,
(long long)m->prepareMax, (long long)m->submitMax, (long long)m->drainMax,
(long long)m->keyMax, (long long)m->deltaMax, (unsigned long long)pts);
m->reportTimer.restart(); m->inputs = m->outputs = m->keys = 0;
m->prepareMax = m->submitMax = m->drainMax = m->keyMax = m->deltaMax = 0;
}
if (key && output.isEmpty()) DEBUG_DEBUG(DEBUG_CATEGORY_VIDEO, "desktop requested KEY buffered name=%s PTS=%llu", if (key && output.isEmpty()) DEBUG_DEBUG(DEBUG_CATEGORY_VIDEO, "desktop requested KEY buffered name=%s PTS=%llu",
qPrintable(m->name), (unsigned long long)pts); qPrintable(m->name), (unsigned long long)pts);
return true; return true;