Skip to content
Snippets Groups Projects
Select Git revision
  • 987811a540769ca54d8e8752d8fa29b23a7da264
  • 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

shmrenderer.cpp

Blame
  • Code owners
    Assign users and groups as approvers for specific file changes. Learn more.
    shmrenderer.cpp 11.19 KiB
    /****************************************************************************
     *    Copyright (C) 2012-2021 Savoir-faire Linux Inc.                          *
     *   Author : Emmanuel Lepage Vallee <emmanuel.lepage@savoirfairelinux.com> *
     *   Author : Guillaume Roguez <guillaume.roguez@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/>.  *
     ***************************************************************************/
    #ifndef ENABLE_LIBWRAP
    
    #include "shmrenderer.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 <chrono>
    
    #include "private/videorenderer_p.h"
    
    // Uncomment following line to output in console the FPS value
    //#define DEBUG_FPS
    
    /* Shared memory object
     * Implementation note: double-buffering
     * Shared memory is divided in two regions, each representing one frame.
     * First byte of each frame is warranted to by aligned on 16 bytes.
     * One region is marked readable: this region can be safely read.
     * The other region is writeable: only the producer can use it.
     */
    
    struct SHMHeader
    {
        sem_t mutex;          /*!< Lock it before any operations on following fields.           */
        sem_t frameGenMutex;  /*!< unlocked by producer when frameGen modified                  */
        unsigned frameGen;    /*!< monotonically incremented when a producer changes readOffset */
        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 writeOffset; /*!< offset of writable frame in data                             */
    
    #pragma GCC diagnostic push
    #pragma GCC diagnostic ignored "-Wpedantic"
        uint8_t data[]; /*!< the whole shared memory                                      */
    #pragma GCC diagnostic pop
    };
    
    namespace Video {
    
    class ShmRendererPrivate final : public QObject
    {
        Q_OBJECT
    
    public:
        ShmRendererPrivate(ShmRenderer* parent);
    
        // Types
        using TimePoint = std::chrono::time_point<std::chrono::system_clock>;
    
        // Attributes
        QString m_ShmPath;
        int m_fd;
        SHMHeader* m_pShmArea;
        unsigned m_ShmAreaLen;
        uint m_FrameGen;
        int m_fpsC;
        int m_Fps;
        TimePoint m_lastFrameDebug;
        QTimer* m_pTimer;
    
        // Constants
        constexpr static const int FPS_RATE_SEC = 1;
        constexpr static const int FRAME_CHECK_RATE_HZ = 120;
    
        // Helpers
        timespec createTimeout();
        bool shmLock();
        void shmUnlock();
        bool getNewFrame(bool wait);
        bool remapShm();
    
    private:
        Video::ShmRenderer* q_ptr;
    };
    
    ShmRendererPrivate::ShmRendererPrivate(ShmRenderer* parent)
        : QObject(parent)
        , q_ptr(parent)
        , m_fd(-1)
        , m_fpsC(0)
        , m_Fps(0)
        , m_pShmArea((SHMHeader*) MAP_FAILED)
        , m_ShmAreaLen(0)
        , m_FrameGen(0)
        , m_pTimer(nullptr)
    #ifdef DEBUG_FPS
        , m_frameCount(0)
        , m_lastFrameDebug(std::chrono::system_clock::now())
    #endif
    {}
    
    /// Constructor
    ShmRenderer::ShmRenderer(const QString& id, const QString& shmPath, const QSize& res)
        : Renderer(id, res)
        , d_ptr(std::make_unique<ShmRendererPrivate>(this))
    {
        d_ptr->m_ShmPath = shmPath;
        setObjectName("Video::Renderer:" + id);
    }
    
    /// Destructor
    ShmRenderer::~ShmRenderer()
    {
        stopShm();
    }
    
    /// Wait new frame data from shared memory and save pointer
    bool
    ShmRendererPrivate::getNewFrame(bool wait)
    {
        if (!shmLock())
            return false;
    
        if (m_FrameGen == m_pShmArea->frameGen) {
            shmUnlock();
    
            if (not wait)
                return false;
    
            // wait for a new frame, max 33ms
            static const struct timespec timeout = {0, 33000000};
            if (::sem_timedwait(&m_pShmArea->frameGenMutex, &timeout) < 0)
                return false;
    
            if (!shmLock())
                return false;
        }
    
        // valid frame to render (daemon may have stopped)?
        if (!m_pShmArea->frameSize) {
            shmUnlock();
            return false;
        }
    
        // map frame data
        if (!remapShm()) {
            qDebug() << "Could not resize shared memory";
            return false;
        }
    
        auto& frame_ptr = q_ptr->Video::Renderer::d_ptr->m_pFrame;
        if (not frame_ptr)
            frame_ptr.reset(new lrc::api::video::Frame);
        frame_ptr->storage.clear();
        frame_ptr->ptr = m_pShmArea->data + m_pShmArea->readOffset;
        frame_ptr->size = m_pShmArea->frameSize;
        m_FrameGen = m_pShmArea->frameGen;
    
        shmUnlock();
    
        ++m_fpsC;
    
        // Compute the FPS shown to the client
        auto currentTime = std::chrono::system_clock::now();
        const std::chrono::duration<double> seconds = currentTime - m_lastFrameDebug;
        if (seconds.count() >= FPS_RATE_SEC) {
            m_Fps = (int) (m_fpsC / seconds.count());
            m_fpsC = 0;
            m_lastFrameDebug = currentTime;
    #ifdef DEBUG_FPS
            qDebug() << this << ": FPS " << m_fps;
    #endif
        }
    
        return true;
    }
    
    /// Remap the shared memory
    /// Shared memory in unlocked state if returns false (resize failed).
    bool
    ShmRendererPrivate::remapShm()
    {
        // This loop handles case where daemon resize shared memory
        // during time we unlock it for remapping.
        while (m_ShmAreaLen != m_pShmArea->mapSize) {
            auto mapSize = m_pShmArea->mapSize;
            shmUnlock();
    
            if (::munmap(m_pShmArea, m_ShmAreaLen)) {
                qDebug() << "Could not unmap shared area: " << strerror(errno);
                return false;
            }
    
            m_pShmArea
                = (SHMHeader*) ::mmap(nullptr, mapSize, PROT_READ | PROT_WRITE, MAP_SHARED, m_fd, 0);
    
            if (m_pShmArea == MAP_FAILED) {
                qDebug() << "Could not remap shared area: " << strerror(errno);
                return false;
            }
    
            if (!shmLock())
                return false;
    
            m_ShmAreaLen = mapSize;
        }
    
        return true;
    }
    
    /// Connect to the shared memory
    bool
    ShmRenderer::startShm()
    {
        if (d_ptr->m_fd != -1) {
            qWarning() << "fd must be -1";
            return false;
        }
    
        d_ptr->m_fd = ::shm_open(d_ptr->m_ShmPath.toLatin1(), O_RDWR, 0);
    
        if (d_ptr->m_fd < 0) {
            qWarning() << "could not open shm area" << d_ptr->m_ShmPath
                       << ", shm_open failed:" << strerror(errno);
            return false;
        }
    
        // Map only header data
        const auto mapSize = sizeof(SHMHeader);
        d_ptr->m_pShmArea
            = (SHMHeader*) ::mmap(nullptr, mapSize, PROT_READ | PROT_WRITE, MAP_SHARED, d_ptr->m_fd, 0);
    
        if (d_ptr->m_pShmArea == MAP_FAILED) {
            qWarning() << "Could not remap shared area";
            return false;
        }
    
        d_ptr->m_ShmAreaLen = mapSize;
        return true;
    }
    
    /// Disconnect from the shared memory
    void
    ShmRenderer::stopShm()
    {
        if (d_ptr->m_fd < 0)
            return;
    
        Video::Renderer::d_ptr->m_isRendering = false;
    
        if (d_ptr->m_pTimer) {
            d_ptr->m_pTimer->stop();
            d_ptr->m_pTimer = nullptr;
        }
    
        // Emit the signal before closing the file, this lower the risk of invalid
        // memory access
        emit stopped();
    
        {
            QMutexLocker lk {mutex()};
            // reset the frame so it doesn't point to an old value
            Video::Renderer::d_ptr->m_pFrame.reset();
        }
    
        ::close(d_ptr->m_fd);
        d_ptr->m_fd = -1;
    
        if (d_ptr->m_pShmArea == MAP_FAILED)
            return;
    
        ::munmap(d_ptr->m_pShmArea, d_ptr->m_ShmAreaLen);
        d_ptr->m_ShmAreaLen = 0;
        d_ptr->m_pShmArea = (SHMHeader*) MAP_FAILED;
    }
    
    /// Lock the memory while the copy is being made
    bool
    ShmRendererPrivate::shmLock()
    {
        return ::sem_wait(&m_pShmArea->mutex) >= 0;
    }
    
    /// Remove the lock, allow a new frame to be drawn
    void
    ShmRendererPrivate::shmUnlock()
    {
        ::sem_post(&m_pShmArea->mutex);
    }
    
    /*****************************************************************************
     *                                                                           *
     *                                   Slots                                   *
     *                                                                           *
     ****************************************************************************/
    
    /// Start the rendering loop
    void
    ShmRenderer::startRendering()
    {
        QMutexLocker locker {mutex()};
    
        if (!startShm() || Video::Renderer::d_ptr->m_isRendering)
            return;
    
        Video::Renderer::d_ptr->m_isRendering = true;
    
        if (!d_ptr->m_pTimer) {
            d_ptr->m_pTimer = new QTimer(this);
            d_ptr->m_pTimer->setInterval(33);
            connect(d_ptr->m_pTimer, &QTimer::timeout, [this]() { emit this->frameUpdated(); });
        }
        // FIXME This is a temporary hack as frameUpdated() is no longer emitted
        d_ptr->m_pTimer->start();
    
        emit started();
    }
    
    /// Done on destroy instead
    void
    ShmRenderer::stopRendering()
    {}
    
    /*****************************************************************************
     *                                                                           *
     *                                 Getters                                   *
     *                                                                           *
     ****************************************************************************/
    
    /// Get the current frame rate of this renderer
    int
    ShmRenderer::fps() const
    {
        return d_ptr->m_Fps;
    }
    
    /// Get frame data pointer from shared memory
    lrc::api::video::Frame
    ShmRenderer::currentFrame() const
    {
        if (not isRendering())
            return {};
    
        QMutexLocker lk {mutex()};
        if (d_ptr->getNewFrame(false)) {
            if (auto frame_ptr = Video::Renderer::d_ptr->m_pFrame)
                return std::move(*frame_ptr);
        }
        return {};
    }
    
    Video::Renderer::ColorSpace
    ShmRenderer::colorSpace() const
    {
        return Video::Renderer::ColorSpace::BGRA;
    }
    
    /*****************************************************************************
     *                                                                           *
     *                                 Setters                                   *
     *                                                                           *
     ****************************************************************************/
    
    void
    ShmRenderer::setShmPath(const QString& path)
    {
        d_ptr->m_ShmPath = path;
    }
    
    } // namespace Video
    
    #include <shmrenderer.moc>
    
    #endif