|
|
|
|
@ -36,6 +36,8 @@ struct CallVideoEngine::Impl {
|
|
|
|
|
int64_t shownPts = -1; |
|
|
|
|
uint64_t publicationEpoch = 0; |
|
|
|
|
bool publicationPending = false; |
|
|
|
|
bool presentationDriven = false; |
|
|
|
|
uint64_t publicationSerial = 0; |
|
|
|
|
unsigned publicationOverwritten = 0; // Только под lock: выбранные кадры, заменённые до GUI callback.
|
|
|
|
|
int64_t publicationQueuedAt = 0; |
|
|
|
|
int64_t shownAt = 0; |
|
|
|
|
@ -88,19 +90,30 @@ CallVideoEngine::CallVideoEngine(QObject* parent) : QObject(parent), m(std::make
|
|
|
|
|
#endif |
|
|
|
|
} |
|
|
|
|
CallVideoEngine::~CallVideoEngine() { stop(); } |
|
|
|
|
// В GUI одновременно не больше одного уведомления; при паузе UI остаётся последний выбранный кадр.
|
|
|
|
|
void CallVideoEngine::publishImage(const QImage& image, int64_t pts) { |
|
|
|
|
uint64_t epoch; |
|
|
|
|
void CallVideoEngine::setPresentationDriven(bool enabled) { |
|
|
|
|
std::lock_guard<std::mutex> guard(m->lock); m->presentationDriven = enabled; |
|
|
|
|
} |
|
|
|
|
void CallVideoEngine::imagePresented(quint64 serial) { |
|
|
|
|
{ std::lock_guard<std::mutex> guard(m->lock); |
|
|
|
|
if (serial != m->publicationSerial) return; |
|
|
|
|
m->publicationPending = false; } |
|
|
|
|
m->wake.notify_all(); |
|
|
|
|
} |
|
|
|
|
// Только один кадр до подтверждения рисования: GUI не заменяет ещё не показанное изображение.
|
|
|
|
|
void CallVideoEngine::publishImage(const QImage& image, int64_t pts) { |
|
|
|
|
uint64_t epoch, serial; |
|
|
|
|
{ std::unique_lock<std::mutex> guard(m->lock); |
|
|
|
|
m->wake.wait(guard, [this] { return !m->running || !m->publicationPending; }); |
|
|
|
|
if (!m->running) return; |
|
|
|
|
m->shown = image; m->shownPts = pts; m->shownAt = nowUs(); |
|
|
|
|
if (m->publicationPending) { ++m->publicationOverwritten; return; } |
|
|
|
|
m->publicationPending = true; m->publicationQueuedAt = m->shownAt; epoch = m->publicationEpoch; } |
|
|
|
|
QMetaObject::invokeMethod(this, [this, epoch] { |
|
|
|
|
m->publicationPending = true; m->publicationQueuedAt = m->shownAt; epoch = m->publicationEpoch; |
|
|
|
|
serial = ++m->publicationSerial; } |
|
|
|
|
QMetaObject::invokeMethod(this, [this, epoch, serial] { |
|
|
|
|
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; queued = m->publicationQueuedAt; |
|
|
|
|
overwritten = m->publicationOverwritten; m->publicationOverwritten = 0; m->publicationPending = false; } |
|
|
|
|
overwritten = m->publicationOverwritten; m->publicationOverwritten = 0; } |
|
|
|
|
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); |
|
|
|
|
@ -110,7 +123,10 @@ void CallVideoEngine::publishImage(const QImage& image, int64_t pts) {
|
|
|
|
|
"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); |
|
|
|
|
emit imageChanged(image, serial); |
|
|
|
|
bool driven; |
|
|
|
|
{ std::lock_guard<std::mutex> guard(m->lock); driven = m->presentationDriven; } |
|
|
|
|
if (!driven || image.isNull()) imagePresented(serial); |
|
|
|
|
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 " |
|
|
|
|
@ -142,12 +158,13 @@ void CallVideoEngine::closeCamera() {
|
|
|
|
|
} |
|
|
|
|
void CallVideoEngine::stop() { |
|
|
|
|
m->running = false; m_timer.stop(); closeCamera(); |
|
|
|
|
m->wake.notify_all(); |
|
|
|
|
if (m->tx.joinable()) m->tx.join(); |
|
|
|
|
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->publicationOverwritten = 0; m->shown = {}; m->shownPts = -1; } |
|
|
|
|
m->id = 0; m->enabled = false; emit imageChanged({}); |
|
|
|
|
m->id = 0; m->enabled = false; emit imageChanged({}, 0); |
|
|
|
|
} |
|
|
|
|
void CallVideoEngine::setVideo(bool enabled, const QByteArray& cameraId, int width, int height, int codec) { |
|
|
|
|
const bool active = enabled && m->running; |
|
|
|
|
@ -341,7 +358,7 @@ void CallVideoEngine::receive() {
|
|
|
|
|
uint64_t reportedGeneration = 0, rejectedGeneration = 0; |
|
|
|
|
while (m->running) { |
|
|
|
|
if (requestedWake) wakeLateMax = std::max(wakeLateMax, nowUs() - requestedWake); |
|
|
|
|
// Один кадр ждёт своего срока, второй позволяет пропустить устаревший показ после паузы CPU.
|
|
|
|
|
// Малый decoded lookahead; накопленный долг остаётся в encoded FIFO.
|
|
|
|
|
while (pictures.size() < 2 && m->running) { |
|
|
|
|
call_video_frame packet{}; |
|
|
|
|
if (call_video_port_pull(m->id, &packet) == 1) { |
|
|
|
|
@ -380,7 +397,7 @@ void CallVideoEngine::receive() {
|
|
|
|
|
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); |
|
|
|
|
pictures.size() > 1 ? &pictures[1].timing : nullptr, now, &clock); |
|
|
|
|
if (action != CALL_VIDEO_PLAYOUT_WAIT) { |
|
|
|
|
if (action == CALL_VIDEO_PLAYOUT_SHOW) { |
|
|
|
|
const auto& f = pictures.front(); publishImage(f.image, f.timing.pts_us); lastPictureAt = now; |
|
|
|
|
|