From 282cebc71bdbd06bb7788f1c0949bc9430141e85 Mon Sep 17 00:00:00 2001 From: Trey Moen Date: Thu, 27 Mar 2025 22:47:09 -0700 Subject: [PATCH] slightly better, skips occasional frames but not horrid --- tools/clip/application.cc | 58 ++++++++++++++++++++++++++--------- tools/clip/application.h | 1 + tools/clip/recorder/widget.cc | 24 ++++++++++----- tools/clip/recorder/widget.h | 3 +- 4 files changed, 63 insertions(+), 23 deletions(-) diff --git a/tools/clip/application.cc b/tools/clip/application.cc index 52cbec0571..c1af4cb6f4 100644 --- a/tools/clip/application.cc +++ b/tools/clip/application.cc @@ -2,6 +2,7 @@ #include #include +#include #include #include @@ -73,30 +74,54 @@ Application::Application(int argc, char *argv[], QObject *parent) : QObject(pare recorder->moveToThread(recorderThread); connect(recorderThread, &QThread::finished, recorder, &QObject::deleteLater); connect(app, &QCoreApplication::aboutToQuit, recorderThread, &QThread::quit); + recorderThread->start(); + + // Initialize and start replay + initReplay(route.toStdString()); + replayThread = QThread::create([this, startTime] { startReplay(startTime); }); + replayThread->start(); - QTimer *loop = new QTimer; - connect(loop, &QTimer::timeout, this, [&]() { + // Frame capture optimization + QElapsedTimer frameTimer; + frameTimer.start(); + int64_t lastFrameTime = 0; + const int64_t frameInterval = 1000 / UI_FREQ; // Target frame interval in ms + + loop = new QTimer; + connect(loop, &QTimer::timeout, this, [&, frameTimer, lastFrameTime]() mutable { if (!window->isVisible()) { return; } - QElapsedTimer timer; - timer.start(); + + int64_t currentTime = frameTimer.elapsed(); + int64_t elapsedSinceLastFrame = currentTime - lastFrameTime; + + // Skip frame if we're ahead of schedule + if (elapsedSinceLastFrame < frameInterval) { + return; + } + QPixmap pixmap = window->grab(); - qDebug() << "pixmap took " << timer.elapsed() << " ms"; - timer.restart(); - recorder->saveFrame(std::make_shared(std::move(pixmap))); + + // Only process frame if capture was successful + if (!pixmap.isNull()) { + recorder->saveFrame(std::make_shared(std::move(pixmap))); + lastFrameTime = currentTime; + } }); - loop->start(1000 / UI_FREQ); + + // Use a higher timer resolution for more precise frame timing + loop->setTimerType(Qt::PreciseTimer); + loop->start(1); // Run at highest possible frequency, we'll control frame rate ourselves window->setAttribute(Qt::WA_DontShowOnScreen); window->setAttribute(Qt::WA_OpaquePaintEvent); window->setAttribute(Qt::WA_NoSystemBackground); - recorderThread->start(); - - // Initialize and start replay - initReplay(route.toStdString()); - replayThread = QThread::create([this, startTime] { startReplay(startTime); }); - replayThread->start(); + window->setAttribute(Qt::WA_TranslucentBackground, false); + window->setAttribute(Qt::WA_AlwaysStackOnTop); + window->setAttribute(Qt::WA_ShowWithoutActivating); + window->setAttribute(Qt::WA_UpdatesDisabled); + window->setAttribute(Qt::WA_StaticContents); } void Application::initReplay(const std::string& route) { @@ -132,15 +157,18 @@ Application::~Application() { if (recorderThread) { recorderThread->quit(); recorderThread->wait(); + delete recorderThread; } + delete recorder; delete window; + delete loop; delete app; } int Application::exec() const { // TODO: modify Replay to block until all OnroadWindow required messages have been broadcast at least once - std::this_thread::sleep_for(std::chrono::seconds(5)); + std::this_thread::sleep_for(std::chrono::seconds(8)); setMainWindow(window); return app->exec(); } diff --git a/tools/clip/application.h b/tools/clip/application.h index 578ce8a39c..09099d1c95 100644 --- a/tools/clip/application.h +++ b/tools/clip/application.h @@ -22,6 +22,7 @@ private: QThread *recorderThread = nullptr; Recorder *recorder = nullptr; OnroadWindow *window; + QTimer *loop; // Replay related members std::unique_ptr replay; diff --git a/tools/clip/recorder/widget.cc b/tools/clip/recorder/widget.cc index 861809d22d..b93ab2b6bb 100644 --- a/tools/clip/recorder/widget.cc +++ b/tools/clip/recorder/widget.cc @@ -3,37 +3,47 @@ #include "tools/clip/recorder/ffmpeg.h" Recorder::Recorder(const std::string& outputFile, QObject *parent) : QObject(parent) { - encoder = new FFmpegEncoder(outputFile, DEVICE_SCREEN_SIZE.width(), DEVICE_SCREEN_SIZE.height(), UI_FREQ); + const float scale = util::getenv("SCALE", 1.0f); + encoder = new FFmpegEncoder(outputFile, DEVICE_SCREEN_SIZE.width() * scale, DEVICE_SCREEN_SIZE.height() * scale, UI_FREQ); } Recorder::~Recorder() { + keepRunning = false; // Signal processing thread to stop delete encoder; } void Recorder::saveFrame(const std::shared_ptr &frame) { QMutexLocker locker(&mutex); + + // Drop frame if queue is full + if (frameQueue.size() >= MAX_QUEUE_SIZE) { + qDebug() << "Dropping frame"; + return; + } + frameQueue.enqueue(frame); - if (!isProcessing) { - isProcessing = true; + if (isProcessing.loadRelaxed() == 0) { + isProcessing.storeRelaxed(1); QMetaObject::invokeMethod(this, &Recorder::processQueue, Qt::QueuedConnection); } } void Recorder::processQueue() { - while (true) { + while (keepRunning) { std::shared_ptr frame; { QMutexLocker locker(&mutex); - if (frameQueue.isEmpty() || !keepRunning) { - isProcessing = false; + if (frameQueue.isEmpty()) { + isProcessing.storeRelaxed(0); return; } frame = frameQueue.dequeue(); } - if (!encoder->writeFrame(frame->toImage().convertToFormat(QImage::Format_ARGB32_Premultiplied))) { + if (!encoder->writeFrame(frame->toImage().convertToFormat(QImage::Format_ARGB32))) { fprintf(stderr, "did not write\n"); } } + isProcessing.storeRelaxed(0); } diff --git a/tools/clip/recorder/widget.h b/tools/clip/recorder/widget.h index 4f5a97a5fd..b9ff712ae8 100644 --- a/tools/clip/recorder/widget.h +++ b/tools/clip/recorder/widget.h @@ -19,10 +19,11 @@ public slots: void saveFrame(const std::shared_ptr &frame); private: + static constexpr int MAX_QUEUE_SIZE = 30; // Limit queue size to prevent memory growth FFmpegEncoder *encoder; QQueue> frameQueue; QMutex mutex; - bool isProcessing = false; + QAtomicInt isProcessing{0}; // Use atomic for thread safety bool keepRunning = true; void processQueue(); };