Select Git revision
sinkclient.cpp
Philippe Gorley authored and
Adrien Béraud
committed
Adds possibility to keep the hardware frame reference on the receiver side instead of immediately transferring it to main memory. Components that require software frames were updated to transfer the frame back to main memory. Change-Id: Idb9ecb64fdefedb9db160ec93592d7a047d356e8
Code owners
Assign users and groups as approvers for specific file changes. Learn more.
sinkclient.cpp 10.70 KiB
/*
* Copyright (C) 2012-2019 Savoir-faire Linux Inc.
*
* Author: Tristan Matthews <tristan.matthews@savoirfairelinux.com>
* Author: Guillaume Roguez <guillaume.roguez@savoirfairelinux.com>
* Author: Alexandre Lision <alexandre.lision@savoirfairelinux.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This program 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include "sinkclient.h"
#if HAVE_SHM
#include "shm_header.h"
#endif // HAVE_SHM
#include "media_buffer.h"
#include "logger.h"
#include "noncopyable.h"
#include "client/ring_signal.h"
#include "dring/videomanager_interface.h"
#include "libav_utils.h"
#include "video_scaler.h"
#include "smartools.h"
#ifdef RING_ACCEL
#include "accel.h"
#endif
#ifndef _WIN32
#include <sys/mman.h>
#endif
#include <ciso646> // fix windows compiler bug
#include <fcntl.h>
#include <cstdio>
#include <sstream>
#include <unistd.h>
#include <cerrno>
#include <cstring>
#include <stdexcept>
namespace ring { namespace video {
#if HAVE_SHM
// RAII class helper on sem_wait/sem_post sempahore operations
class SemGuardLock {
public:
explicit SemGuardLock(sem_t& mutex) : m_(mutex) {
auto ret = ::sem_wait(&m_);
if (ret < 0) {
std::ostringstream msg;
msg << "SHM mutex@" << &m_
<< " lock failed (" << ret << ")";
throw std::logic_error {msg.str()};
}
}
~SemGuardLock() {
::sem_post(&m_);
}
private:
sem_t& m_;
};
class ShmHolder
{
public:
ShmHolder(const std::string& name={});
~ShmHolder();
std::string name() const noexcept {
return openedName_;
}
void renderFrame(const VideoFrame& src) noexcept;
private:
bool resizeArea(std::size_t desired_length) noexcept;
char* getShmAreaDataPtr() noexcept;
void unMapShmArea() noexcept {
if (area_ != MAP_FAILED and ::munmap(area_, areaSize_) < 0) {
RING_ERR("ShmHolder[%s]: munmap(%zu) failed with errno %d",
openedName_.c_str(), areaSize_, errno);
}
}
SHMHeader* area_ {static_cast<SHMHeader*>(MAP_FAILED)};
std::size_t areaSize_ {0};
std::string openedName_;
int fd_ {-1};
};
ShmHolder::ShmHolder(const std::string& name)
{
static constexpr int flags = O_RDWR | O_CREAT | O_TRUNC | O_EXCL;
static constexpr int perms = S_IRUSR | S_IWUSR;
static auto shmFailedWithErrno = [this](const std::string& what) {
std::ostringstream msg;
msg << "ShmHolder[" << openedName_ << "]: "
<< what << " failed, errno=" << errno;
throw std::runtime_error {msg.str()};
};
if (not name.empty()) {
openedName_ = name;
fd_ = ::shm_open(openedName_.c_str(), flags, perms);
if (fd_ < 0)
shmFailedWithErrno("shm_open");
} else {
for (int i = 0; fd_ < 0; ++i) {
std::ostringstream tmpName;
tmpName << PACKAGE_NAME << "_shm_" << getpid() << "_" << i;
openedName_ = tmpName.str();
fd_ = ::shm_open(openedName_.c_str(), flags, perms);
if (fd_ < 0 and errno != EEXIST)
shmFailedWithErrno("shm_open");
}
}
// Set size enough for header only (no frame data)
if (!resizeArea(0))
shmFailedWithErrno("resizeArea");
// Header fields initialization
std::memset(area_, 0, areaSize_);
if (::sem_init(&area_->mutex, 1, 1) < 0)
shmFailedWithErrno("sem_init(mutex)");
if (::sem_init(&area_->frameGenMutex, 1, 0) < 0)
shmFailedWithErrno("sem_init(frameGenMutex)");
RING_DBG("ShmHolder: new holder '%s'", openedName_.c_str());
}
ShmHolder::~ShmHolder()
{
if (fd_ < 0)
return;
::close(fd_);
::shm_unlink(openedName_.c_str());
if (area_ == MAP_FAILED)
return;
::sem_wait(&area_->mutex);
area_->frameSize = 0;
::sem_post(&area_->mutex);
::sem_post(&area_->frameGenMutex); // unlock waiting client before leaving
unMapShmArea();
}
bool
ShmHolder::resizeArea(std::size_t frameSize) noexcept
{
// aligned on 16-byte boundary frameSize
frameSize = (frameSize + 15) & ~15;
if (area_ != MAP_FAILED and frameSize == area_->frameSize)
return true;
// full area size: +15 to take care of maximum padding size
const auto areaSize = sizeof(SHMHeader) + 2 * frameSize + 15;
RING_DBG("ShmHolder[%s]: new sizes: f=%zu, a=%zu", openedName_.c_str(),
frameSize, areaSize);
unMapShmArea();
if (::ftruncate(fd_, areaSize) < 0) {
RING_ERR("ShmHolder[%s]: ftruncate(%zu) failed with errno %d",
openedName_.c_str(), areaSize, errno);
return false;
}
area_ = static_cast<SHMHeader*>(::mmap(nullptr, areaSize,
PROT_READ | PROT_WRITE,
MAP_SHARED, fd_, 0));
if (area_ == MAP_FAILED) {
areaSize_ = 0;
RING_ERR("ShmHolder[%s]: mmap(%zu) failed with errno %d",
openedName_.c_str(), areaSize, errno);
return false;
}
areaSize_ = areaSize;
if (frameSize) {
SemGuardLock lk {area_->mutex};
area_->frameSize = frameSize;
area_->mapSize = areaSize;
// Compute aligned IO pointers
// Note: we not using std::align as not implemented in 4.9
// https://gcc.gnu.org/bugzilla/show_bug.cgi?id=57350
auto p = reinterpret_cast<std::uintptr_t>(area_->data);
area_->writeOffset = ((p + 15) & ~15) - p;
area_->readOffset = area_->writeOffset + frameSize;
}
return true;
}
void
ShmHolder::renderFrame(const VideoFrame& src) noexcept
{
const auto width = src.width();
const auto height = src.height();
const auto format = AV_PIX_FMT_BGRA;
const auto frameSize = videoFrameSize(format, width, height);
if (!resizeArea(frameSize)) {
RING_ERR("ShmHolder[%s]: could not resize area",
openedName_.c_str());
return;
}
{
VideoFrame dst;
VideoScaler scaler;
dst.setFromMemory(area_->data + area_->writeOffset, format, width, height);
scaler.scale(src, dst);
}
{
SemGuardLock lk {area_->mutex};
++area_->frameGen;
std::swap(area_->readOffset, area_->writeOffset);
::sem_post(&area_->frameGenMutex);
}
}
std::string
SinkClient::openedName() const noexcept
{
if (shm_)
return shm_->name();
return {};
}
bool
SinkClient::start() noexcept
{
if (not shm_) {
try {
shm_ = std::make_shared<ShmHolder>();
} catch (const std::runtime_error& e) {
RING_ERR("SHMHolder ctor failure: %s", e.what());
}
}
return static_cast<bool>(shm_);
}
bool
SinkClient::stop() noexcept
{
setFrameSize(0, 0);
shm_.reset();
return true;
}
#else // HAVE_SHM
std::string
SinkClient::openedName() const noexcept
{
return {};
}
bool
SinkClient::start() noexcept
{
return true;
}
bool
SinkClient::stop() noexcept
{
setFrameSize(0, 0);
return true;
}
#endif // !HAVE_SHM
SinkClient::SinkClient(const std::string& id, bool mixer)
: id_ {id}
, mixer_(mixer)
, scaler_(new VideoScaler())
#ifdef DEBUG_FPS
, frameCount_(0u)
, lastFrameDebug_(std::chrono::system_clock::now())
#endif
{}
void
SinkClient::update(Observable<std::shared_ptr<MediaFrame>>* /*obs*/,
const std::shared_ptr<MediaFrame>& frame_p)
{
auto& f = *std::static_pointer_cast<VideoFrame>(frame_p);
#ifdef DEBUG_FPS
auto currentTime = std::chrono::system_clock::now();
const std::chrono::duration<double> seconds = currentTime - lastFrameDebug_;
++frameCount_;
if (seconds.count() > 1) {
std::ostringstream fps;
fps << frameCount_ / seconds.count();
// Send the framerate in smartInfo
Smartools::getInstance().setFrameRate(id_, fps.str());
frameCount_ = 0;
lastFrameDebug_ = currentTime;
}
#endif
if (avTarget_.push) {
auto outFrame = std::make_unique<VideoFrame>();
outFrame->copyFrom(f);
avTarget_.push(std::move(outFrame));
}
bool doTransfer = (target_.pull != nullptr);
#if HAVE_SHM
doTransfer |= (shm_ != nullptr);
#endif
if (doTransfer) {
#ifdef RING_ACCEL
auto framePtr = transferToMainMemory(f, AV_PIX_FMT_NV12);
const auto& swFrame = *framePtr;
#else
const auto& swFrame = f;
#endif
#if HAVE_SHM
shm_->renderFrame(swFrame);
#endif
if (target_.pull) {
VideoFrame dst;
const int width = swFrame.width();
const int height = swFrame.height();
#if defined(__ANDROID__) || (defined(__APPLE__) && !TARGET_OS_IPHONE)
const int format = AV_PIX_FMT_RGBA;
#else
const int format = AV_PIX_FMT_BGRA;
#endif
const auto bytes = videoFrameSize(format, width, height);
if (bytes > 0) {
if (auto buffer_ptr = target_.pull(bytes)) {
buffer_ptr->format = format;
buffer_ptr->width = width;
buffer_ptr->height = height;
dst.setFromMemory(buffer_ptr->ptr, format, width, height);
scaler_->scale(swFrame, dst);
target_.push(std::move(buffer_ptr));
}
}
}
}
}
void
SinkClient::setFrameSize(int width, int height)
{
width_ = width;
height_ = height;
if (width > 0 and height > 0) {
RING_WARN("Start sink <%s / %s>, size=%dx%d, mixer=%u",
getId().c_str(), openedName().c_str(), width, height, mixer_);
emitSignal<DRing::VideoSignal::DecodingStarted>(getId(), openedName(), width, height, mixer_);
started_ = true;
} else if (started_) {
RING_ERR("Stop sink <%s / %s>, mixer=%u",
getId().c_str(), openedName().c_str(), mixer_);
emitSignal<DRing::VideoSignal::DecodingStopped>(getId(), openedName(), mixer_);
started_ = false;
}
}
}} // namespace ring::video