1
0
Fork 0
mirror of https://git.jami.net/savoirfairelinux/jami-client-qt.git synced 2025-09-10 03:53:23 +02:00

video: shm: replace fixed timer with waiting on producer

Removes the fixed rate QTimer that was used to query shm frames, and waits on the producer in a thread loop.

Also factors FPS value tracking into the Renderer base class.

Gitlab: #938
Change-Id: Icf44c8399d70c4127c512802b6cf6c6dccdccfd6
This commit is contained in:
Andreas Traczyk 2023-05-02 11:03:42 -04:00 committed by Sébastien Blin
parent b8bc061a86
commit 02d25db786
5 changed files with 163 additions and 127 deletions

View file

@ -20,7 +20,6 @@
#include "api/avmodel.h" #include "api/avmodel.h"
#include "api/video.h" #include "api/video.h"
#include "api/call.h"
#include "api/lrc.h" #include "api/lrc.h"
#ifdef ENABLE_LIBWRAP #ifdef ENABLE_LIBWRAP
#include "directrenderer.h" #include "directrenderer.h"
@ -943,44 +942,48 @@ createRenderer(const QString& id, const QSize& res, const QString& shmPath = {})
void void
AVModelPimpl::addRenderer(const QString& id, const QSize& res, const QString& shmPath) AVModelPimpl::addRenderer(const QString& id, const QSize& res, const QString& shmPath)
{ {
auto connectRenderer = [this](Renderer* renderer, const QString& id) { // First remove the existing renderer.
renderers_.erase(id);
// Create a new one and add it.
auto renderer = createRenderer(id, res, shmPath);
std::lock_guard<std::mutex> lk(renderers_mtx_);
auto& r = renderers_[id];
r = std::move(renderer);
renderers_mtx_.unlock();
// Listen and forward id-bound signals upwards.
connect( connect(
renderer, r.get(),
&Renderer::fpsChanged, &Renderer::fpsChanged,
this, this,
[this, id](void) { Q_EMIT linked_.updateRenderersFPSInfo(id); }, [this, id](void) { linked_.updateRenderersFPSInfo(id); },
Qt::QueuedConnection); Qt::QueuedConnection);
connect( connect(
renderer, r.get(),
&Renderer::started, &Renderer::started,
this, this,
[this, id](const QSize& size) { Q_EMIT linked_.rendererStarted(id, size); }, [this, id](const QSize& size) { Q_EMIT linked_.rendererStarted(id, size); },
Qt::DirectConnection); Qt::DirectConnection);
connect( connect(
renderer, r.get(),
&Renderer::frameBufferRequested, &Renderer::frameBufferRequested,
this, this,
[this, id](AVFrame* frame) { Q_EMIT linked_.frameBufferRequested(id, frame); }, [this, id](AVFrame* frame) { Q_EMIT linked_.frameBufferRequested(id, frame); },
Qt::DirectConnection); Qt::DirectConnection);
connect( connect(
renderer, r.get(),
&Renderer::frameUpdated, &Renderer::frameUpdated,
this, this,
[this, id] { Q_EMIT linked_.frameUpdated(id); }, [this, id] { Q_EMIT linked_.frameUpdated(id); },
Qt::DirectConnection); Qt::DirectConnection);
connect( connect(
renderer, r.get(),
&Renderer::stopped, &Renderer::stopped,
this, this,
[this, id] { Q_EMIT linked_.rendererStopped(id); }, [this, id] { Q_EMIT linked_.rendererStopped(id); },
Qt::DirectConnection); Qt::DirectConnection);
};
std::lock_guard<std::mutex> lk(renderers_mtx_);
renderers_.erase(id); // Because it should be done before creating the renderer
auto renderer = createRenderer(id, res, shmPath);
auto& r = renderers_[id];
r = std::move(renderer);
connectRenderer(r.get(), id);
r->startRendering(); r->startRendering();
} }

View file

@ -33,19 +33,10 @@ using namespace lrc::api::video;
struct DirectRenderer::Impl : public QObject struct DirectRenderer::Impl : public QObject
{ {
Q_OBJECT Q_OBJECT
private:
int fpsC;
int fps;
public: public:
std::chrono::time_point<std::chrono::system_clock> lastFrameDebug;
Impl(DirectRenderer* parent) Impl(DirectRenderer* parent)
: QObject(nullptr) : QObject(nullptr)
, parent_(parent) , parent_(parent)
, fpsC(0)
, fps(0)
, lastFrameDebug(std::chrono::system_clock::now())
{ {
configureTarget(); configureTarget();
if (!VideoManager::instance().registerSinkTarget(parent_->id(), target)) if (!VideoManager::instance().registerSinkTarget(parent_->id(), target))
@ -90,17 +81,8 @@ public:
QMutexLocker lk(&mutex); QMutexLocker lk(&mutex);
frameBufferPtr = std::move(buf); frameBufferPtr = std::move(buf);
} }
// compute FPS
++fpsC;
auto currentTime = std::chrono::system_clock::now();
const std::chrono::duration<double> seconds = currentTime - lastFrameDebug;
if (seconds.count() >= FPS_RATE_SEC) {
fps = static_cast<int>(fpsC / seconds.count());
fpsC = 0;
lastFrameDebug = currentTime;
parent_->setFPS(fps);
}
parent_->updateFpsTracker();
Q_EMIT parent_->frameUpdated(); Q_EMIT parent_->frameUpdated();
}; };
@ -109,6 +91,7 @@ private:
public: public:
libjami::SinkTarget target; libjami::SinkTarget target;
FpsTracker fpsTracker;
QMutex mutex; QMutex mutex;
libjami::FrameBuffer frameBufferPtr; libjami::FrameBuffer frameBufferPtr;
}; };

View file

@ -21,20 +21,34 @@
#include <QSize> #include <QSize>
#include <QMutex> #include <QMutex>
// Uncomment following line to output in console the FPS value for the
// current renderer type (DirectRenderer, ShmRenderer, etc.).
// #define DEBUG_FPS
namespace lrc { namespace lrc {
namespace video { namespace video {
using namespace lrc::api::video; using namespace lrc::api::video;
Renderer::Renderer(const QString& id, const QSize& res) Renderer::Renderer(const QString& id, const QSize& res)
: id_(id) : QObject(nullptr)
, id_(id)
, size_(res) , size_(res)
, QObject(nullptr) , fps_(0.0)
{} , fpsTracker_(new FpsTracker(this))
{
// Subscribe to frame rate updates.
connect(fpsTracker_, &FpsTracker::fpsUpdated, this, [this](double fps) {
setFPS(fps);
#ifdef DEBUG_FPS
qDebug() << this << ": FPS " << fps;
#endif
});
}
Renderer::~Renderer() {} Renderer::~Renderer() {}
int double
Renderer::fps() const Renderer::fps() const
{ {
return fps_; return fps_;
@ -52,12 +66,18 @@ Renderer::size() const
return size_; return size_;
} }
void void
Renderer::setFPS(int fps) Renderer::setFPS(double fps)
{ {
fps_ = fps; fps_ = fps;
Q_EMIT fpsChanged(); Q_EMIT fpsChanged();
} }
void
Renderer::updateFpsTracker()
{
fpsTracker_->update();
}
MapStringString MapStringString
Renderer::getInfos() const Renderer::getInfos() const
{ {
@ -68,5 +88,24 @@ Renderer::getInfos() const
return map; return map;
} }
FpsTracker::FpsTracker(QObject* parent)
: QObject(parent)
, lastTime_(clock_type::now())
{}
void
FpsTracker::update()
{
frameCount_++;
auto now = clock_type::now();
const std::chrono::duration<double> elapsed = now - lastTime_;
if (elapsed.count() >= checkInterval_) {
double fps = static_cast<double>(frameCount_) / elapsed.count();
Q_EMIT fpsUpdated(fps);
frameCount_ = 0;
lastTime_ = now;
}
}
} // namespace video } // namespace video
} // namespace lrc } // namespace lrc

View file

@ -32,6 +32,8 @@
namespace lrc { namespace lrc {
namespace video { namespace video {
class FpsTracker;
class Renderer : public QObject class Renderer : public QObject
{ {
Q_OBJECT Q_OBJECT
@ -39,7 +41,6 @@ public:
constexpr static const char RENDERER_ID[] = "RENDERER_ID"; constexpr static const char RENDERER_ID[] = "RENDERER_ID";
constexpr static const char FPS[] = "FPS"; constexpr static const char FPS[] = "FPS";
constexpr static const char RES[] = "RES"; constexpr static const char RES[] = "RES";
constexpr static const int FPS_RATE_SEC = 1;
Renderer(const QString& id, const QSize& res); Renderer(const QString& id, const QSize& res);
virtual ~Renderer(); virtual ~Renderer();
@ -47,7 +48,7 @@ public:
/** /**
* @return renderer's fps * @return renderer's fps
*/ */
int fps() const; double fps() const;
/** /**
* @return renderer's id * @return renderer's id
@ -67,7 +68,12 @@ public:
/** /**
* set fps * set fps
*/ */
void setFPS(int fps); void setFPS(double fps);
/**
* Update the FPS tracker.
*/
void updateFpsTracker();
MapStringString getInfos() const; MapStringString getInfos() const;
@ -85,7 +91,30 @@ Q_SIGNALS:
private: private:
QString id_; QString id_;
QSize size_; QSize size_;
int fps_; double fps_;
FpsTracker* fpsTracker_;
};
// Helper that counts ticks, and notifies of FPS changes.
class FpsTracker : public QObject
{
Q_OBJECT
public:
FpsTracker(QObject* parent = nullptr);
~FpsTracker() = default;
// Call this function every frame.
void update();
// Emitted after every checkInterval_ when update() is called.
Q_SIGNAL void fpsUpdated(double fps);
private:
using clock_type = std::chrono::high_resolution_clock;
const double checkInterval_ {1.0};
unsigned frameCount_ {0};
std::chrono::time_point<clock_type> lastTime_;
}; };
} // namespace video } // namespace video

View file

@ -20,7 +20,6 @@
#include "shmrenderer.h" #include "shmrenderer.h"
#include "dbus/videomanager.h" #include "dbus/videomanager.h"
#include "videomanager_interface.h"
#include <QDebug> #include <QDebug>
#include <QMutex> #include <QMutex>
@ -39,19 +38,12 @@
#define CLOCK_REALTIME 0 #define CLOCK_REALTIME 0
#endif #endif
#include <QTimer>
#include <chrono>
namespace lrc { namespace lrc {
using namespace api::video; using namespace api::video;
namespace video { namespace video {
// Uncomment following line to output in console the FPS value
//#define DEBUG_FPS
/* Shared memory object /* Shared memory object
* Implementation note: double-buffering * Implementation note: double-buffering
* Shared memory is divided in two regions, each representing one frame. * Shared memory is divided in two regions, each representing one frame.
@ -87,27 +79,46 @@ public:
, shmArea((SHMHeader*) MAP_FAILED) , shmArea((SHMHeader*) MAP_FAILED)
, shmAreaLen(0) , shmAreaLen(0)
, frameGen(0) , frameGen(0)
, fpsC(0)
, fps(0)
, timer(new QTimer(this))
, lastFrameDebug(std::chrono::system_clock::now())
{ {
timer->setInterval(33);
connect(timer, &QTimer::timeout, [this]() { Q_EMIT parent_->frameUpdated(); });
VideoManager::instance().startShmSink(parent_->id(), true); VideoManager::instance().startShmSink(parent_->id(), true);
parent_->moveToThread(&thread); // Continuously check for new frames on a separate thread.
connect(&thread, &QThread::finished, [this] { parent_->stopRendering(); }); // This is necessary because the frame rate is not constant.
thread.start(); // The function getNewFrame() will return false if no new frame is available.
}; thread = QThread::create([this] {
~Impl() forever {
{ if (QThread::currentThread()->isInterruptionRequested()) {
thread.quit(); return;
thread.wait();
} }
// Constants if (!waitForNewFrame()) {
constexpr static const int FRAME_CHECK_RATE_HZ = 120; continue;
}
parent_->updateFpsTracker();
Q_EMIT parent_->frameUpdated();
}
});
};
~Impl() {} // Thread is stopped by parent in ShmRenderer::stopShm.
void stopThread()
{
// Request thread loop interruption and then unblock the sem_wait.
thread->requestInterruption();
// Set the isDestroying flag to true so that the thread loop can exit
// without emitting the frameUpdated signal for an invalid resolution
// (e.g. smartphone rotation).
// This works as ShmHolder::renderFrame should reset frameSize appropriately.
shmLock();
shmArea->frameSize = 0;
shmUnlock();
::sem_post(&shmArea->frameGenMutex);
thread->wait();
}
// Lock the memory while the copy is being made // Lock the memory while the copy is being made
bool shmLock() bool shmLock()
@ -122,7 +133,7 @@ public:
}; };
// Wait for new frame data from shared memory and save pointer. // Wait for new frame data from shared memory and save pointer.
bool getNewFrame(bool wait) bool waitForNewFrame()
{ {
if (!shmLock()) if (!shmLock())
return false; return false;
@ -130,12 +141,7 @@ public:
if (frameGen == shmArea->frameGen) { if (frameGen == shmArea->frameGen) {
shmUnlock(); shmUnlock();
if (not wait) if (::sem_wait(&shmArea->frameGenMutex) < 0)
return false;
// wait for a new frame, max 33ms
static const struct timespec timeout = {0, 33000000};
if (::sem_timedwait(&shmArea->frameGenMutex, &timeout) < 0)
return false; return false;
if (!shmLock()) if (!shmLock())
@ -161,22 +167,6 @@ public:
frameGen = shmArea->frameGen; frameGen = shmArea->frameGen;
shmUnlock(); shmUnlock();
++fpsC;
// Compute the FPS shown to the client
auto currentTime = std::chrono::system_clock::now();
const std::chrono::duration<double> seconds = currentTime - lastFrameDebug;
if (seconds.count() >= FPS_RATE_SEC) {
fps = static_cast<int>(fpsC / seconds.count());
fpsC = 0;
lastFrameDebug = currentTime;
parent_->setFPS(fps);
#ifdef DEBUG_FPS
qDebug() << this << ": FPS " << fps;
#endif
}
return true; return true;
}; };
@ -222,13 +212,8 @@ public:
unsigned shmAreaLen; unsigned shmAreaLen;
uint frameGen; uint frameGen;
int fpsC;
int fps;
std::chrono::time_point<std::chrono::system_clock> lastFrameDebug;
QTimer* timer;
QMutex mutex; QMutex mutex;
QThread thread; QThread* thread;
std::shared_ptr<lrc::api::video::Frame> frame; std::shared_ptr<lrc::api::video::Frame> frame;
}; };
@ -249,10 +234,8 @@ Frame
ShmRenderer::currentFrame() const ShmRenderer::currentFrame() const
{ {
QMutexLocker lk {&pimpl_->mutex}; QMutexLocker lk {&pimpl_->mutex};
if (pimpl_->getNewFrame(false)) {
if (auto frame_ptr = pimpl_->frame) if (auto frame_ptr = pimpl_->frame)
return std::move(*frame_ptr); return std::move(*frame_ptr);
}
return {}; return {};
} }
@ -283,6 +266,7 @@ ShmRenderer::startShm()
} }
pimpl_->shmAreaLen = mapSize; pimpl_->shmAreaLen = mapSize;
pimpl_->thread->start();
return true; return true;
} }
@ -292,12 +276,12 @@ ShmRenderer::stopShm()
if (pimpl_->fd < 0) if (pimpl_->fd < 0)
return; return;
pimpl_->timer->stop();
// Emit the signal before closing the file, this lower the risk of invalid // Emit the signal before closing the file, this lower the risk of invalid
// memory access // memory access
Q_EMIT stopped(); Q_EMIT stopped();
pimpl_->stopThread();
{ {
QMutexLocker lk(&pimpl_->mutex); QMutexLocker lk(&pimpl_->mutex);
// reset the frame so it doesn't point to an old value // reset the frame so it doesn't point to an old value
@ -323,8 +307,6 @@ ShmRenderer::startRendering()
if (!startShm()) if (!startShm())
return; return;
pimpl_->timer->start();
Q_EMIT started(size()); Q_EMIT started(size());
} }