Skip to content
Snippets Groups Projects
Commit cf8769dc authored by Guillaume Roguez's avatar Guillaume Roguez
Browse files

video: shared memory renderer refactoring

Direct rendering done by client

Refs #70057
parent f462eeba
Branches
Tags
No related merge requests found
...@@ -28,6 +28,7 @@ ADD_DEFINITIONS("-std=c++1y") ...@@ -28,6 +28,7 @@ ADD_DEFINITIONS("-std=c++1y")
ADD_DEFINITIONS( ADD_DEFINITIONS(
${QT_DEFINITIONS} ${QT_DEFINITIONS}
-fexceptions -fexceptions
-O2
) )
PROJECT(ringclient) PROJECT(ringclient)
......
...@@ -21,7 +21,6 @@ ...@@ -21,7 +21,6 @@
#include <QtCore/QDebug> #include <QtCore/QDebug>
#include <QtCore/QMutex> #include <QtCore/QMutex>
#include <QtCore/QThread> #include <QtCore/QThread>
#include <QtCore/QTime>
#include <sys/ipc.h> #include <sys/ipc.h>
#include <sys/sem.h> #include <sys/sem.h>
...@@ -37,27 +36,29 @@ ...@@ -37,27 +36,29 @@
#endif #endif
#include <QtCore/QTimer> #include <QtCore/QTimer>
#include <chrono>
#include "private/videorenderermanager.h" #include "private/videorenderermanager.h"
#include "video/resolution.h" #include "video/resolution.h"
#include "private/videorenderer_p.h" #include "private/videorenderer_p.h"
// Uncomment following line to output in console the FPS value
//#define DEBUG_FPS //#define DEBUG_FPS
#ifdef DEBUG_FPS
#include <chrono>
#endif
///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.
// First byte of each frame is warranted to by aligned on 16 bytes. * First byte of each frame is warranted to by aligned on 16 bytes.
// One region is marked readable: this region can be safely read. * One region is marked readable: this region can be safely read.
// The other region is writeable: only the producer can use it. * The other region is writeable: only the producer can use it.
*/
struct SHMHeader { struct SHMHeader {
sem_t mutex; // Lock it before any operations on following fields. sem_t mutex; // Lock it before any operations on following fields.
sem_t frameGenMutex; // unlocked by producer when frameGen modified sem_t frameGenMutex; // unlocked by producer when frameGen modified
unsigned frameGen; // monotonically incremented when a producer changes readOffset unsigned frameGen; // monotonically incremented when a producer changes readOffset
unsigned frameSize; // size in bytes of 1 frame unsigned frameSize; // size in bytes of 1 frame
unsigned mapSize; // size to map if you need to see all data
unsigned readOffset; // offset of readable frame in data unsigned readOffset; // offset of readable frame in data
unsigned writeOffset; // offset of writable frame in data unsigned writeOffset; // offset of writable frame in data
...@@ -72,48 +73,46 @@ namespace Video { ...@@ -72,48 +73,46 @@ namespace Video {
class ShmRendererPrivate : public QObject class ShmRendererPrivate : public QObject
{ {
Q_OBJECT Q_OBJECT
public: public:
ShmRendererPrivate(Video::ShmRenderer* parent); ShmRendererPrivate(ShmRenderer* parent);
// Attributes // Attributes
QString m_ShmPath ; QString m_ShmPath ;
int m_fd ; int m_fd ;
SHMHeader* m_pShmArea ; SHMHeader* m_pShmArea ;
unsigned m_ShmAreaLen ; unsigned m_ShmAreaLen ;
uint m_BufferGen ; uint m_FrameGen ;
QTimer* m_pTimer ; QTimer* m_pTimer ;
int m_fpsC ; int m_fpsC ;
int m_Fps ; int m_Fps ;
QTime m_CurrentTime;
#ifdef DEBUG_FPS
unsigned m_frameCount;
std::chrono::time_point<std::chrono::system_clock> m_lastFrameDebug; std::chrono::time_point<std::chrono::system_clock> m_lastFrameDebug;
#endif
// Constants // Constants
static const int TIMEOUT_SEC = 1; // 1 second static const int FPS_RATE_SEC = 1;
static const int FRAME_CHECK_RATE_HZ = 120;
// Helpers // Helpers
timespec createTimeout(); timespec createTimeout();
bool shmLock(); bool shmLock();
void shmUnlock(); void shmUnlock();
bool renderToBitmap(); bool getNewFrame(bool wait);
bool resizeShm(); bool remapShm();
private: private:
Video::ShmRenderer* q_ptr; Video::ShmRenderer* q_ptr;
private Q_SLOTS:
void timedEvents();
}; };
} ShmRendererPrivate::ShmRendererPrivate(ShmRenderer* parent)
: QObject(parent)
Video::ShmRendererPrivate::ShmRendererPrivate(Video::ShmRenderer* parent) : QObject(parent), q_ptr(parent), , q_ptr(parent)
m_fd(-1),m_fpsC(0),m_Fps(0), , m_fd(-1)
m_pShmArea((SHMHeader*)MAP_FAILED), m_ShmAreaLen(0), m_BufferGen(0), , m_fpsC(0)
m_pTimer(nullptr) , m_Fps(0)
, m_pShmArea((SHMHeader*)MAP_FAILED)
, m_ShmAreaLen(0)
, m_FrameGen(0)
, m_pTimer(nullptr)
#ifdef DEBUG_FPS #ifdef DEBUG_FPS
, m_frameCount(0) , m_frameCount(0)
, m_lastFrameDebug(std::chrono::system_clock::now()) , m_lastFrameDebug(std::chrono::system_clock::now())
...@@ -122,34 +121,34 @@ Video::ShmRendererPrivate::ShmRendererPrivate(Video::ShmRenderer* parent) : QObj ...@@ -122,34 +121,34 @@ Video::ShmRendererPrivate::ShmRendererPrivate(Video::ShmRenderer* parent) : QObj
} }
/// Constructor /// Constructor
Video::ShmRenderer::ShmRenderer(const QByteArray& id, const QString& shmPath, const QSize& res): Renderer(id, res), d_ptr(new ShmRendererPrivate(this)) ShmRenderer::ShmRenderer(const QByteArray& id, const QString& shmPath,
const QSize& res)
: Renderer(id, res)
, d_ptr(new ShmRendererPrivate(this))
{ {
d_ptr->m_ShmPath = shmPath; d_ptr->m_ShmPath = shmPath;
setObjectName("Video::Renderer:"+id); setObjectName("Video::Renderer:"+id);
} }
/// Destructor /// Destructor
Video::ShmRenderer::~ShmRenderer() ShmRenderer::~ShmRenderer()
{ {
stopShm(); stopShm();
} }
///Get the data from shared memory and transform it into a QByteArray /// Wait new frame data from shared memory and save pointer
bool bool
Video::ShmRendererPrivate::renderToBitmap() ShmRendererPrivate::getNewFrame(bool wait)
{ {
QMutexLocker locker {q_ptr->mutex()};
#ifdef Q_OS_LINUX
if (!q_ptr->isRendering())
return false;
if (!shmLock()) if (!shmLock())
return false; return false;
if (m_BufferGen == m_pShmArea->frameGen) { if (m_FrameGen == m_pShmArea->frameGen) {
shmUnlock(); shmUnlock();
if (not wait)
return false;
// wait for a new frame, max 33ms // wait for a new frame, max 33ms
static const struct timespec timeout = {0, 33000000}; static const struct timespec timeout = {0, 33000000};
if (::sem_timedwait(&m_pShmArea->frameGenMutex, &timeout) < 0) if (::sem_timedwait(&m_pShmArea->frameGenMutex, &timeout) < 0)
...@@ -159,69 +158,76 @@ Video::ShmRendererPrivate::renderToBitmap() ...@@ -159,69 +158,76 @@ Video::ShmRendererPrivate::renderToBitmap()
return false; return false;
} }
// valid frame to render? // valid frame to render (daemon may have stopped)?
if (not m_pShmArea->frameSize) if (not m_pShmArea->frameSize) {
shmUnlock();
return false; return false;
}
if (!resizeShm()) { // map frame data
if (!remapShm()) {
qDebug() << "Could not resize shared memory"; qDebug() << "Could not resize shared memory";
return false; return false;
} }
auto& renderer = static_cast<Video::Renderer*>(q_ptr)->d_ptr; q_ptr->setFramePtr(m_pShmArea->data + m_pShmArea->readOffset);
auto& frame = renderer->otherFrame(); m_FrameGen = m_pShmArea->frameGen;
if ((unsigned)frame.size() != m_pShmArea->frameSize)
frame.resize(m_pShmArea->frameSize);
std::copy_n(m_pShmArea->data + m_pShmArea->readOffset, m_pShmArea->frameSize, frame.data());
m_BufferGen = m_pShmArea->frameGen;
renderer->updateFrameIndex();
shmUnlock(); shmUnlock();
#ifdef DEBUG_FPS ++m_fpsC;
// Compute the FPS shown to the client
auto currentTime = std::chrono::system_clock::now(); auto currentTime = std::chrono::system_clock::now();
const std::chrono::duration<double> seconds = currentTime - m_lastFrameDebug; const std::chrono::duration<double> seconds = currentTime - m_lastFrameDebug;
++m_frameCount; if (seconds.count() >= FPS_RATE_SEC) {
if (seconds.count() > 1) { m_Fps = m_fpsC / seconds.count();
qDebug() << this << ": FPS " << (m_frameCount / seconds.count()); m_fpsC = 0;
m_frameCount = 0;
m_lastFrameDebug = currentTime; m_lastFrameDebug = currentTime;
} #ifdef DEBUG_FPS
qDebug() << this << ": FPS " << m_fps;
#endif #endif
}
emit q_ptr->frameUpdated();
return true; return true;
#else
return false;
#endif
} }
///Resize the shared memory /// Remap the shared memory
/// Shared memory in unlocked state if returns false (resize failed).
bool bool
Video::ShmRendererPrivate::resizeShm() ShmRendererPrivate::remapShm()
{ {
const auto areaSize = sizeof(SHMHeader) + 2 * m_pShmArea->frameSize + 15; // This loop handles case where deamon resize shared memory
if (m_ShmAreaLen == areaSize) // during time we unlock it for remapping.
return true; while (m_ShmAreaLen != m_pShmArea->mapSize) {
auto mapSize = m_pShmArea->mapSize;
shmUnlock(); shmUnlock();
if (::munmap(m_pShmArea, m_ShmAreaLen)) { if (::munmap(m_pShmArea, m_ShmAreaLen)) {
qDebug() << "Could not unmap shared area: " << strerror(errno); qDebug() << "Could not unmap shared area: " << strerror(errno);
return false; return false;
} }
m_pShmArea = (SHMHeader*) ::mmap(nullptr, areaSize, m_pShmArea = (SHMHeader*) ::mmap(nullptr, mapSize, PROT_READ | PROT_WRITE,
PROT_READ | PROT_WRITE,
MAP_SHARED, m_fd, 0); MAP_SHARED, m_fd, 0);
if (m_pShmArea == MAP_FAILED) { if (m_pShmArea == MAP_FAILED) {
qDebug() << "Could not remap shared area"; qDebug() << "Could not remap shared area: " << strerror(errno);
return false; return false;
} }
m_ShmAreaLen = areaSize; if (!shmLock())
return shmLock(); return false;
m_ShmAreaLen = mapSize;
}
return true;
} }
/// Connect to the shared memory /// Connect to the shared memory
bool Video::ShmRenderer::startShm() bool
ShmRenderer::startShm()
{ {
if (d_ptr->m_fd != -1) { if (d_ptr->m_fd != -1) {
qDebug() << "fd must be -1"; qDebug() << "fd must be -1";
...@@ -235,8 +241,9 @@ bool Video::ShmRenderer::startShm() ...@@ -235,8 +241,9 @@ bool Video::ShmRenderer::startShm()
return false; return false;
} }
const auto areaSize = sizeof(SHMHeader); // Map only header data
d_ptr->m_pShmArea = (SHMHeader*) ::mmap(nullptr, areaSize, const auto mapSize = sizeof(SHMHeader);
d_ptr->m_pShmArea = (SHMHeader*) ::mmap(nullptr, mapSize,
PROT_READ | PROT_WRITE, PROT_READ | PROT_WRITE,
MAP_SHARED, d_ptr->m_fd, 0); MAP_SHARED, d_ptr->m_fd, 0);
if (d_ptr->m_pShmArea == MAP_FAILED) { if (d_ptr->m_pShmArea == MAP_FAILED) {
...@@ -244,14 +251,13 @@ bool Video::ShmRenderer::startShm() ...@@ -244,14 +251,13 @@ bool Video::ShmRenderer::startShm()
return false; return false;
} }
d_ptr->m_ShmAreaLen = areaSize; d_ptr->m_ShmAreaLen = mapSize;
emit started();
return true; return true;
} }
/// Disconnect from the shared memory /// Disconnect from the shared memory
void Video::ShmRenderer::stopShm() void
ShmRenderer::stopShm()
{ {
if (d_ptr->m_fd < 0) if (d_ptr->m_fd < 0)
return; return;
...@@ -268,92 +274,49 @@ void Video::ShmRenderer::stopShm() ...@@ -268,92 +274,49 @@ void Video::ShmRenderer::stopShm()
} }
/// Lock the memory while the copy is being made /// Lock the memory while the copy is being made
bool Video::ShmRendererPrivate::shmLock() bool
ShmRendererPrivate::shmLock()
{ {
#ifdef Q_OS_LINUX return ::sem_wait(&m_pShmArea->mutex) >= 0;
return sem_wait(&m_pShmArea->mutex) >= 0;
#else
return false;
#endif
} }
/// Remove the lock, allow a new frame to be drawn /// Remove the lock, allow a new frame to be drawn
void Video::ShmRendererPrivate::shmUnlock() void
ShmRendererPrivate::shmUnlock()
{ {
#ifdef Q_OS_LINUX ::sem_post(&m_pShmArea->mutex);
sem_post(&m_pShmArea->mutex);
#endif
} }
/***************************************************************************** /*****************************************************************************
* * * *
* Slots * * Slots *
* * * *
****************************************************************************/ ****************************************************************************/
///Update the buffer
void Video::ShmRendererPrivate::timedEvents()
{
if (renderToBitmap()) {
//Compute the FPS shown to the client
if (m_CurrentTime.second() != QTime::currentTime().second()) {
m_Fps = m_fpsC;
m_fpsC=0;
m_CurrentTime = QTime::currentTime();
}
m_fpsC++;
emit q_ptr->frameUpdated();
}
/*else {
qDebug() << "Frame dropped";
usleep(rand()%1000); //Be sure it can come back in sync
}*/
}
/// Start the rendering loop /// Start the rendering loop
void Video::ShmRenderer::startRendering() void
ShmRenderer::startRendering()
{ {
QMutexLocker locker {mutex()}; QMutexLocker locker {mutex()};
if (!startShm()) { if (!startShm())
qDebug() << "Cannot start rendering on " << d_ptr->m_ShmPath;
return; return;
}
if (!d_ptr->m_pTimer) {
d_ptr->m_pTimer = new QTimer(nullptr);
// m_pTimer->moveToThread(thread());
connect(d_ptr->m_pTimer,SIGNAL(timeout()),d_ptr.data(),SLOT(timedEvents()));
d_ptr->m_pTimer->setInterval(30);
}
if (!d_ptr->m_pTimer->isActive()) {
qDebug() << "Is running" << thread()->isRunning();
d_ptr->m_pTimer->start();
}
else
qDebug() << "Timer already started!";
setRendering(true); setRendering(true);
emit started();
} }
/// Stop the rendering loop /// Stop the rendering loop
void Video::ShmRenderer::stopRendering() void
ShmRenderer::stopRendering()
{ {
QMutexLocker locker {mutex()};
setRendering(false); setRendering(false);
QMutexLocker locker {mutex()};
qDebug() << "Stopping rendering on" << this;
if (d_ptr->m_pTimer)
d_ptr->m_pTimer->stop();
emit stopped(); emit stopped();
stopShm(); stopShm();
} }
/***************************************************************************** /*****************************************************************************
* * * *
* Getters * * Getters *
...@@ -361,20 +324,36 @@ void Video::ShmRenderer::stopRendering() ...@@ -361,20 +324,36 @@ void Video::ShmRenderer::stopRendering()
****************************************************************************/ ****************************************************************************/
/// Get the current frame rate of this renderer /// Get the current frame rate of this renderer
int Video::ShmRenderer::fps() const int
ShmRenderer::fps() const
{ {
return d_ptr->m_Fps; return d_ptr->m_Fps;
} }
/// Get frame data pointer from shared memory
void*
ShmRenderer::getFramePtr() const
{
if (!isRendering())
return nullptr;
QMutexLocker lk {mutex()};
d_ptr->getNewFrame(false);
return Renderer::getFramePtr();
}
/***************************************************************************** /*****************************************************************************
* * * *
* Setters * * Setters *
* * * *
****************************************************************************/ ****************************************************************************/
void Video::ShmRenderer::setShmPath(const QString& path) void
ShmRenderer::setShmPath(const QString& path)
{ {
d_ptr->m_ShmPath = path; d_ptr->m_ShmPath = path;
} }
} // namespace Video
#include <shmrenderer.moc> #include <shmrenderer.moc>
...@@ -53,6 +53,7 @@ class LIB_EXPORT ShmRenderer : public Renderer { ...@@ -53,6 +53,7 @@ class LIB_EXPORT ShmRenderer : public Renderer {
//Getters //Getters
virtual int fps() const; virtual int fps() const;
virtual void* getFramePtr() const;
//Setters //Setters
void setShmPath(const QString& path); void setShmPath(const QString& path);
...@@ -64,9 +65,6 @@ class LIB_EXPORT ShmRenderer : public Renderer { ...@@ -64,9 +65,6 @@ class LIB_EXPORT ShmRenderer : public Renderer {
public Q_SLOTS: public Q_SLOTS:
void startRendering(); void startRendering();
void stopRendering (); void stopRendering ();
}; };
} }
......
...@@ -38,15 +38,10 @@ public: ...@@ -38,15 +38,10 @@ public:
//Attributes //Attributes
std::atomic_bool m_isRendering; std::atomic_bool m_isRendering;
QByteArray m_Frame[2] ;
bool m_FrameIdx ;
QMutex* m_pMutex ; QMutex* m_pMutex ;
QString m_Id ; QString m_Id ;
QSize m_pSize ; QSize m_pSize ;
void* m_framePtr ;
//Helpers
QByteArray& otherFrame () ;
void updateFrameIndex () ;
private: private:
Video::Renderer* q_ptr; Video::Renderer* q_ptr;
......
...@@ -23,8 +23,9 @@ ...@@ -23,8 +23,9 @@
//Qt //Qt
#include <QtCore/QMutex> #include <QtCore/QMutex>
Video::RendererPrivate::RendererPrivate(Video::Renderer* parent) : QObject(parent), q_ptr(parent), Video::RendererPrivate::RendererPrivate(Video::Renderer* parent)
m_pMutex(new QMutex()), m_FrameIdx(false) : QObject(parent), q_ptr(parent)
, m_pMutex(new QMutex())
{ {
} }
...@@ -49,19 +50,6 @@ bool Video::Renderer::isRendering() const ...@@ -49,19 +50,6 @@ bool Video::Renderer::isRendering() const
return d_ptr->m_isRendering; return d_ptr->m_isRendering;
} }
QByteArray& Video::RendererPrivate::otherFrame()
{
static QByteArray empty;
return q_ptr->isRendering()?m_Frame[!m_FrameIdx]:empty;
}
///Return the current framerate
const QByteArray& Video::Renderer::currentFrame() const
{
static QByteArray empty;
return isRendering()?d_ptr->m_Frame[d_ptr->m_FrameIdx]:empty;
}
///Get mutex, in case renderer and views are not in the same thread ///Get mutex, in case renderer and views are not in the same thread
QMutex* Video::Renderer::mutex() const QMutex* Video::Renderer::mutex() const
{ {
...@@ -74,6 +62,11 @@ QSize Video::Renderer::size() const ...@@ -74,6 +62,11 @@ QSize Video::Renderer::size() const
return d_ptr->m_pSize; return d_ptr->m_pSize;
} }
void* Video::Renderer::getFramePtr() const
{
return d_ptr->m_framePtr;
}
/***************************************************************************** /*****************************************************************************
* * * *
* Setters * * Setters *
...@@ -91,9 +84,9 @@ void Video::Renderer::setSize(const QSize& size) const ...@@ -91,9 +84,9 @@ void Video::Renderer::setSize(const QSize& size) const
d_ptr->m_pSize = size; d_ptr->m_pSize = size;
} }
void Video::RendererPrivate::updateFrameIndex() void Video::Renderer::setFramePtr(void* ptr) const
{ {
m_FrameIdx = !m_FrameIdx; d_ptr->m_framePtr = ptr;
} }
#include <renderer.moc> #include <renderer.moc>
...@@ -60,17 +60,17 @@ public: ...@@ -60,17 +60,17 @@ public:
//Getters //Getters
virtual bool isRendering () const; virtual bool isRendering () const;
virtual const QByteArray& currentFrame () const;
virtual QSize size () const; virtual QSize size () const;
virtual QMutex* mutex () const; virtual QMutex* mutex () const;
virtual void* getFramePtr () const;
//Setters //Setters
void setRendering(bool rendering) const; void setRendering(bool rendering) const;
void setSize(const QSize& size) const; void setSize(const QSize& size) const;
void setFramePtr(void* ptr) const;
Q_SIGNALS: Q_SIGNALS:
///Emitted when a new frame is ready void frameUpdated(); // Emitted when a new frame is ready
void frameUpdated();
void stopped (); void stopped ();
void started (); void started ();
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment