Commit ef9c6272 authored by Guillaume Roguez's avatar Guillaume Roguez Committed by Alexandre Lision

video: implement frame data direct access and make shm optional

This patchset permits OSX client access.
This one doesn't use shared memory and requiers a direct access
to video frame.
This is done by a client side callback.

Refs #67977

Change-Id: Ib6780efe4beeab027bb868d3a124d2cec59de915
Signed-off-by: default avatarAlexandre Lision <alexandre.lision@savoirfairelinux.com>
parent df01eedd
......@@ -543,6 +543,9 @@ AS_IF([test "x$with_upnp" = "xyes"],
AC_DEFINE([HAVE_LIBUPNP], 0, [Define if you have libupnp])])
])
AC_DEFINE_UNQUOTED([HAVE_SHM], `if test -z "${HAVE_LINUX_TRUE}"; then echo 1; else echo 0; fi`,
[Define if you have shared memory support])
# DOXYGEN
# required dependency(ies): doxygen
# check for doxygen, mostly stolen from http://log4cpp.sourceforge.net/
......
......@@ -40,6 +40,12 @@
#include "manager.h"
#include "system_codec_container.h"
#include "client/signal.h"
#include "video/sinkclient.h"
#include <functional>
#include <memory>
#include <string>
#include <vector>
namespace DRing {
......@@ -142,6 +148,29 @@ hasCameraStarted()
return videoManager.started;
}
template <class T>
static void
registerSinkTarget_(const std::string& sinkId, T&& cb)
{
if (auto sink = ring::Manager::instance().getSinkClient(sinkId))
sink->registerTarget(std::forward<T>(cb));
else
RING_WARN("No sink found for id '%s'", sinkId.c_str());
}
void
registerSinkTarget(const std::string& sinkId,
const std::function<void(unsigned char*)>& cb)
{
registerSinkTarget_(sinkId, cb);
}
void
registerSinkTarget(const std::string& sinkId,
std::function<void(unsigned char*)>&& cb)
{
registerSinkTarget_(sinkId, cb);
}
} // namespace DRing
......
......@@ -74,6 +74,8 @@ void stopCamera();
bool hasCameraStarted();
bool switchInput(const std::string& resource);
bool switchToCamera();
void registerSinkTarget(const std::string& sinkId, const std::function<void(unsigned char*)>& cb);
void registerSinkTarget(const std::string& sinkId, std::function<void(unsigned char*)>&& cb);
} // namespace DRing
......
......@@ -83,6 +83,9 @@
#include "gnutls_support.h"
#endif
#include "libav_utils.h"
#include "video/sinkclient.h"
#include <cerrno>
#include <algorithm>
#include <ctime>
......@@ -2853,4 +2856,30 @@ ManagerImpl::newOutgoingCall(const std::string& toUrl,
return account->newOutgoingCall(finalToUrl);
}
std::shared_ptr<video::SinkClient>
ManagerImpl::createSinkClient(const std::string& id)
{
const auto& iter = sinkMap_.find(id);
if (iter != std::end(sinkMap_)) {
if (iter->second.expired())
sinkMap_.erase(iter);
else
return nullptr;
}
auto sink = std::make_shared<video::SinkClient>(id);
sinkMap_.emplace(id, sink);
return sink;
}
std::shared_ptr<video::SinkClient>
ManagerImpl::getSinkClient(const std::string& id)
{
const auto& iter = sinkMap_.find(id);
if (iter != std::end(sinkMap_))
if (auto sink = iter->second.lock())
return sink;
return nullptr;
}
} // namespace ring
......@@ -72,6 +72,10 @@ namespace tls {
class GnuTlsGlobalInit;
}
namespace video {
class SinkClient;
}
class PluginManager;
class AudioFile;
class DTMF;
......@@ -951,6 +955,10 @@ class ManagerImpl {
void addTask(const std::function<bool()>&& task);
std::shared_ptr<video::SinkClient> createSinkClient(const std::string& id="");
std::shared_ptr<video::SinkClient> getSinkClient(const std::string& id);
private:
NON_COPYABLE(ManagerImpl);
......@@ -991,6 +999,9 @@ class ManagerImpl {
std::unique_ptr<IceTransportFactory> ice_tf_;
std::unique_ptr<tls::GnuTlsGlobalInit> gnutlGIG_;
/* Sink ID mapping */
std::map<std::string, std::weak_ptr<video::SinkClient>> sinkMap_;
};
} // namespace ring
......
/*
* Copyright (C) 2012-2015 Savoir-Faire Linux Inc.
* Author: Tristan Matthews <tristan.matthews@savoirfairelinux.com>
* Author: Guillaume Roguez <guillaume.roguez@savoirfairelinux.com>
*
* Portions derived from GStreamer:
* Copyright (C) <2009> Collabora Ltd
......@@ -38,11 +39,15 @@
#endif
#include "sinkclient.h"
#if HAVE_SHM
#include "shm_header.h"
#endif // HAVE_SHM
#include "video_scaler.h"
#include "media_buffer.h"
#include "logger.h"
#include "noncopyable.h"
#include <sys/mman.h>
#include <fcntl.h>
......@@ -51,54 +56,85 @@
#include <unistd.h>
#include <cerrno>
#include <cstring>
#include <stdexcept>
namespace ring { namespace video {
SinkClient::SinkClient(const std::string &shm_name) :
shm_name_(shm_name)
, fd_(-1)
, shm_area_(static_cast<SHMHeader*>(MAP_FAILED))
, shm_area_len_(0)
, opened_name_()
#ifdef DEBUG_FPS
, frameCount_(0u)
, lastFrameDebug_(std::chrono::system_clock::now())
#endif
{}
#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_;
};
SinkClient::~SinkClient()
class ShmHolder
{
stop();
}
public:
ShmHolder(const std::string& shm_name);
~ShmHolder();
bool
SinkClient::start()
std::string openedName() const noexcept {
return opened_name_;
}
void render_frame(VideoFrame& src);
private:
bool resize_area(std::size_t desired_length);
void alloc_area(std::size_t desired_length) noexcept;
std::string shm_name_;
std::string opened_name_;
std::size_t shm_area_len_ {0};
SHMHeader* shm_area_ {static_cast<SHMHeader*>(MAP_FAILED)};
int fd_ {-1};
};
void
ShmHolder::alloc_area(std::size_t desired_length) noexcept
{
if (fd_ != -1) {
RING_ERR("fd must be -1");
return false;
}
shm_area_ = static_cast<SHMHeader*>(::mmap(nullptr, desired_length,
PROT_READ | PROT_WRITE,
MAP_SHARED, fd_, 0));
}
const int flags = O_RDWR | O_CREAT | O_TRUNC | O_EXCL;
const int perms = S_IRUSR | S_IWUSR;
ShmHolder::ShmHolder(const std::string& shm_name) : shm_name_ {shm_name}
{
static constexpr int flags = O_RDWR | O_CREAT | O_TRUNC | O_EXCL;
static constexpr int perms = S_IRUSR | S_IWUSR;
if (not shm_name_.empty()) {
fd_ = shm_open(shm_name_.c_str(), flags, perms);
fd_ = ::shm_open(shm_name_.c_str(), flags, perms);
if (fd_ < 0) {
RING_ERR("could not open shm area \"%s\"", shm_name_.c_str());
strErr();
return false;
std::ostringstream msg;
msg << "could not open shm area \""
<< shm_name_.c_str()
<< "\"";
throw std::runtime_error {msg.str()};
}
} else {
for (int i = 0; fd_ < 0; ++i) {
std::ostringstream name;
name << PACKAGE_NAME << "_shm_" << getpid() << "_" << i;
shm_name_ = name.str();
fd_ = shm_open(shm_name_.c_str(), flags, perms);
if (fd_ < 0 and errno != EEXIST) {
strErr();
return false;
}
fd_ = ::shm_open(shm_name_.c_str(), flags, perms);
if (fd_ < 0 and errno != EEXIST)
throw std::runtime_error {"shm_open() failed"};
}
}
......@@ -107,106 +143,73 @@ SinkClient::start()
shm_area_len_ = sizeof(SHMHeader);
if (ftruncate(fd_, shm_area_len_)) {
if (::ftruncate(fd_, shm_area_len_)) {
RING_ERR("Could not make shm area large enough for header");
strErr();
return false;
throw std::runtime_error {"could not make shm area large enough for header"};
}
shm_area_ = static_cast<SHMHeader*>(mmap(NULL, shm_area_len_,
PROT_READ | PROT_WRITE,
MAP_SHARED, fd_, 0));
alloc_area(shm_area_len_);
if (shm_area_ == MAP_FAILED) {
RING_ERR("Could not map shm area, mmap failed");
return false;
}
if (shm_area_ == MAP_FAILED)
throw std::runtime_error {"could not map shm area, mmap failed"};
memset(shm_area_, 0, shm_area_len_);
if (sem_init(&shm_area_->notification, 1, 0) != 0) {
RING_ERR("sem_init: notification initialization failed");
return false;
}
if (sem_init(&shm_area_->mutex, 1, 1) != 0) {
RING_ERR("sem_init: mutex initialization failed");
return false;
}
return true;
std::memset(shm_area_, 0, shm_area_len_);
if (::sem_init(&shm_area_->notification, 1, 0) != 0)
throw std::runtime_error {"sem_init: notification initialization failed"};
if (::sem_init(&shm_area_->mutex, 1, 1) != 0)
throw std::runtime_error {"sem_init: mutex initialization failed"};
}
bool
SinkClient::stop()
ShmHolder::~ShmHolder()
{
if (fd_ >= 0 and close(fd_) == -1)
if (fd_ >= 0 and ::close(fd_) == -1)
strErr();
fd_ = -1;
if (not opened_name_.empty())
::shm_unlink(opened_name_.c_str());
if (not opened_name_.empty()) {
shm_unlink(opened_name_.c_str());
opened_name_ = "";
if (shm_area_ != MAP_FAILED) {
::sem_post(&shm_area_->notification);
if (::munmap(shm_area_, shm_area_len_) < 0)
strErr();
}
if (shm_area_ != MAP_FAILED)
munmap(shm_area_, shm_area_len_);
shm_area_len_ = 0;
shm_area_ = static_cast<SHMHeader*>(MAP_FAILED);
return true;
}
bool
SinkClient::resize_area(size_t desired_length)
ShmHolder::resize_area(size_t desired_length)
{
if (desired_length <= shm_area_len_)
return true;
shm_unlock();
if (munmap(shm_area_, shm_area_len_)) {
if (::munmap(shm_area_, shm_area_len_)) {
RING_ERR("Could not unmap shared area");
strErr();
return false;
}
if (ftruncate(fd_, desired_length)) {
if (::ftruncate(fd_, desired_length)) {
RING_ERR("Could not resize shared area");
strErr();
return false;
}
shm_area_ = static_cast<SHMHeader*>(mmap(NULL, desired_length,
PROT_READ | PROT_WRITE,
MAP_SHARED, fd_, 0));
shm_area_len_ = desired_length;
alloc_area(desired_length);
if (shm_area_ == MAP_FAILED) {
shm_area_ = 0;
shm_area_len_ = 0;
RING_ERR("Could not remap shared area");
return false;
}
shm_lock();
shm_area_len_ = desired_length;
return true;
}
void
SinkClient::render(const std::vector<unsigned char>& data)
{
shm_lock();
if (!resize_area(sizeof(SHMHeader) + data.size()))
return;
memcpy(shm_area_->data, data.data(), data.size());
shm_area_->buffer_size = data.size();
shm_area_->buffer_gen++;
sem_post(&shm_area_->notification);
shm_unlock();
}
void
SinkClient::render_frame(VideoFrame& src)
ShmHolder::render_frame(VideoFrame& src)
{
VideoFrame dst;
VideoScaler scaler;
......@@ -214,71 +217,101 @@ SinkClient::render_frame(VideoFrame& src)
const int width = src.width();
const int height = src.height();
const int format = VIDEO_PIXFMT_BGRA;
size_t bytes = videoFrameSize(format, width, height);
shm_lock();
const auto bytes = videoFrameSize(format, width, height);
if (!resize_area(sizeof(SHMHeader) + bytes)) {
RING_ERR("Could not resize area");
return;
}
SemGuardLock lk{shm_area_->mutex};
dst.setFromMemory(shm_area_->data, format, width, height);
scaler.scale(src, dst);
#ifdef DEBUG_FPS
const std::chrono::time_point<std::chrono::system_clock> currentTime = \
std::chrono::system_clock::now();
const std::chrono::duration<double> seconds = currentTime - lastFrameDebug_;
frameCount_++;
if (seconds.count() > 1) {
RING_DBG("%s: FPS %f", shm_name_.c_str(), frameCount_ / seconds.count());
frameCount_ = 0;
lastFrameDebug_ = currentTime;
}
#endif
shm_area_->buffer_size = bytes;
shm_area_->buffer_gen++;
++shm_area_->buffer_gen;
sem_post(&shm_area_->notification);
shm_unlock();
}
void
SinkClient::render_callback(VideoProvider &provider, size_t bytes)
std::string
SinkClient::openedName() const noexcept
{
shm_lock();
return shm_->openedName();
}
if (!resize_area(sizeof(SHMHeader) + bytes)) {
RING_ERR("Could not resize area");
return;
bool
SinkClient::start() noexcept
{
if (not shm_) {
try {
shm_ = std::make_shared<ShmHolder>(id_);
} catch (const std::runtime_error& e) {
strErr();
RING_ERR("SHMHolder ctor failure: %s", e.what());
}
}
return static_cast<bool>(shm_);
}
provider.fillBuffer(static_cast<void*>(shm_area_->data));
shm_area_->buffer_size = bytes;
shm_area_->buffer_gen++;
sem_post(&shm_area_->notification);
shm_unlock();
bool
SinkClient::stop() noexcept
{
shm_.reset();
return true;
}
void
SinkClient::shm_lock()
#else // HAVE_SHM
std::string
SinkClient::openedName() const noexcept
{
sem_wait(&shm_area_->mutex);
return {};
}
void
SinkClient::shm_unlock()
bool
SinkClient::start() noexcept
{
return true;
}
bool
SinkClient::stop() noexcept
{
sem_post(&shm_area_->mutex);
return true;
}
#endif // !HAVE_SHM
SinkClient::SinkClient(const std::string& id) : id_ {id}
{}
void
SinkClient::update(Observable<std::shared_ptr<VideoFrame> >* /*obs*/,
std::shared_ptr<VideoFrame> &frame_p)
SinkClient::update(Observable<std::shared_ptr<VideoFrame>>* /*obs*/,
std::shared_ptr<VideoFrame>& frame_p)
{
auto f = frame_p; // keep a local reference during rendering
render_frame(*f.get());
#if HAVE_SHM
shm_->render_frame(*f.get());
#endif
if (target_) {
VideoFrame dst;
VideoScaler scaler;
const int width = f->width();
const int height = f->height();
const int format = VIDEO_PIXFMT_BGRA;
const auto bytes = videoFrameSize(format, width, height);
targetData_.resize(bytes);
auto data = targetData_.data();
dst.setFromMemory(data, format, width, height);
scaler.scale(*f, dst);
target_(data);
}
}
}} // namespace ring::video
......@@ -35,51 +35,55 @@
#pragma once
#include "noncopyable.h"
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include "video_provider.h"
#include "video_base.h"
#include <string>
#include <vector>
class SHMHeader;
#include <memory>
namespace ring { namespace video {
#if HAVE_SHM
class ShmHolder;
#endif // HAVE_SHM
class SinkClient : public VideoFramePassiveReader
{
public:
SinkClient(const std::string &shm_name = "");
std::string openedName() const { return opened_name_; }
~SinkClient();
SinkClient(const std::string& id="");
bool start();
bool stop();
const std::string& getId() const noexcept {
return id_;
}
bool resize_area(size_t desired_length);
void render(const std::vector<unsigned char> &data);
void render_frame(VideoFrame& src);
void render_callback(VideoProvider &provider, size_t bytes);
std::string openedName() const noexcept;
// as VideoFramePassiveReader
void update(Observable<std::shared_ptr<ring::VideoFrame>>*,
std::shared_ptr<ring::VideoFrame> &);
std::shared_ptr<ring::VideoFrame>&);
bool start() noexcept;
bool stop() noexcept;
template <class T>
void registerTarget(T&& cb) noexcept {
target_ = std::forward<T>(cb);
}
private:
NON_COPYABLE(SinkClient);