Prevent delayed camera startup from shifting video into the future
This commit is contained in:
1 parent
ac3801761e
commit
4c2d8aa4cc
3 files changed
+57
-14
No files matched your search
@@ -0,0 +1,19 @@
|
||||
#pragma once
|
||||
#include <cstdint>
|
||||
|
||||
// Первый callback может опоздать при запуске камеры. Его задержка не должна навсегда сдвигать PTS в будущее.
|
||||
struct CallVideoCaptureClock {
|
||||
int64_t source = -1, offset = 0, correction = 0;
|
||||
int64_t map(int64_t next, int64_t arrival) {
|
||||
if (next < 0) { source = -1; return arrival; }
|
||||
if (source < 0 || next < source) offset = arrival - next;
|
||||
source = next;
|
||||
int64_t pts = next + offset;
|
||||
if (pts > arrival) {
|
||||
correction += pts - arrival;
|
||||
offset -= pts - arrival;
|
||||
pts = arrival;
|
||||
}
|
||||
return pts;
|
||||
}
|
||||
};
|
||||
@@ -2,6 +2,7 @@
|
||||
#include "call_video_encoder.h"
|
||||
#include "call_video_decoder.h"
|
||||
#include "call_video_cadence.h"
|
||||
#include "call_video_capture_clock.h"
|
||||
#include "call_camera_rate.h"
|
||||
#include "../../../src/call/call_video_port.h"
|
||||
#include "../../../src/call/call_audio.h"
|
||||
@@ -32,7 +33,8 @@ struct CallVideoEngine::Impl {
|
||||
uint64_t id = 0, revision = 1;
|
||||
bool enabled = false;
|
||||
int width = 640, height = 480, codec = 0;
|
||||
int64_t pts = -1, backend = -1, anchor = -1;
|
||||
int64_t pts = -1;
|
||||
CallVideoCaptureClock captureClock;
|
||||
uint64_t captured = 0;
|
||||
QImage shown;
|
||||
uint64_t shownSerial = 0, deliveredSerial = 0;
|
||||
@@ -109,7 +111,7 @@ void CallVideoEngine::closeCamera() {
|
||||
if (m->camera) { disconnect(m->camera.get(), nullptr, this, nullptr); m->camera->stop(); }
|
||||
m->cameraRate.restore();
|
||||
m->capture.reset(); m->camera.reset(); m->sink.reset();
|
||||
{ std::lock_guard<std::mutex> guard(m->lock); m->raw = {}; m->pts = m->backend = m->anchor = -1; }
|
||||
{ std::lock_guard<std::mutex> guard(m->lock); m->raw = {}; m->pts = -1; m->captureClock = {}; }
|
||||
#endif
|
||||
m->previewPts = -1; m->previewVisible = false; m->previewReportAt = nowUs(); m->previewFrames = 0; m->previewMaxUs = 0;
|
||||
emit localImageChanged({});
|
||||
@@ -152,15 +154,12 @@ void CallVideoEngine::setVideo(bool enabled, const QByteArray& cameraId, int wid
|
||||
if (!frame.isValid() || !m->running) return;
|
||||
std::lock_guard<std::mutex> guard(m->lock);
|
||||
if (revision != m->revision) return;
|
||||
int64_t pts = nowUs();
|
||||
if (frame.startTime() >= 0) {
|
||||
if (m->backend < 0) {
|
||||
m->backend = frame.startTime(); m->anchor = pts;
|
||||
DEBUG_INFO(DEBUG_CATEGORY_CALL, "camera clock source=%lld monotonic=%lld calibration=arrival-estimate",
|
||||
(long long)m->backend, (long long)m->anchor);
|
||||
}
|
||||
pts = m->anchor + frame.startTime() - m->backend;
|
||||
}
|
||||
const int64_t arrival = nowUs(), source = frame.startTime();
|
||||
if (m->pts < 0) DEBUG_INFO(DEBUG_CATEGORY_CALL, "camera clock source=%lld monotonic=%lld calibration=arrival-upper-bound",
|
||||
(long long)source, (long long)arrival);
|
||||
if (m->captureClock.source >= 0 && source < m->captureClock.source)
|
||||
DEBUG_WARN(DEBUG_CATEGORY_CALL, "camera clock reset source=%lld previous=%lld", (long long)source, (long long)m->captureClock.source);
|
||||
const int64_t pts = m->captureClock.map(source, arrival);
|
||||
if (pts > m->pts) { m->raw = frame; m->pts = pts; ++m->captured; }
|
||||
}, Qt::DirectConnection);
|
||||
connect(m->camera.get(), &QCamera::errorOccurred, this, [this](QCamera::Error, const QString& error) {
|
||||
@@ -201,13 +200,14 @@ void CallVideoEngine::transmit() {
|
||||
struct call_video_port_status state{};
|
||||
if (call_video_port_status(m->id, &state) < 0) break;
|
||||
if (state.error) { m->report(tr("Core rejected video format")); break; }
|
||||
uint64_t revision, captured; bool enabled; int width, height, codec; int64_t pts;
|
||||
uint64_t revision, captured; bool enabled; int width, height, codec; int64_t pts, clockCorrection;
|
||||
#ifdef HAVE_STREAM_CAPTURE
|
||||
QVideoFrame raw;
|
||||
#endif
|
||||
{ std::lock_guard<std::mutex> guard(m->lock);
|
||||
revision = m->revision; enabled = m->enabled; width = m->width; height = m->height; codec = m->codec; pts = m->pts;
|
||||
captured = m->captured;
|
||||
clockCorrection = m->captureClock.correction;
|
||||
#ifdef HAVE_STREAM_CAPTURE
|
||||
raw = m->raw;
|
||||
#endif
|
||||
@@ -216,10 +216,10 @@ void CallVideoEngine::transmit() {
|
||||
if (now - reportAt >= 1000000) {
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_CALL,
|
||||
"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",
|
||||
"encode_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),
|
||||
encodedCount, keyCount, sendBusy, paced, (long long)encodeMax,
|
||||
(long long)(pts >= 0 ? now - pts : 0), (long long)pts);
|
||||
(long long)(pts >= 0 ? now - pts : 0), (long long)pts, (long long)clockCorrection);
|
||||
reportAt = now; captureBase = captured; encodedCount = keyCount = sendBusy = paced = 0; encodeMax = 0;
|
||||
}
|
||||
if (revision != userRevision) { userRevision = revision; excluded = 0; }
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
#include "../src/call_video_encoder.h"
|
||||
#include "../src/call_video_decoder.h"
|
||||
#include "../src/call_video_cadence.h"
|
||||
#include "../src/call_video_capture_clock.h"
|
||||
#include "../../../src/call/call_video_bitstream.h"
|
||||
extern "C" {
|
||||
#include "debug_config.h"
|
||||
@@ -42,6 +43,29 @@ class CallVideoEncoderTest : public QObject {
|
||||
return {17, 640, 360, 30, 0, (uint8_t)codec, (uint8_t)(codec == 1 ? 31 : 93), 4000000, (uint8_t)(codec == 1 ? 2 : 1)};
|
||||
}
|
||||
private slots:
|
||||
void captureClockDelayedStartup() {
|
||||
CallVideoCaptureClock clock;
|
||||
const int64_t origin = 1000000;
|
||||
int64_t previous = -1;
|
||||
for (int i = 0; i < 900; i++) {
|
||||
// Первый кадр приходит с задержкой 220мс; затем камера догоняет по 5мс за кадр.
|
||||
const int64_t source = i * 33333LL;
|
||||
const int64_t delay = std::max<int64_t>(0, 220000 - i * 5000LL);
|
||||
const int64_t arrival = origin + source + delay;
|
||||
const int64_t pts = clock.map(source, arrival);
|
||||
QVERIFY(pts <= arrival);
|
||||
QVERIFY(pts > previous);
|
||||
if (i > 44) QCOMPARE(pts, origin + source);
|
||||
previous = pts;
|
||||
}
|
||||
QCOMPARE(clock.correction, 220000LL);
|
||||
// Задержанный callback не сдвигает уже откалиброванные часы вперёд.
|
||||
QCOMPARE(clock.map(30000000, origin + 30100000), origin + 30000000);
|
||||
QCOMPARE(clock.map(30033333, origin + 30033333), origin + 30033333);
|
||||
QCOMPARE(clock.map(0, origin + 31000000), origin + 31000000); // Перезапуск часов камеры.
|
||||
QCOMPARE(clock.map(-1, origin + 31033333), origin + 31033333); // Нет PTS.
|
||||
QCOMPARE(clock.map(100, origin + 31066666), origin + 31066666);
|
||||
}
|
||||
void captureCadence() {
|
||||
CallVideoCadence jittered, fast;
|
||||
for (int i = 0; i < 300; i++) QVERIFY(jittered.accept(1000000 + i * 33333 + (i % 2 ? -1500 : 1500)));
|
||||
|
||||
Reference in new issue
Block a user