Present desktop video sequentially through GUI paint acknowledgement
This commit is contained in:
1 parent
0eb743d627
commit
4e9cce8a01
3 files changed
+47
-14
No files matched your search
@@ -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;
|
||||
|
||||
@@ -14,9 +14,11 @@ public:
|
||||
void start(quint64 id);
|
||||
void stop();
|
||||
void setVideo(bool enabled, const QByteArray& camera, int width, int height, int codec);
|
||||
void setPresentationDriven(bool enabled); // Окно подтверждает рисование; без окна достаточно доставки signal.
|
||||
void imagePresented(quint64 serial);
|
||||
signals:
|
||||
void camerasChanged(const CallCameraDevices& devices);
|
||||
void imageChanged(const QImage& image);
|
||||
void imageChanged(const QImage& image, quint64 serial);
|
||||
void localImageChanged(const QImage& image);
|
||||
void statusChanged(const QString& status);
|
||||
private:
|
||||
|
||||
@@ -44,7 +44,14 @@ public:
|
||||
resize(qMin(1100, available.width() * 9 / 10), qMin(800, available.height() * 9 / 10));
|
||||
}
|
||||
QImage remote, local;
|
||||
CallVideoEngine* engine = nullptr;
|
||||
quint64 serial = 0;
|
||||
protected:
|
||||
void hideEvent(QHideEvent* event) override { presented(); QWidget::hideEvent(event); }
|
||||
void changeEvent(QEvent* event) override {
|
||||
QWidget::changeEvent(event);
|
||||
if (isMinimized()) presented();
|
||||
}
|
||||
void closeEvent(QCloseEvent* event) override {
|
||||
DEBUG_INFO(DEBUG_CATEGORY_CALL, "call video window closed; call continues"); event->accept();
|
||||
}
|
||||
@@ -69,8 +76,12 @@ protected:
|
||||
QRect target(QPoint(videoRect.right() - preview.width() - 12, videoRect.top() + 12), preview);
|
||||
painter.setPen(QPen(Qt::white, 2)); painter.drawRect(target.adjusted(-1, -1, 1, 1)); painter.drawImage(target, local);
|
||||
}
|
||||
painter.end(); presented();
|
||||
}
|
||||
private:
|
||||
void presented() {
|
||||
if (engine && serial) { engine->imagePresented(serial); serial = 0; }
|
||||
}
|
||||
void toggleFullScreen() {
|
||||
if (isFullScreen()) { showNormal(); if (!m_geometry.isEmpty()) restoreGeometry(m_geometry); }
|
||||
else { m_geometry = saveGeometry(); showFullScreen(); }
|
||||
@@ -271,6 +282,7 @@ CallWindow::CallWindow(QWidget* parent)
|
||||
lay->addWidget(m_bgpPathLabel);
|
||||
lay->addWidget(m_statsLabel);
|
||||
m_video = new CallVideoEngine(this);
|
||||
m_video->setPresentationDriven(true); m_videoWindow->engine = m_video;
|
||||
m_localPreview = new QLabel(this); m_localPreview->setAlignment(Qt::AlignCenter); m_localPreview->hide();
|
||||
lay->addWidget(m_localPreview, 0, Qt::AlignHCenter);
|
||||
m_videoStatus = new QLabel(this); m_videoStatus->setWordWrap(true); lay->addWidget(m_videoStatus);
|
||||
@@ -317,10 +329,12 @@ CallWindow::CallWindow(QWidget* parent)
|
||||
connect(m_video, &CallVideoEngine::statusChanged, this, [this](const QString& text) {
|
||||
m_videoStatus->setText(text); m_videoStatus->setVisible(m_state == Active && !text.isEmpty());
|
||||
});
|
||||
connect(m_video, &CallVideoEngine::imageChanged, this, [this](const QImage& image) {
|
||||
connect(m_video, &CallVideoEngine::imageChanged, this, [this](const QImage& image, quint64 serial) {
|
||||
bool changed = m_remoteImage.isNull() != image.isNull(); m_remoteImage = image;
|
||||
m_videoWindow->remote = image; m_videoWindow->update();
|
||||
m_videoWindow->remote = image; m_videoWindow->serial = serial; m_videoWindow->update();
|
||||
if (changed) updateVideoPresentation();
|
||||
// Скрытое окно продолжает принимать поток, не ждёт невозможного paintEvent.
|
||||
if (!m_videoWindow->isVisible() || m_videoWindow->isMinimized()) m_video->imagePresented(serial);
|
||||
update();
|
||||
});
|
||||
connect(m_video, &CallVideoEngine::localImageChanged, this, [this](const QImage& image) {
|
||||
|
||||
Reference in new issue
Block a user