1
0
Fork 0
mirror of https://git.jami.net/savoirfairelinux/jami-client-qt.git synced 2025-07-24 01:15:32 +02:00
jami-client-qt/src/libclient/shmrenderer.cpp
Amin Bandali 8d46acedf1 misc: Update copyright years to 2023
Change-Id: Idf38e82631a4e22540aa5dec8ec2db0ab4a38c2e
2023-02-06 01:47:15 -05:00

340 lines
8.8 KiB
C++

/*
* Copyright (C) 2012-2023 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/>.
*/
#include "shmrenderer.h"
#include "dbus/videomanager.h"
#include "videomanager_interface.h"
#include <QDebug>
#include <QMutex>
#include <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 <QTimer>
#include <chrono>
namespace lrc {
using namespace api::video;
namespace video {
// 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
};
struct ShmRenderer::Impl final : public QObject
{
Q_OBJECT
public:
Impl(ShmRenderer* parent)
: QObject(nullptr)
, parent_(parent)
, fd(-1)
, shmArea((SHMHeader*) MAP_FAILED)
, shmAreaLen(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);
parent_->moveToThread(&thread);
connect(&thread, &QThread::finished, [this] { parent_->stopRendering(); });
thread.start();
};
~Impl()
{
thread.quit();
thread.wait();
}
// Constants
constexpr static const int FRAME_CHECK_RATE_HZ = 120;
// Lock the memory while the copy is being made
bool shmLock()
{
return ::sem_wait(&shmArea->mutex) >= 0;
};
// Remove the lock, allow a new frame to be drawn
void shmUnlock()
{
::sem_post(&shmArea->mutex);
};
// Wait for new frame data from shared memory and save pointer.
bool getNewFrame(bool wait)
{
if (!shmLock())
return false;
if (frameGen == shmArea->frameGen) {
shmUnlock();
if (not wait)
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;
if (!shmLock())
return false;
}
// valid frame to render (daemon may have stopped)?
if (!shmArea->frameSize) {
shmUnlock();
return false;
}
// map frame data
if (!remapShm()) {
qDebug() << "Could not resize shared memory";
return false;
}
if (not frame)
frame.reset(new lrc::api::video::Frame);
frame->ptr = shmArea->data + shmArea->readOffset;
frame->size = shmArea->frameSize;
frameGen = shmArea->frameGen;
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;
};
// Remap the shared memory.
// Shared memory is in an unlocked state if returns false (resize failed).
bool remapShm()
{
// This loop handles case where daemon resize shared memory
// during time we unlock it for remapping.
while (shmAreaLen != shmArea->mapSize) {
auto mapSize = shmArea->mapSize;
shmUnlock();
if (::munmap(shmArea, shmAreaLen)) {
qDebug() << "Could not unmap shared area: " << strerror(errno);
return false;
}
shmArea
= (SHMHeader*) ::mmap(nullptr, mapSize, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
if (shmArea == MAP_FAILED) {
qDebug() << "Could not remap shared area: " << strerror(errno);
return false;
}
if (!shmLock())
return false;
shmAreaLen = mapSize;
}
return true;
};
private:
ShmRenderer* parent_;
public:
QString path;
int fd;
SHMHeader* shmArea;
unsigned shmAreaLen;
uint frameGen;
int fpsC;
int fps;
std::chrono::time_point<std::chrono::system_clock> lastFrameDebug;
QTimer* timer;
QMutex mutex;
QThread thread;
std::shared_ptr<lrc::api::video::Frame> frame;
};
ShmRenderer::ShmRenderer(const QString& id, const QSize& res, const QString& shmPath)
: Renderer(id, res)
, pimpl_(std::make_unique<ShmRenderer::Impl>(this))
{
pimpl_->path = shmPath;
}
ShmRenderer::~ShmRenderer()
{
VideoManager::instance().startShmSink(id(), false);
stopShm();
}
Frame
ShmRenderer::currentFrame() const
{
QMutexLocker lk {&pimpl_->mutex};
if (pimpl_->getNewFrame(false)) {
if (auto frame_ptr = pimpl_->frame)
return std::move(*frame_ptr);
}
return {};
}
bool
ShmRenderer::startShm()
{
if (pimpl_->fd != -1) {
qWarning() << "fd must be -1";
return false;
}
pimpl_->fd = ::shm_open(pimpl_->path.toLatin1(), O_RDWR, 0);
if (pimpl_->fd < 0) {
qWarning() << "could not open shm area" << pimpl_->path
<< ", shm_open failed:" << strerror(errno);
return false;
}
// Map only header data
const auto mapSize = sizeof(SHMHeader);
pimpl_->shmArea
= (SHMHeader*) ::mmap(nullptr, mapSize, PROT_READ | PROT_WRITE, MAP_SHARED, pimpl_->fd, 0);
if (pimpl_->shmArea == MAP_FAILED) {
qWarning() << "Could not remap shared area";
return false;
}
pimpl_->shmAreaLen = mapSize;
return true;
}
void
ShmRenderer::stopShm()
{
if (pimpl_->fd < 0)
return;
pimpl_->timer->stop();
// Emit the signal before closing the file, this lower the risk of invalid
// memory access
Q_EMIT stopped();
{
QMutexLocker lk(&pimpl_->mutex);
// reset the frame so it doesn't point to an old value
pimpl_->frame.reset();
}
::close(pimpl_->fd);
pimpl_->fd = -1;
if (pimpl_->shmArea == MAP_FAILED)
return;
::munmap(pimpl_->shmArea, pimpl_->shmAreaLen);
pimpl_->shmAreaLen = 0;
pimpl_->shmArea = (SHMHeader*) MAP_FAILED;
}
void
ShmRenderer::startRendering()
{
QMutexLocker lk(&pimpl_->mutex);
if (!startShm())
return;
pimpl_->timer->start();
Q_EMIT started(size());
}
// Done on destroy instead
void
ShmRenderer::stopRendering()
{}
} // namespace video
} // namespace lrc
#include "moc_shmrenderer.cpp"
#include "shmrenderer.moc"