Диагностика задержек программного видео на desktop
This commit is contained in:
1 parent
03ff431d78
commit
5a55bcb500
3 files changed
+51
-11
No files matched your search
@@ -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";
|
||||||
|
|||||||
@@ -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; }
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
Reference in new issue
Block a user