Skip to content
Snippets Groups Projects
Commit a83147d5 authored by Eloi Bail's avatar Eloi Bail Committed by Gerrit Code Review
Browse files

daemon: implement double-buffered SHM

Refs #70056

Change-Id: I914339024e3570c3ebf2d1772265d31f0a4e2d7f
parent 5feb8552
No related branches found
No related tags found
No related merge requests found
...@@ -38,16 +38,21 @@ ...@@ -38,16 +38,21 @@
#include <semaphore.h> #include <semaphore.h>
struct SHMHeader { // Implementation note: double-buffering
sem_t notification; // Shared memory is divided in two regions, each representing one frame.
sem_t mutex; // First byte of each frame is warranted to by aligned on 16 bytes.
// One region is marked readable: this region can be safely read.
unsigned buffer_gen; // The other region is writeable: only the producer can use it.
int buffer_size;
/* The header will be aligned on 16-byte boundaries */
char padding[8];
char data[]; 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
char data[]; // the whole shared memory
}; };
#endif #endif
...@@ -48,6 +48,8 @@ ...@@ -48,6 +48,8 @@
#include "media_buffer.h" #include "media_buffer.h"
#include "logger.h" #include "logger.h"
#include "noncopyable.h" #include "noncopyable.h"
#include "client/ring_signal.h"
#include "dring/videomanager_interface.h"
#include <sys/mman.h> #include <sys/mman.h>
#include <fcntl.h> #include <fcntl.h>
...@@ -85,159 +87,179 @@ class SemGuardLock { ...@@ -85,159 +87,179 @@ class SemGuardLock {
class ShmHolder class ShmHolder
{ {
public: public:
ShmHolder(const std::string& shm_name={}); ShmHolder(const std::string& name={});
~ShmHolder(); ~ShmHolder();
std::string openedName() const noexcept { std::string name() const noexcept {
return opened_name_; return openedName_;
} }
void render_frame(VideoFrame& src); void renderFrame(VideoFrame& src) noexcept;
private: private:
bool resize_area(std::size_t desired_length); bool resizeArea(std::size_t desired_length) noexcept;
void alloc_area(std::size_t desired_length) noexcept; char* getShmAreaDataPtr() noexcept;
std::string shm_name_; void unMapShmArea() noexcept {
std::string opened_name_; if (area_ != MAP_FAILED and ::munmap(area_, areaSize_) < 0) {
std::size_t shm_area_len_ {0}; RING_ERR("ShmHolder[%s]: munmap(%u) failed with errno %d",
SHMHeader* shm_area_ {static_cast<SHMHeader*>(MAP_FAILED)}; openedName_.c_str(), areaSize_, errno);
}
}
SHMHeader* area_ {static_cast<SHMHeader*>(MAP_FAILED)};
std::size_t areaSize_ {0};
std::string openedName_;
int fd_ {-1}; int fd_ {-1};
}; };
void ShmHolder::ShmHolder(const std::string& name)
ShmHolder::alloc_area(std::size_t desired_length) noexcept
{
shm_area_ = static_cast<SHMHeader*>(::mmap(nullptr, desired_length,
PROT_READ | PROT_WRITE,
MAP_SHARED, fd_, 0));
}
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 flags = O_RDWR | O_CREAT | O_TRUNC | O_EXCL;
static constexpr int perms = S_IRUSR | S_IWUSR; static constexpr int perms = S_IRUSR | S_IWUSR;
if (not shm_name_.empty()) { static auto shmFailedWithErrno = [this](const std::string& what) {
fd_ = ::shm_open(shm_name_.c_str(), flags, perms);
if (fd_ < 0) {
std::ostringstream msg; std::ostringstream msg;
msg << "could not open shm area \"" msg << "ShmHolder[" << openedName_ << "]: "
<< shm_name_.c_str() << what << " failed, errno=" << errno;
<< "\"";
throw std::runtime_error {msg.str()}; 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 { } else {
for (int i = 0; fd_ < 0; ++i) { for (int i = 0; fd_ < 0; ++i) {
std::ostringstream name; std::ostringstream tmpName;
name << PACKAGE_NAME << "_shm_" << getpid() << "_" << i; tmpName << PACKAGE_NAME << "_shm_" << getpid() << "_" << i;
shm_name_ = name.str(); openedName_ = tmpName.str();
fd_ = ::shm_open(shm_name_.c_str(), flags, perms); fd_ = ::shm_open(openedName_.c_str(), flags, perms);
if (fd_ < 0 and errno != EEXIST) if (fd_ < 0 and errno != EEXIST)
throw std::runtime_error {"shm_open() failed"}; shmFailedWithErrno("shm_open");
}
} }
RING_DBG("Using name %s", shm_name_.c_str());
opened_name_ = shm_name_;
shm_area_len_ = sizeof(SHMHeader);
if (::ftruncate(fd_, shm_area_len_)) {
RING_ERR("Could not make shm area large enough for header");
strErr();
throw std::runtime_error {"could not make shm area large enough for header"};
} }
alloc_area(shm_area_len_); // Set size enough for header only (no frame data)
if (!resizeArea(0))
shmFailedWithErrno("resizeArea");
if (shm_area_ == MAP_FAILED) // Header fields initialization
throw std::runtime_error {"could not map shm area, mmap failed"}; std::memset(area_, 0, areaSize_);
std::memset(shm_area_, 0, shm_area_len_); if (::sem_init(&area_->mutex, 1, 1) < 0)
shmFailedWithErrno("sem_init(mutex)");
if (::sem_init(&shm_area_->notification, 1, 0) != 0) if (::sem_init(&area_->frameGenMutex, 1, 0) < 0)
throw std::runtime_error {"sem_init: notification initialization failed"}; shmFailedWithErrno("sem_init(frameGenMutex)");
if (::sem_init(&shm_area_->mutex, 1, 1) != 0) RING_DBG("ShmHolder: new holder '%s'", openedName_.c_str());
throw std::runtime_error {"sem_init: mutex initialization failed"};
} }
ShmHolder::~ShmHolder() ShmHolder::~ShmHolder()
{ {
if (fd_ >= 0 and ::close(fd_) == -1) if (fd_ < 0)
strErr(); return;
if (not opened_name_.empty()) ::close(fd_);
::shm_unlink(opened_name_.c_str()); ::shm_unlink(openedName_.c_str());
if (shm_area_ != MAP_FAILED) { if (area_ == MAP_FAILED)
::sem_post(&shm_area_->notification); return;
if (::munmap(shm_area_, shm_area_len_) < 0)
strErr(); area_->frameSize = 0;
} ::sem_post(&area_->frameGenMutex);
unMapShmArea();
} }
bool bool
ShmHolder::resize_area(size_t desired_length) ShmHolder::resizeArea(std::size_t frameSize) noexcept
{ {
if (desired_length <= shm_area_len_) // aligned on 16-byte boundary frameSize
frameSize = (frameSize + 15) & ~15;
if (area_ != MAP_FAILED and frameSize == area_->frameSize)
return true; return true;
if (::munmap(shm_area_, shm_area_len_)) { // full area size: +15 to take care of maximum padding size
RING_ERR("Could not unmap shared area"); const auto areaSize = sizeof(SHMHeader) + 2 * frameSize + 15;
strErr(); RING_DBG("ShmHolder[%s]: new sizes: f=%u, a=%u", openedName_.c_str(),
frameSize, areaSize);
unMapShmArea();
if (::ftruncate(fd_, areaSize) < 0) {
RING_ERR("ShmHolder[%s]: ftruncate(%u) failed with errno %d",
openedName_.c_str(), areaSize, errno);
return false; return false;
} }
if (::ftruncate(fd_, desired_length)) { area_ = static_cast<SHMHeader*>(::mmap(nullptr, areaSize,
RING_ERR("Could not resize shared area"); PROT_READ | PROT_WRITE,
strErr(); MAP_SHARED, fd_, 0));
if (area_ == MAP_FAILED) {
areaSize_ = 0;
RING_ERR("ShmHolder[%s]: mmap(%u) failed with errno %d",
openedName_.c_str(), areaSize, errno);
return false; return false;
} }
alloc_area(desired_length); areaSize_ = areaSize;
if (shm_area_ == MAP_FAILED) { if (frameSize) {
shm_area_len_ = 0; SemGuardLock lk {area_->mutex};
RING_ERR("Could not remap shared area");
return false; 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;
} }
shm_area_len_ = desired_length;
return true; return true;
} }
void void
ShmHolder::render_frame(VideoFrame& src) ShmHolder::renderFrame(VideoFrame& src) noexcept
{ {
VideoFrame dst; const auto width = src.width();
VideoScaler scaler; const auto height = src.height();
const auto format = VIDEO_PIXFMT_BGRA;
const int width = src.width(); const auto frameSize = videoFrameSize(format, width, height);
const int height = src.height();
const int format = VIDEO_PIXFMT_BGRA; if (!resizeArea(frameSize)) {
const auto bytes = videoFrameSize(format, width, height); RING_ERR("ShmHolder[%s]: could not resize area",
openedName_.c_str());
if (!resize_area(sizeof(SHMHeader) + bytes)) {
RING_ERR("Could not resize area");
return; return;
} }
SemGuardLock lk{shm_area_->mutex}; {
VideoFrame dst;
VideoScaler scaler;
dst.setFromMemory(shm_area_->data, format, width, height); dst.setFromMemory(area_->data + area_->writeOffset, format, width, height);
scaler.scale(src, dst); scaler.scale(src, dst);
}
shm_area_->buffer_size = bytes; {
++shm_area_->buffer_gen; SemGuardLock lk {area_->mutex};
sem_post(&shm_area_->notification);
++area_->frameGen;
std::swap(area_->readOffset, area_->writeOffset);
::sem_post(&area_->frameGenMutex);
}
} }
std::string std::string
SinkClient::openedName() const noexcept SinkClient::openedName() const noexcept
{ {
return shm_->openedName(); return shm_->name();
} }
bool bool
...@@ -247,7 +269,6 @@ SinkClient::start() noexcept ...@@ -247,7 +269,6 @@ SinkClient::start() noexcept
try { try {
shm_ = std::make_shared<ShmHolder>(); shm_ = std::make_shared<ShmHolder>();
} catch (const std::runtime_error& e) { } catch (const std::runtime_error& e) {
strErr();
RING_ERR("SHMHolder ctor failure: %s", e.what()); RING_ERR("SHMHolder ctor failure: %s", e.what());
} }
} }
...@@ -308,7 +329,7 @@ SinkClient::update(Observable<std::shared_ptr<VideoFrame>>* /*obs*/, ...@@ -308,7 +329,7 @@ SinkClient::update(Observable<std::shared_ptr<VideoFrame>>* /*obs*/,
#endif #endif
#if HAVE_SHM #if HAVE_SHM
shm_->render_frame(*f.get()); shm_->renderFrame(*f.get());
#endif #endif
if (target_) { if (target_) {
......
...@@ -195,7 +195,7 @@ void VideoMixer::start_sink() ...@@ -195,7 +195,7 @@ void VideoMixer::start_sink()
{ {
if (sink_->start()) { if (sink_->start()) {
if (this->attach(sink_.get())) { if (this->attach(sink_.get())) {
emitSignal<DRing::VideoSignal::DecodingStarted>(id_, emitSignal<DRing::VideoSignal::DecodingStarted>(sink_->getId(),
sink_->openedName(), sink_->openedName(),
width_, height_, width_, height_,
true); true);
...@@ -208,13 +208,12 @@ void VideoMixer::start_sink() ...@@ -208,13 +208,12 @@ void VideoMixer::start_sink()
void VideoMixer::stop_sink() void VideoMixer::stop_sink()
{ {
if (this->detach(sink_.get())) { this->detach(sink_.get());
emitSignal<DRing::VideoSignal::DecodingStopped>(id_, emitSignal<DRing::VideoSignal::DecodingStopped>(sink_->getId(),
sink_->openedName(), sink_->openedName(),
true); true);
sink_->stop(); sink_->stop();
} }
}
int VideoMixer::getWidth() const int VideoMixer::getWidth() const
{ return width_; } { return width_; }
......
...@@ -143,9 +143,8 @@ void VideoReceiveThread::process() ...@@ -143,9 +143,8 @@ void VideoReceiveThread::process()
void VideoReceiveThread::cleanup() void VideoReceiveThread::cleanup()
{ {
if (detach(sink_.get())) detach(sink_.get());
emitSignal<DRing::VideoSignal::DecodingStopped>(id_, emitSignal<DRing::VideoSignal::DecodingStopped>(id_, sink_->openedName(),
sink_->openedName(),
false); false);
sink_->stop(); sink_->stop();
...@@ -206,9 +205,9 @@ void VideoReceiveThread::enterConference() ...@@ -206,9 +205,9 @@ void VideoReceiveThread::enterConference()
return; return;
if (detach(sink_.get())) { if (detach(sink_.get())) {
emitSignal<DRing::VideoSignal::DecodingStopped>(id_, emitSignal<DRing::VideoSignal::DecodingStopped>(sink_->getId(),
sink_->openedName(), sink_->openedName(),
false); true);
RING_DBG("RX: shm sink <%s> detached", sink_->openedName().c_str()); RING_DBG("RX: shm sink <%s> detached", sink_->openedName().c_str());
} }
} }
...@@ -220,7 +219,7 @@ void VideoReceiveThread::exitConference() ...@@ -220,7 +219,7 @@ void VideoReceiveThread::exitConference()
if (dstWidth_ > 0 && dstHeight_ > 0) { if (dstWidth_ > 0 && dstHeight_ > 0) {
if (attach(sink_.get())) { if (attach(sink_.get())) {
emitSignal<DRing::VideoSignal::DecodingStarted>(id_, emitSignal<DRing::VideoSignal::DecodingStarted>(sink_->getId(),
sink_->openedName(), sink_->openedName(),
dstWidth_, dstWidth_,
dstHeight_, false); dstHeight_, false);
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment