Skip to content
Snippets Groups Projects
Select Git revision
  • 68a9abcb58d85fcfb0cfb3d937a33eef7e7d3dfc
  • master default protected
  • release/202005
  • release/202001
  • release/201912
  • release/201911
  • release/releaseWindowsTestOne
  • release/releaseTest
  • release/releaseWindowsTest
  • release/windowsReleaseTest
  • release/201910
  • release/qt/201910
  • release/windows-test/201910
  • release/201908
  • release/201906
  • release/201905
  • release/201904
  • release/201903
  • release/201902
  • release/201901
  • release/201812
  • 1.0.0
  • 0.3.0
  • 0.2.1
  • 0.2.0
  • 0.1.0
26 results

videorenderer.cpp

Blame
  • Code owners
    Assign users and groups as approvers for specific file changes. Learn more.
    videorenderer.cpp 10.20 KiB
    /****************************************************************************
     *   Copyright (C) 2012-2014 by Savoir-Faire Linux                          *
     *   Author : Emmanuel Lepage Vallee <emmanuel.lepage@savoirfairelinux.com> *
     *                                                                          *
     *   This library is free software; you can redistribute it and/or          *
     *   modify it under the terms of the GNU Lesser General Public             *
     *   License as published by the Free Software Foundation; either           *
     *   version 2.1 of the License, or (at your option) any later version.     *
     *                                                                          *
     *   This library is distributed in the hope that it will be useful,        *
     *   but WITHOUT ANY WARRANTY; without even the implied warranty of         *
     *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU      *
     *   Lesser General Public License for more details.                        *
     *                                                                          *
     *   You should have received a copy of the GNU General Public License      *
     *   along with this program.  If not, see <http://www.gnu.org/licenses/>.  *
     ***************************************************************************/
    #include "videorenderer.h"
    
    #include <QtCore/QDebug>
    #include <QtCore/QMutex>
    #include <QtCore/QThread>
    
    #include <sys/ipc.h>
    #include <sys/sem.h>
    #include <sys/shm.h>
    #include <fcntl.h>
    #include <unistd.h>
    #include <sys/mman.h>
    #include <semaphore.h>
    #include <errno.h>
    
    
    #ifndef CLOCK_REALTIME
    #define CLOCK_REALTIME 0
    #endif
    
    #include <QtCore/QTimer>
    #include "videomodel.h"
    
    ///Shared memory object
    struct SHMHeader{
       sem_t notification;
       sem_t mutex;
    
       unsigned m_BufferGen;
       int m_BufferSize;
       /* The header will be aligned on 16-byte boundaries */
       char padding[8];
    
    #pragma GCC diagnostic push
    #pragma GCC diagnostic ignored "-pedantic"
       char m_Data[];
    #pragma GCC diagnostic pop
    };
    
    ///Constructor
    VideoRenderer::VideoRenderer(const QString& id, const QString& shmPath, Resolution res): QObject(nullptr),
       m_Width(res.width()), m_Height(res.height()), m_ShmPath(shmPath), fd(-1),
       m_pShmArea((SHMHeader*)MAP_FAILED), m_ShmAreaLen(0), m_BufferGen(0),
       m_isRendering(false),m_pTimer(nullptr),m_Res(res),m_pMutex(new QMutex()),
       m_Id(id),m_FrameIdx(false),m_pSSMutex(new QMutex())
    {
       setObjectName("VideoRenderer:"+id);
    }
    
    ///Destructor
    VideoRenderer::~VideoRenderer()
    {
       stopShm();
       //delete m_pShmArea;
    }
    
    ///Get the data from shared memory and transform it into a QByteArray
    bool VideoRenderer::renderToBitmap()
    {
    #ifdef Q_OS_LINUX
       if (!m_isRendering) {
          return false;
       }
    
       if (!shmLock()) {
          return true;
       }
    
       // wait for a new buffer
       while (m_BufferGen == m_pShmArea->m_BufferGen) {
          shmUnlock();
    
          if(!VideoModel::instance()->startStopMutex()->tryLock())
             return false;
          int err = sem_trywait(&m_pShmArea->notification);
          VideoModel::instance()->startStopMutex()->unlock();
          // Useful for debugging
    //       switch (errno ) {
    //          case EINTR:
    //             qDebug() << "Unlock failed: Interrupted function call (POSIX.1); see signal(7)";
    //             ok = false;
    //             return QByteArray();
    //             break;
    //          case EINVAL:
    //             qDebug() << "Unlock failed: Invalid argument (POSIX.1)";
    //             ok = false;
    //             return QByteArray();
    //             break;
    //          case EAGAIN:
    //             qDebug() << "Unlock failed: Resource temporarily unavailable (may be the same value as EWOULDBLOCK) (POSIX.1)";
    //             ok = false;
    //             return QByteArray();
    //             break;
    //          case ETIMEDOUT:
    //             qDebug() << "Unlock failed: Connection timed out (POSIX.1)";
    //             ok = false;
    //             return QByteArray();
    //             break;
    //       }
          if (err < 0) {
             return errno == EAGAIN;
          }
    
          if (!shmLock()) {
             return true;
          }
       }
    
       if (!resizeShm()) {
          qDebug() << "Could not resize shared memory";
          return false;
       }
    
       bool otherFrame = ! m_FrameIdx;
       if (m_Frame[otherFrame].size() != m_pShmArea->m_BufferSize)
          m_Frame[otherFrame].resize(m_pShmArea->m_BufferSize);
       memcpy(m_Frame[otherFrame].data(),m_pShmArea->m_Data,m_pShmArea->m_BufferSize);
       m_BufferGen = m_pShmArea->m_BufferGen;
       shmUnlock();
       m_FrameIdx = !m_FrameIdx;
    //    return data;
    //    m_pSSMutex->unlock();
       return true;
    #else
       return false;
    #endif
    }
    
    ///Connect to the shared memory
    bool VideoRenderer::startShm()
    {
       if (fd != -1) {
          qDebug() << "fd must be -1";
          return false;
       }
    
       fd = shm_open(m_ShmPath.toAscii(), O_RDWR, 0);
       if (fd < 0) {
          qDebug() << "could not open shm area " << m_ShmPath << ", shm_open failed:" << strerror(errno);
          return false;
       }
       m_ShmAreaLen = sizeof(SHMHeader);
       #pragma GCC diagnostic push
       #pragma GCC diagnostic ignored "-Wzero-as-null-pointer-constant"
       m_pShmArea = (SHMHeader*) mmap(NULL, m_ShmAreaLen, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
       #pragma GCC diagnostic pop
       if (m_pShmArea == MAP_FAILED) {
          qDebug() << "Could not map shm area, mmap failed";
          return false;
       }
       emit started();
       return true;
    }
    
    ///Disconnect from the shared memory
    void VideoRenderer::stopShm()
    {
       if (fd >= 0)
          close(fd);
       fd = -1;
    
       if (m_pShmArea != MAP_FAILED)
          munmap(m_pShmArea, m_ShmAreaLen);
       m_ShmAreaLen = 0;
       m_pShmArea = (SHMHeader*) MAP_FAILED;
    }
    
    ///Resize the shared memory
    bool VideoRenderer::resizeShm()
    {
       while (( (unsigned int) sizeof(SHMHeader) + (unsigned int) m_pShmArea->m_BufferSize) > (unsigned int) m_ShmAreaLen) {
          const size_t new_size = sizeof(SHMHeader) + m_pShmArea->m_BufferSize;
    
          shmUnlock();
          if (munmap(m_pShmArea, m_ShmAreaLen)) {
                qDebug() << "Could not unmap shared area:" << strerror(errno);
                return false;
          }
    
          #pragma GCC diagnostic push
          #pragma GCC diagnostic ignored "-Wzero-as-null-pointer-constant"
          m_pShmArea = (SHMHeader*) mmap(NULL, new_size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
          #pragma GCC diagnostic pop
          m_ShmAreaLen = new_size;
    
          if (!m_pShmArea) {
                m_pShmArea = nullptr;
                qDebug() << "Could not remap shared area";
                return false;
          }
    
          m_ShmAreaLen = new_size;
          if (!shmLock())
                return true;
       }
       return true;
    }
    
    ///Lock the memory while the copy is being made
    bool VideoRenderer::shmLock()
    {
    #ifdef Q_OS_LINUX
       return sem_trywait(&m_pShmArea->mutex) >= 0;
    #else
       return false;
    #endif
    }
    
    ///Remove the lock, allow a new frame to be drawn
    void VideoRenderer::shmUnlock()
    {
       sem_post(&m_pShmArea->mutex);
    }
    
    
    /*****************************************************************************
     *                                                                           *
     *                                   Slots                                   *
     *                                                                           *
     ****************************************************************************/
    
    ///Update the buffer
    void VideoRenderer::timedEvents()
    {
    
       bool ok = renderToBitmap();
    
       if (ok == true) {
    
          //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 frameUpdated();
       }
       else {
          qDebug() << "Frame dropped";
          usleep(rand()%1000); //Be sure it can come back in sync
       }
    }
    
    ///Start the rendering loop
    void VideoRenderer::startRendering()
    {
       VideoModel::instance()->startStopMutex()->lock();
       QMutexLocker locker(m_pMutex);
       startShm();
       if (!m_pTimer) {
          m_pTimer = new QTimer(nullptr);
    
    //       m_pTimer->moveToThread(thread());
          connect(m_pTimer,SIGNAL(timeout()),this,SLOT(timedEvents()));
          m_pTimer->setInterval(30);
       }
    
       if (!m_pTimer->isActive()) {
          qDebug() << "Is running" << thread()->isRunning();
          m_pTimer->start();
       }
       else
          qDebug() << "Timer already started!";
    
       m_isRendering = true;
       VideoModel::instance()->startStopMutex()->unlock();
    }
    
    ///Stop the rendering loop
    void VideoRenderer::stopRendering()
    {
       VideoModel::instance()->startStopMutex()->lock();
       QMutexLocker locker(m_pMutex);
       m_isRendering = false;
       qDebug() << "Stopping rendering on" << m_Id;
       if (m_pTimer)
          m_pTimer->stop();
       emit stopped();
       stopShm();
       VideoModel::instance()->startStopMutex()->unlock();
    }
    
    
    /*****************************************************************************
     *                                                                           *
     *                                 Getters                                   *
     *                                                                           *
     ****************************************************************************/
    
    ///Get the raw bytes directly from the SHM, not recommended, but optimal
    const char* VideoRenderer::rawData()
    {
       return m_isRendering?m_Frame[m_FrameIdx].data():nullptr;
    }
    
    ///Is this redenrer active
    bool VideoRenderer::isRendering()
    {
       return m_isRendering;
    }
    
    ///Return the current framerate
    QByteArray VideoRenderer::currentFrame()
    {
       return m_Frame[m_FrameIdx];
    }
    
    ///Return the current resolution
    Resolution VideoRenderer::activeResolution()
    {
       return m_Res;
    }
    
    ///Get mutex, in case renderer and views are not in the same thread
    QMutex* VideoRenderer::mutex()
    {
       return m_pMutex;
    }
    
    ///Get the current frame rate of this renderer
    int VideoRenderer::fps() const
    {
       return m_Fps;
    }
    
    
    /*****************************************************************************
     *                                                                           *
     *                                 Setters                                   *
     *                                                                           *
     ****************************************************************************/
    
    void VideoRenderer::setResolution(QSize size)
    {
       m_Res = size;
       m_Width = size.width();
       m_Height = size.height();
    }
    
    void VideoRenderer::setShmPath(QString path)
    {
       m_ShmPath = path;
    }