LCOV - code coverage report
Current view: top level - src/media/video - sinkclient.cpp (source / functions) Hit Total Coverage
Test: jami-coverage-filtered.info Lines: 96 238 40.3 %
Date: 2024-04-23 08:02:50 Functions: 12 19 63.2 %

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

Generated by: LCOV version 1.14