Ring Daemon 16.0.0
Loading...
Searching...
No Matches
sinkclient.cpp
Go to the documentation of this file.
1/*
2 * Copyright (C) 2004-2025 Savoir-faire Linux Inc.
3 *
4 * This program is free software: you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation, either version 3 of the License, or
7 * (at your option) any later version.
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License
15 * along with this program. If not, see <https://www.gnu.org/licenses/>.
16 */
17
18#ifdef HAVE_CONFIG_H
19#include "config.h"
20#endif
21
22#include "sinkclient.h"
23
24#ifdef ENABLE_SHM
25#include "shm_header.h"
26#endif // ENABLE_SHM
27
28#include "media_buffer.h"
29#include "logger.h"
30#include "noncopyable.h"
31#include "client/ring_signal.h"
33#include "libav_utils.h"
34#include "video_scaler.h"
35#include "media_filter.h"
36#include "filter_transpose.h"
37
38#ifdef RING_ACCEL
39#include "accel.h"
40#endif
41
42#ifndef _WIN32
43#include <sys/mman.h>
44#endif
45#include <ciso646> // fix windows compiler bug
46#include <fcntl.h>
47#include <cstdio>
48#include <sstream>
49#include <unistd.h>
50#include <cerrno>
51#include <cstring>
52#include <stdexcept>
53#include <cmath>
54
55namespace jami {
56namespace video {
57
58const constexpr char FILTER_INPUT_NAME[] = "in";
59
60#ifdef ENABLE_SHM
61// RAII class helper on sem_wait/sem_post sempahore operations
62class SemGuardLock
63{
64public:
65 explicit SemGuardLock(sem_t& mutex)
66 : m_(mutex)
67 {
68 auto ret = ::sem_wait(&m_);
69 if (ret < 0) {
70 std::ostringstream msg;
71 msg << "SHM mutex@" << &m_ << " lock failed (" << ret << ")";
72 throw std::logic_error {msg.str()};
73 }
74 }
75
76 ~SemGuardLock() { ::sem_post(&m_); }
77
78private:
79 sem_t& m_;
80};
81
82class ShmHolder
83{
84public:
85 ShmHolder(const std::string& name = {});
86 ~ShmHolder();
87
88 std::string name() const noexcept { return openedName_; }
89
90 void renderFrame(const VideoFrame& src) noexcept;
91
92private:
93 bool resizeArea(std::size_t desired_length) noexcept;
94 char* getShmAreaDataPtr() noexcept;
95
96 void unMapShmArea() noexcept
97 {
98 if (area_ != MAP_FAILED and ::munmap(area_, areaSize_) < 0) {
99 JAMI_ERR("[ShmHolder:%s] munmap(%zu) failed with errno %d",
100 openedName_.c_str(),
101 areaSize_,
102 errno);
103 }
104 }
105
106 SHMHeader* area_ {static_cast<SHMHeader*>(MAP_FAILED)};
107 std::size_t areaSize_ {0};
108 std::string openedName_;
109 int fd_ {-1};
110};
111
112ShmHolder::ShmHolder(const std::string& name)
113{
114 static constexpr int flags = O_RDWR | O_CREAT | O_TRUNC | O_EXCL;
115 static constexpr int perms = S_IRUSR | S_IWUSR;
116
117 static auto shmFailedWithErrno = [this](const std::string& what) {
118 std::ostringstream msg;
119 msg << "ShmHolder[" << openedName_ << "]: " << what << " failed, errno=" << errno;
120 throw std::runtime_error {msg.str()};
121 };
122
123 if (not name.empty()) {
124 openedName_ = name;
125 fd_ = ::shm_open(openedName_.c_str(), flags, perms);
126 if (fd_ < 0)
127 shmFailedWithErrno("shm_open");
128 } else {
129 for (int i = 0; fd_ < 0; ++i) {
130 std::ostringstream tmpName;
131 tmpName << PACKAGE_NAME << "_shm_" << getpid() << "_" << i;
132 openedName_ = tmpName.str();
133 fd_ = ::shm_open(openedName_.c_str(), flags, perms);
134 if (fd_ < 0 and errno != EEXIST)
135 shmFailedWithErrno("shm_open");
136 }
137 }
138
139 // Set size enough for header only (no frame data)
140 if (!resizeArea(0))
141 shmFailedWithErrno("resizeArea");
142
143 // Header fields initialization
144 std::memset(area_, 0, areaSize_);
145
146 if (::sem_init(&area_->mutex, 1, 1) < 0)
147 shmFailedWithErrno("sem_init(mutex)");
148
149 if (::sem_init(&area_->frameGenMutex, 1, 0) < 0)
150 shmFailedWithErrno("sem_init(frameGenMutex)");
151
152 JAMI_DBG("[ShmHolder:%s] New holder created", openedName_.c_str());
153}
154
155ShmHolder::~ShmHolder()
156{
157 if (fd_ < 0)
158 return;
159
160 ::close(fd_);
161 ::shm_unlink(openedName_.c_str());
162
163 if (area_ == MAP_FAILED)
164 return;
165
166 ::sem_wait(&area_->mutex);
167 area_->frameSize = 0;
168 ::sem_post(&area_->mutex);
169
170 ::sem_post(&area_->frameGenMutex); // unlock waiting client before leaving
171 unMapShmArea();
172}
173
174bool
175ShmHolder::resizeArea(std::size_t frameSize) noexcept
176{
177 // aligned on 16-byte boundary frameSize
178 frameSize = (frameSize + 15) & ~15;
179
180 if (area_ != MAP_FAILED and frameSize == area_->frameSize)
181 return true;
182
183 // full area size: +15 to take care of maximum padding size
184 const auto areaSize = sizeof(SHMHeader) + 2 * frameSize + 15;
185 JAMI_DBG("[ShmHolder:%s] New size: f=%zu, a=%zu", openedName_.c_str(), frameSize, areaSize);
186
187 unMapShmArea();
188
189 if (::ftruncate(fd_, areaSize) < 0) {
190 JAMI_ERR("[ShmHolder:%s] ftruncate(%zu) failed with errno %d",
191 openedName_.c_str(),
192 areaSize,
193 errno);
194 return false;
195 }
196
197 area_ = static_cast<SHMHeader*>(
199
200 if (area_ == MAP_FAILED) {
201 areaSize_ = 0;
202 JAMI_ERR("[ShmHolder:%s] mmap(%zu) failed with errno %d",
203 openedName_.c_str(),
204 areaSize,
205 errno);
206 return false;
207 }
208
210
211 if (frameSize) {
212 SemGuardLock lk {area_->mutex};
213
214 area_->frameSize = frameSize;
215 area_->mapSize = areaSize;
216
217 // Compute aligned IO pointers
218 // Note: we not using std::align as not implemented in 4.9
219 // https://gcc.gnu.org/bugzilla/show_bug.cgi?id=57350
220 auto p = reinterpret_cast<std::uintptr_t>(area_->data);
221 area_->writeOffset = ((p + 15) & ~15) - p;
222 area_->readOffset = area_->writeOffset + frameSize;
223 }
224
225 return true;
226}
227
228void
229ShmHolder::renderFrame(const VideoFrame& src) noexcept
230{
231 const auto width = src.width();
232 const auto height = src.height();
233 const auto format = AV_PIX_FMT_BGRA;
234 const auto frameSize = videoFrameSize(format, width, height);
235
236 if (!resizeArea(frameSize)) {
237 JAMI_ERR("[ShmHolder:%s] Unable to resize area size: %dx%d, format: %d",
238 openedName_.c_str(),
239 width,
240 height,
241 format);
242 return;
243 }
244
245 {
247 VideoScaler scaler;
248
249 dst.setFromMemory(area_->data + area_->writeOffset, format, width, height);
250 scaler.scale(src, dst);
251 }
252
253 {
254 SemGuardLock lk {area_->mutex};
255
256 ++area_->frameGen;
257 std::swap(area_->readOffset, area_->writeOffset);
258 ::sem_post(&area_->frameGenMutex);
259 }
260}
261
262std::string
264{
265 if (shm_)
266 return shm_->name();
267 return {};
268}
269
270bool
272{
273 if (not shm_) {
274 try {
275 char* envvar = getenv("JAMI_DISABLE_SHM");
276 if (envvar) // Do not use SHM if set
277 return true;
278 shm_ = std::make_shared<ShmHolder>();
279 JAMI_DBG("[Sink:%p] Shared memory [%s] created", this, openedName().c_str());
280 } catch (const std::runtime_error& e) {
281 JAMI_ERR("[Sink:%p] Failed to create shared memory: %s", this, e.what());
282 }
283 }
284
285 return static_cast<bool>(shm_);
286}
287
288bool
290{
291 setFrameSize(0, 0);
292 setCrop(0, 0, 0, 0);
293 shm_.reset();
294 return true;
295}
296
297#else // ENABLE_SHM
298
299std::string
301{
302 return {};
303}
304
305bool
307{
308 return true;
309}
310
311bool
313{
314 setFrameSize(0, 0);
315 setCrop(0, 0, 0, 0);
316 return true;
317}
318
319#endif // !ENABLE_SHM
320
321SinkClient::SinkClient(const std::string& id, bool mixer)
322 : id_ {id}
323 , mixer_(mixer)
324 , scaler_(new VideoScaler())
326 , frameCount_(0u)
328#endif
329{
330 JAMI_DBG("[Sink:%p] Sink [%s] created", this, getId().c_str());
331}
332
333void
334SinkClient::sendFrameDirect(const std::shared_ptr<jami::MediaFrame>& frame_p)
335{
337
339 av_frame_ref(outFrame.get(), std::static_pointer_cast<VideoFrame>(frame_p)->pointer());
340
341 if (crop_.w || crop_.h) {
342#ifdef RING_ACCEL
344 (AVPixelFormat) std::static_pointer_cast<VideoFrame>(frame_p)->format());
345 /*
346 Cropping does not work for hardware-decoded frames.
347 They need to be transferred to main memory.
348 */
349 if (desc && (desc->flags & AV_PIX_FMT_FLAG_HWACCEL)) {
350 std::shared_ptr<VideoFrame> frame = std::make_shared<VideoFrame>();
351 try {
352 frame = HardwareAccel::transferToMainMemory(*std::static_pointer_cast<VideoFrame>(
353 frame_p),
355 } catch (const std::runtime_error& e) {
356 JAMI_ERR("[Sink:%p] Transfert to hardware acceleration memory failed: %s",
357 this,
358 e.what());
359 return;
360 }
361 if (not frame)
362 return;
364 av_frame_ref(outFrame.get(), frame->pointer());
365 }
366#endif
367 outFrame->crop_top = crop_.y;
368 outFrame->crop_bottom = (size_t) outFrame->height - crop_.y - crop_.h;
369 outFrame->crop_left = crop_.x;
370 outFrame->crop_right = (size_t) outFrame->width - crop_.x - crop_.w;
372 }
373 if (outFrame->height != height_ || outFrame->width != width_) {
374 setFrameSize(outFrame->width, outFrame->height);
375 return;
376 }
377 target_.push(std::move(outFrame));
378}
379
380void
381SinkClient::sendFrameTransformed(AVFrame* frame)
382{
383 if (frame->width > 0 and frame->height > 0) {
384 if (auto buffer_ptr = target_.pull()) {
385 scaler_->scale(frame, buffer_ptr.get());
386 target_.push(std::move(buffer_ptr));
387 }
388 }
389}
390
391std::shared_ptr<VideoFrame>
392SinkClient::applyTransform(VideoFrame& frame_p)
393{
394 std::shared_ptr<VideoFrame> frame = std::make_shared<VideoFrame>();
395#ifdef RING_ACCEL
397 if (desc && (desc->flags & AV_PIX_FMT_FLAG_HWACCEL)) {
398 try {
400 } catch (const std::runtime_error& e) {
401 JAMI_ERR("[Sink:%p] Transfert to hardware acceleration memory failed: %s",
402 this,
403 e.what());
404 return {};
405 }
406 } else
407#endif
408 frame->copyFrom(frame_p);
409
410 int angle = frame->getOrientation();
411 if (angle != rotation_) {
412 filter_ = getTransposeFilter(angle,
414 frame->width(),
415 frame->height(),
416 frame->format(),
417 false);
418 rotation_ = angle;
419 }
420 if (filter_) {
421 filter_->feedInput(frame->pointer(), FILTER_INPUT_NAME);
422 frame = std::static_pointer_cast<VideoFrame>(
423 std::shared_ptr<MediaFrame>(filter_->readOutput()));
424 }
425 if (crop_.w || crop_.h) {
426 frame->pointer()->crop_top = crop_.y;
427 frame->pointer()->crop_bottom = (size_t) frame->height() - crop_.y - crop_.h;
428 frame->pointer()->crop_left = crop_.x;
429 frame->pointer()->crop_right = (size_t) frame->width() - crop_.x - crop_.w;
431 }
432 return frame;
433}
434
435void
436SinkClient::update(Observable<std::shared_ptr<MediaFrame>>* /*obs*/,
437 const std::shared_ptr<MediaFrame>& frame_p)
438{
439#ifdef DEBUG_FPS
440 auto currentTime = std::chrono::steady_clock::now();
442 ++frameCount_;
443 if (seconds > std::chrono::seconds(1)) {
444 auto fps = frameCount_ / std::chrono::duration<double>(seconds).count();
445 JAMI_WARNING("Sink {}, {} FPS", id_, fps);
446 frameCount_ = 0;
448 }
449#endif
450
451 std::unique_lock lock(mtx_);
452 bool hasObservers = getObserversCount() != 0;
453 bool hasDirectListener = target_.push and not target_.pull;
454 bool hasTransformedListener = target_.push and target_.pull;
455
456 if (hasDirectListener) {
457 sendFrameDirect(frame_p);
458 return;
459 }
460
462#ifdef ENABLE_SHM
464#endif
465
466 if (doTransfer) {
467 auto frame = applyTransform(*std::static_pointer_cast<VideoFrame>(frame_p));
468 if (not frame)
469 return;
470
471 notify(std::static_pointer_cast<MediaFrame>(frame));
472
473 if (frame->height() != height_ || frame->width() != width_) {
474 lock.unlock();
475 setFrameSize(frame->width(), frame->height());
476 return;
477 }
478#ifdef ENABLE_SHM
479 if (shm_ && doShmTransfer_)
480 shm_->renderFrame(*frame);
481#endif
483 sendFrameTransformed(frame->pointer());
484 }
485}
486
487void
488SinkClient::setFrameSize(int width, int height)
489{
490 width_ = width;
491 height_ = height;
492 if (width > 0 and height > 0) {
493 JAMI_DBG("[Sink:%p] Started - size=%dx%d, mixer=%s",
494 this,
495 width,
496 height,
497 mixer_ ? "Yes" : "No");
499 openedName(),
500 width,
501 height,
502 mixer_);
503 started_ = true;
504 } else if (started_) {
505 JAMI_DBG("[Sink:%p] Stopped - size=%dx%d, mixer=%s",
506 this,
507 width,
508 height,
509 mixer_ ? "Yes" : "No");
511 started_ = false;
512 }
513}
514
515void
516SinkClient::setCrop(int x, int y, int w, int h)
517{
518 JAMI_DBG("[Sink:%p] Change crop to [%dx%d at (%d, %d)]", this, w, h, x, y);
519 crop_.x = x;
520 crop_.y = y;
521 crop_.w = w;
522 crop_.h = h;
523}
524
525} // namespace video
526} // namespace jami
static std::unique_ptr< VideoFrame > transferToMainMemory(const VideoFrame &frame, AVPixelFormat desiredFormat)
Transfers hardware frame to main memory.
Definition accel.cpp:372
void setCrop(int x, int y, int w, int h)
const std::string & getId() const noexcept
Definition sinkclient.h:50
SinkClient(const std::string &id="", bool mixer=false)
bool start() noexcept
void update(Observable< std::shared_ptr< jami::MediaFrame > > *, const std::shared_ptr< jami::MediaFrame > &) override
std::string openedName() const noexcept
void setFrameSize(int width, int height)
void setFromMemory(uint8_t *data, int format, int width, int height) noexcept
#define JAMI_ERR(...)
Definition logger.h:218
#define JAMI_DBG(...)
Definition logger.h:216
#define JAMI_WARNING(formatstr,...)
Definition logger.h:227
std::unique_ptr< MediaFilter > getTransposeFilter(int rotation, std::string inputName, int width, int height, int format, bool rescale)
const constexpr char FILTER_INPUT_NAME[]
void emitSignal(Args... args)
Definition ring_signal.h:64
libjami::VideoFrame VideoFrame
Definition video_base.h:53
std::unique_ptr< AVFrame, AVFrame_deleter > FrameBuffer
Simple macro to hide class' copy constructor and assignment operator.
std::function< FrameBuffer()> pull
std::function< void(FrameBuffer)> push
#define S_IRUSR
Definition windirent.h:105
#define S_IWUSR
Definition windirent.h:110