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-12-21 08:56:24 Functions: 12 19 63.2 %

          Line data    Source code
       1             : /*
       2             :  *  Copyright (C) 2004-2024 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"
      32             : #include "jami/videomanager_interface.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             : 
      55             : namespace jami {
      56             : namespace video {
      57             : 
      58             : const constexpr char FILTER_INPUT_NAME[] = "in";
      59             : 
      60             : #ifdef ENABLE_SHM
      61             : // RAII class helper on sem_wait/sem_post sempahore operations
      62             : class SemGuardLock
      63             : {
      64             : public:
      65           0 :     explicit SemGuardLock(sem_t& mutex)
      66           0 :         : m_(mutex)
      67             :     {
      68           0 :         auto ret = ::sem_wait(&m_);
      69           0 :         if (ret < 0) {
      70           0 :             std::ostringstream msg;
      71           0 :             msg << "SHM mutex@" << &m_ << " lock failed (" << ret << ")";
      72           0 :             throw std::logic_error {msg.str()};
      73           0 :         }
      74           0 :     }
      75             : 
      76           0 :     ~SemGuardLock() { ::sem_post(&m_); }
      77             : 
      78             : private:
      79             :     sem_t& m_;
      80             : };
      81             : 
      82             : class ShmHolder
      83             : {
      84             : public:
      85             :     ShmHolder(const std::string& name = {});
      86             :     ~ShmHolder();
      87             : 
      88         430 :     std::string name() const noexcept { return openedName_; }
      89             : 
      90             :     void renderFrame(const VideoFrame& src) noexcept;
      91             : 
      92             : private:
      93             :     bool resizeArea(std::size_t desired_length) noexcept;
      94             :     char* getShmAreaDataPtr() noexcept;
      95             : 
      96         532 :     void unMapShmArea() noexcept
      97             :     {
      98         532 :         if (area_ != MAP_FAILED and ::munmap(area_, areaSize_) < 0) {
      99           0 :             JAMI_ERR("[ShmHolder:%s] munmap(%zu) failed with errno %d",
     100             :                      openedName_.c_str(),
     101             :                      areaSize_,
     102             :                      errno);
     103             :         }
     104         532 :     }
     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             : 
     112         266 : ShmHolder::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           0 :     static auto shmFailedWithErrno = [this](const std::string& what) {
     118           0 :         std::ostringstream msg;
     119           0 :         msg << "ShmHolder[" << openedName_ << "]: " << what << " failed, errno=" << errno;
     120           0 :         throw std::runtime_error {msg.str()};
     121         266 :     };
     122             : 
     123         266 :     if (not name.empty()) {
     124           0 :         openedName_ = name;
     125           0 :         fd_ = ::shm_open(openedName_.c_str(), flags, perms);
     126           0 :         if (fd_ < 0)
     127           0 :             shmFailedWithErrno("shm_open");
     128             :     } else {
     129         732 :         for (int i = 0; fd_ < 0; ++i) {
     130         466 :             std::ostringstream tmpName;
     131         466 :             tmpName << PACKAGE_NAME << "_shm_" << getpid() << "_" << i;
     132         466 :             openedName_ = tmpName.str();
     133         466 :             fd_ = ::shm_open(openedName_.c_str(), flags, perms);
     134         466 :             if (fd_ < 0 and errno != EEXIST)
     135           0 :                 shmFailedWithErrno("shm_open");
     136         466 :         }
     137             :     }
     138             : 
     139             :     // Set size enough for header only (no frame data)
     140         266 :     if (!resizeArea(0))
     141           0 :         shmFailedWithErrno("resizeArea");
     142             : 
     143             :     // Header fields initialization
     144         266 :     std::memset(area_, 0, areaSize_);
     145             : 
     146         266 :     if (::sem_init(&area_->mutex, 1, 1) < 0)
     147           0 :         shmFailedWithErrno("sem_init(mutex)");
     148             : 
     149         266 :     if (::sem_init(&area_->frameGenMutex, 1, 0) < 0)
     150           0 :         shmFailedWithErrno("sem_init(frameGenMutex)");
     151             : 
     152         266 :     JAMI_DBG("[ShmHolder:%s] New holder created", openedName_.c_str());
     153         266 : }
     154             : 
     155         532 : ShmHolder::~ShmHolder()
     156             : {
     157         266 :     if (fd_ < 0)
     158           0 :         return;
     159             : 
     160         266 :     ::close(fd_);
     161         266 :     ::shm_unlink(openedName_.c_str());
     162             : 
     163         266 :     if (area_ == MAP_FAILED)
     164           0 :         return;
     165             : 
     166         266 :     ::sem_wait(&area_->mutex);
     167         266 :     area_->frameSize = 0;
     168         266 :     ::sem_post(&area_->mutex);
     169             : 
     170         266 :     ::sem_post(&area_->frameGenMutex); // unlock waiting client before leaving
     171         266 :     unMapShmArea();
     172         266 : }
     173             : 
     174             : bool
     175         266 : ShmHolder::resizeArea(std::size_t frameSize) noexcept
     176             : {
     177             :     // aligned on 16-byte boundary frameSize
     178         266 :     frameSize = (frameSize + 15) & ~15;
     179             : 
     180         266 :     if (area_ != MAP_FAILED and frameSize == area_->frameSize)
     181           0 :         return true;
     182             : 
     183             :     // full area size: +15 to take care of maximum padding size
     184         266 :     const auto areaSize = sizeof(SHMHeader) + 2 * frameSize + 15;
     185         266 :     JAMI_DBG("[ShmHolder:%s] New size: f=%zu, a=%zu", openedName_.c_str(), frameSize, areaSize);
     186             : 
     187         266 :     unMapShmArea();
     188             : 
     189         266 :     if (::ftruncate(fd_, areaSize) < 0) {
     190           0 :         JAMI_ERR("[ShmHolder:%s] ftruncate(%zu) failed with errno %d",
     191             :                  openedName_.c_str(),
     192             :                  areaSize,
     193             :                  errno);
     194           0 :         return false;
     195             :     }
     196             : 
     197         266 :     area_ = static_cast<SHMHeader*>(
     198         266 :         ::mmap(nullptr, areaSize, PROT_READ | PROT_WRITE, MAP_SHARED, fd_, 0));
     199             : 
     200         266 :     if (area_ == MAP_FAILED) {
     201           0 :         areaSize_ = 0;
     202           0 :         JAMI_ERR("[ShmHolder:%s] mmap(%zu) failed with errno %d",
     203             :                  openedName_.c_str(),
     204             :                  areaSize,
     205             :                  errno);
     206           0 :         return false;
     207             :     }
     208             : 
     209         266 :     areaSize_ = areaSize;
     210             : 
     211         266 :     if (frameSize) {
     212           0 :         SemGuardLock lk {area_->mutex};
     213             : 
     214           0 :         area_->frameSize = frameSize;
     215           0 :         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           0 :         auto p = reinterpret_cast<std::uintptr_t>(area_->data);
     221           0 :         area_->writeOffset = ((p + 15) & ~15) - p;
     222           0 :         area_->readOffset = area_->writeOffset + frameSize;
     223           0 :     }
     224             : 
     225         266 :     return true;
     226             : }
     227             : 
     228             : void
     229           0 : ShmHolder::renderFrame(const VideoFrame& src) noexcept
     230             : {
     231           0 :     const auto width = src.width();
     232           0 :     const auto height = src.height();
     233           0 :     const auto format = AV_PIX_FMT_BGRA;
     234           0 :     const auto frameSize = videoFrameSize(format, width, height);
     235             : 
     236           0 :     if (!resizeArea(frameSize)) {
     237           0 :         JAMI_ERR("[ShmHolder:%s] Unable to resize area size: %dx%d, format: %d",
     238             :                  openedName_.c_str(),
     239             :                  width,
     240             :                  height,
     241             :                  format);
     242           0 :         return;
     243             :     }
     244             : 
     245             :     {
     246           0 :         VideoFrame dst;
     247           0 :         VideoScaler scaler;
     248             : 
     249           0 :         dst.setFromMemory(area_->data + area_->writeOffset, format, width, height);
     250           0 :         scaler.scale(src, dst);
     251           0 :     }
     252             : 
     253             :     {
     254           0 :         SemGuardLock lk {area_->mutex};
     255             : 
     256           0 :         ++area_->frameGen;
     257           0 :         std::swap(area_->readOffset, area_->writeOffset);
     258           0 :         ::sem_post(&area_->frameGenMutex);
     259           0 :     }
     260             : }
     261             : 
     262             : std::string
     263         430 : SinkClient::openedName() const noexcept
     264             : {
     265         430 :     if (shm_)
     266         430 :         return shm_->name();
     267           0 :     return {};
     268             : }
     269             : 
     270             : bool
     271         276 : SinkClient::start() noexcept
     272             : {
     273         276 :     if (not shm_) {
     274             :         try {
     275         266 :             char* envvar = getenv("JAMI_DISABLE_SHM");
     276         266 :             if (envvar) // Do not use SHM if set
     277           0 :                 return true;
     278         266 :             shm_ = std::make_shared<ShmHolder>();
     279         266 :             JAMI_DBG("[Sink:%p] Shared memory [%s] created", this, openedName().c_str());
     280           0 :         } catch (const std::runtime_error& e) {
     281           0 :             JAMI_ERR("[Sink:%p] Failed to create shared memory: %s", this, e.what());
     282           0 :         }
     283             :     }
     284             : 
     285         276 :     return static_cast<bool>(shm_);
     286             : }
     287             : 
     288             : bool
     289         298 : SinkClient::stop() noexcept
     290             : {
     291         298 :     setFrameSize(0, 0);
     292         298 :     setCrop(0, 0, 0, 0);
     293         298 :     shm_.reset();
     294         298 :     return true;
     295             : }
     296             : 
     297             : #else // ENABLE_SHM
     298             : 
     299             : std::string
     300             : SinkClient::openedName() const noexcept
     301             : {
     302             :     return {};
     303             : }
     304             : 
     305             : bool
     306             : SinkClient::start() noexcept
     307             : {
     308             :     return true;
     309             : }
     310             : 
     311             : bool
     312             : SinkClient::stop() noexcept
     313             : {
     314             :     setFrameSize(0, 0);
     315             :     setCrop(0, 0, 0, 0);
     316             :     return true;
     317             : }
     318             : 
     319             : #endif // !ENABLE_SHM
     320             : 
     321         247 : SinkClient::SinkClient(const std::string& id, bool mixer)
     322         247 :     : id_ {id}
     323         247 :     , mixer_(mixer)
     324         247 :     , scaler_(new VideoScaler())
     325             : #ifdef DEBUG_FPS
     326             :     , frameCount_(0u)
     327             :     , lastFrameDebug_(std::chrono::steady_clock::now())
     328             : #endif
     329             : {
     330         247 :     JAMI_DBG("[Sink:%p] Sink [%s] created", this, getId().c_str());
     331         247 : }
     332             : 
     333             : void
     334           0 : SinkClient::sendFrameDirect(const std::shared_ptr<jami::MediaFrame>& frame_p)
     335             : {
     336           0 :     notify(frame_p);
     337             : 
     338           0 :     libjami::FrameBuffer outFrame(av_frame_alloc());
     339           0 :     av_frame_ref(outFrame.get(), std::static_pointer_cast<VideoFrame>(frame_p)->pointer());
     340             : 
     341           0 :     if (crop_.w || crop_.h) {
     342             : #ifdef RING_ACCEL
     343           0 :         auto desc = av_pix_fmt_desc_get(
     344           0 :             (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           0 :         if (desc && (desc->flags & AV_PIX_FMT_FLAG_HWACCEL)) {
     350           0 :             std::shared_ptr<VideoFrame> frame = std::make_shared<VideoFrame>();
     351             :             try {
     352           0 :                 frame = HardwareAccel::transferToMainMemory(*std::static_pointer_cast<VideoFrame>(
     353           0 :                                                                 frame_p),
     354           0 :                                                             AV_PIX_FMT_NV12);
     355           0 :             } catch (const std::runtime_error& e) {
     356           0 :                 JAMI_ERR("[Sink:%p] Transfert to hardware acceleration memory failed: %s",
     357             :                          this,
     358             :                          e.what());
     359           0 :                 return;
     360           0 :             }
     361           0 :             if (not frame)
     362           0 :                 return;
     363           0 :             av_frame_unref(outFrame.get());
     364           0 :             av_frame_ref(outFrame.get(), frame->pointer());
     365           0 :         }
     366             : #endif
     367           0 :         outFrame->crop_top = crop_.y;
     368           0 :         outFrame->crop_bottom = (size_t) outFrame->height - crop_.y - crop_.h;
     369           0 :         outFrame->crop_left = crop_.x;
     370           0 :         outFrame->crop_right = (size_t) outFrame->width - crop_.x - crop_.w;
     371           0 :         av_frame_apply_cropping(outFrame.get(), AV_FRAME_CROP_UNALIGNED);
     372             :     }
     373           0 :     if (outFrame->height != height_ || outFrame->width != width_) {
     374           0 :         setFrameSize(outFrame->width, outFrame->height);
     375           0 :         return;
     376             :     }
     377           0 :     target_.push(std::move(outFrame));
     378           0 : }
     379             : 
     380             : void
     381           0 : SinkClient::sendFrameTransformed(AVFrame* frame)
     382             : {
     383           0 :     if (frame->width > 0 and frame->height > 0) {
     384           0 :         if (auto buffer_ptr = target_.pull()) {
     385           0 :             scaler_->scale(frame, buffer_ptr.get());
     386           0 :             target_.push(std::move(buffer_ptr));
     387           0 :         }
     388             :     }
     389           0 : }
     390             : 
     391             : std::shared_ptr<VideoFrame>
     392           0 : SinkClient::applyTransform(VideoFrame& frame_p)
     393             : {
     394           0 :     std::shared_ptr<VideoFrame> frame = std::make_shared<VideoFrame>();
     395             : #ifdef RING_ACCEL
     396           0 :     auto desc = av_pix_fmt_desc_get((AVPixelFormat) frame_p.format());
     397           0 :     if (desc && (desc->flags & AV_PIX_FMT_FLAG_HWACCEL)) {
     398             :         try {
     399           0 :             frame = HardwareAccel::transferToMainMemory(frame_p, AV_PIX_FMT_NV12);
     400           0 :         } catch (const std::runtime_error& e) {
     401           0 :             JAMI_ERR("[Sink:%p] Transfert to hardware acceleration memory failed: %s",
     402             :                      this,
     403             :                      e.what());
     404           0 :             return {};
     405           0 :         }
     406           0 :     } else
     407             : #endif
     408           0 :         frame->copyFrom(frame_p);
     409             : 
     410           0 :     int angle = frame->getOrientation();
     411           0 :     if (angle != rotation_) {
     412           0 :         filter_ = getTransposeFilter(angle,
     413             :                                      FILTER_INPUT_NAME,
     414             :                                      frame->width(),
     415             :                                      frame->height(),
     416             :                                      frame->format(),
     417           0 :                                      false);
     418           0 :         rotation_ = angle;
     419             :     }
     420           0 :     if (filter_) {
     421           0 :         filter_->feedInput(frame->pointer(), FILTER_INPUT_NAME);
     422           0 :         frame = std::static_pointer_cast<VideoFrame>(
     423           0 :             std::shared_ptr<MediaFrame>(filter_->readOutput()));
     424             :     }
     425           0 :     if (crop_.w || crop_.h) {
     426           0 :         frame->pointer()->crop_top = crop_.y;
     427           0 :         frame->pointer()->crop_bottom = (size_t) frame->height() - crop_.y - crop_.h;
     428           0 :         frame->pointer()->crop_left = crop_.x;
     429           0 :         frame->pointer()->crop_right = (size_t) frame->width() - crop_.x - crop_.w;
     430           0 :         av_frame_apply_cropping(frame->pointer(), AV_FRAME_CROP_UNALIGNED);
     431             :     }
     432           0 :     return frame;
     433           0 : }
     434             : 
     435             : void
     436        8524 : SinkClient::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();
     441             :     auto seconds = currentTime - lastFrameDebug_;
     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;
     447             :         lastFrameDebug_ = currentTime;
     448             :     }
     449             : #endif
     450             : 
     451        8524 :     std::unique_lock lock(mtx_);
     452        8524 :     bool hasObservers = getObserversCount() != 0;
     453        8524 :     bool hasDirectListener = target_.push and not target_.pull;
     454        8524 :     bool hasTransformedListener = target_.push and target_.pull;
     455             : 
     456        8524 :     if (hasDirectListener) {
     457           0 :         sendFrameDirect(frame_p);
     458           0 :         return;
     459             :     }
     460             : 
     461        8524 :     bool doTransfer = hasTransformedListener or hasObservers;
     462             : #ifdef ENABLE_SHM
     463        8524 :     doTransfer |= (shm_ && doShmTransfer_);
     464             : #endif
     465             : 
     466        8524 :     if (doTransfer) {
     467           0 :         auto frame = applyTransform(*std::static_pointer_cast<VideoFrame>(frame_p));
     468           0 :         if (not frame)
     469           0 :             return;
     470             : 
     471           0 :         notify(std::static_pointer_cast<MediaFrame>(frame));
     472             : 
     473           0 :         if (frame->height() != height_ || frame->width() != width_) {
     474           0 :             lock.unlock();
     475           0 :             setFrameSize(frame->width(), frame->height());
     476           0 :             return;
     477             :         }
     478             : #ifdef ENABLE_SHM
     479           0 :         if (shm_ && doShmTransfer_)
     480           0 :             shm_->renderFrame(*frame);
     481             : #endif
     482           0 :         if (hasTransformedListener)
     483           0 :             sendFrameTransformed(frame->pointer());
     484           0 :     }
     485        8524 : }
     486             : 
     487             : void
     488         437 : SinkClient::setFrameSize(int width, int height)
     489             : {
     490         437 :     width_ = width;
     491         437 :     height_ = height;
     492         437 :     if (width > 0 and height > 0) {
     493          85 :         JAMI_DBG("[Sink:%p] Started - size=%dx%d, mixer=%s",
     494             :                  this,
     495             :                  width,
     496             :                  height,
     497             :                  mixer_ ? "Yes" : "No");
     498          85 :         emitSignal<libjami::VideoSignal::DecodingStarted>(getId(),
     499          85 :                                                           openedName(),
     500             :                                                           width,
     501             :                                                           height,
     502          85 :                                                           mixer_);
     503          85 :         started_ = true;
     504         352 :     } else if (started_) {
     505          79 :         JAMI_DBG("[Sink:%p] Stopped - size=%dx%d, mixer=%s",
     506             :                  this,
     507             :                  width,
     508             :                  height,
     509             :                  mixer_ ? "Yes" : "No");
     510          79 :         emitSignal<libjami::VideoSignal::DecodingStopped>(getId(), openedName(), mixer_);
     511          79 :         started_ = false;
     512             :     }
     513         437 : }
     514             : 
     515             : void
     516         306 : SinkClient::setCrop(int x, int y, int w, int h)
     517             : {
     518         306 :     JAMI_DBG("[Sink:%p] Change crop to [%dx%d at (%d, %d)]", this, w, h, x, y);
     519         306 :     crop_.x = x;
     520         306 :     crop_.y = y;
     521         306 :     crop_.w = w;
     522         306 :     crop_.h = h;
     523         306 : }
     524             : 
     525             : } // namespace video
     526             : } // namespace jami

Generated by: LCOV version 1.14