Browse Source

Prevent delayed camera startup from shifting video into the future

master
evgeny 3 days ago
parent
commit
4c2d8aa4cc
  1. 19
      tools/chatgui/src/call_video_capture_clock.h
  2. 28
      tools/chatgui/src/call_video_engine.cpp
  3. 24
      tools/chatgui/tests/test_call_video_encoder.cpp

19
tools/chatgui/src/call_video_capture_clock.h

@ -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;
}
};

28
tools/chatgui/src/call_video_engine.cpp

@ -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; }

24
tools/chatgui/tests/test_call_video_encoder.cpp

@ -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)));

Loading…
Cancel
Save