LCOV - code coverage report
Current view: top level - foo/src/media/video - video_input.cpp (source / functions) Hit Total Coverage
Test: jami-coverage-filtered.info Lines: 140 360 38.9 %
Date: 2026-02-28 10:41:24 Functions: 22 46 47.8 %

          Line data    Source code
       1             : /*
       2             :  *  Copyright (C) 2004-2026 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 "video_input.h"
      23             : 
      24             : #include "media_decoder.h"
      25             : #include "media_const.h"
      26             : #include "manager.h"
      27             : #include "client/videomanager.h"
      28             : #include "client/jami_signal.h"
      29             : #include "logger.h"
      30             : #include "media/media_buffer.h"
      31             : 
      32             : #include <libavformat/avio.h>
      33             : 
      34             : #include <string>
      35             : #include <cassert>
      36             : #ifdef _MSC_VER
      37             : #include <io.h> // for access
      38             : #else
      39             : #include <sys/syscall.h>
      40             : #include <unistd.h>
      41             : #endif
      42             : extern "C" {
      43             : #include <libavutil/display.h>
      44             : }
      45             : 
      46             : namespace jami {
      47             : namespace video {
      48             : 
      49             : static constexpr unsigned default_grab_width = 640;
      50             : static constexpr unsigned default_grab_height = 480;
      51             : 
      52          63 : VideoInput::VideoInput(VideoInputMode inputMode, const std::string& resource, const std::string& sink)
      53             :     : VideoGenerator::VideoGenerator()
      54         126 :     , loop_(std::bind(&VideoInput::setup, this),
      55         126 :             std::bind(&VideoInput::process, this),
      56         126 :             std::bind(&VideoInput::cleanup, this))
      57             : {
      58          63 :     inputMode_ = inputMode;
      59          63 :     if (inputMode_ == VideoInputMode::Undefined) {
      60             : #if (defined(__ANDROID__) || (defined(TARGET_OS_IOS) && TARGET_OS_IOS))
      61             :         inputMode_ = VideoInputMode::ManagedByClient;
      62             : #else
      63          57 :         inputMode_ = VideoInputMode::ManagedByDaemon;
      64             : #endif
      65             :     }
      66          63 :     sink_ = Manager::instance().createSinkClient(sink.empty() ? resource : sink);
      67          63 :     switchInput(resource);
      68          63 : }
      69             : 
      70          63 : VideoInput::~VideoInput()
      71             : {
      72          63 :     isStopped_ = true;
      73          63 :     if (videoManagedByClient()) {
      74             :         // Stop the video sink for videos that are managed by the client.
      75           0 :         cleanup();
      76           0 :         emitSignal<libjami::VideoSignal::StopCapture>(decOpts_.input);
      77           0 :         capturing_ = false;
      78           0 :         return;
      79             :     }
      80          63 :     loop_.join();
      81          63 : }
      82             : 
      83             : void
      84          60 : VideoInput::startLoop()
      85             : {
      86          60 :     if (videoManagedByClient()) {
      87           0 :         switchDevice();
      88           0 :         return;
      89             :     }
      90          60 :     if (!loop_.isRunning())
      91          60 :         loop_.start();
      92             : }
      93             : 
      94             : void
      95           0 : VideoInput::switchDevice()
      96             : {
      97           0 :     if (switchPending_.exchange(false)) {
      98           0 :         JAMI_DBG("Switching input to '%s'", decOpts_.input.c_str());
      99           0 :         if (decOpts_.input.empty()) {
     100           0 :             capturing_ = false;
     101           0 :             return;
     102             :         }
     103             : 
     104           0 :         emitSignal<libjami::VideoSignal::StartCapture>(decOpts_.input);
     105           0 :         capturing_ = true;
     106             :     }
     107             : }
     108             : 
     109             : int
     110         160 : VideoInput::getWidth() const
     111             : {
     112         160 :     if (videoManagedByClient()) {
     113           0 :         return static_cast<int>(decOpts_.width);
     114             :     }
     115         160 :     return decoder_ ? decoder_->getWidth() : 0;
     116             : }
     117             : 
     118             : int
     119         160 : VideoInput::getHeight() const
     120             : {
     121         160 :     if (videoManagedByClient()) {
     122           0 :         return static_cast<int>(decOpts_.height);
     123             :     }
     124         160 :     return decoder_ ? decoder_->getHeight() : 0;
     125             : }
     126             : 
     127             : AVPixelFormat
     128           0 : VideoInput::getPixelFormat() const
     129             : {
     130           0 :     if (!videoManagedByClient()) {
     131           0 :         return decoder_->getPixelFormat();
     132             :     }
     133           0 :     return (AVPixelFormat) std::stoi(decOpts_.format);
     134             : }
     135             : 
     136             : void
     137           0 : VideoInput::setRotation(int angle)
     138             : {
     139           0 :     std::shared_ptr<AVBufferRef> displayMatrix {av_buffer_alloc(sizeof(int32_t) * 9), [](AVBufferRef* buf) {
     140           0 :                                                     av_buffer_unref(&buf);
     141           0 :                                                 }};
     142           0 :     if (displayMatrix) {
     143           0 :         av_display_rotation_set(reinterpret_cast<int32_t*>(displayMatrix->data), angle);
     144           0 :         displayMatrix_ = std::move(displayMatrix);
     145             :     }
     146           0 : }
     147             : 
     148             : bool
     149          65 : VideoInput::setup()
     150             : {
     151          65 :     if (not attach(sink_.get())) {
     152           0 :         JAMI_ERR("attach sink failed");
     153           0 :         return false;
     154             :     }
     155             : 
     156          65 :     if (!sink_->start())
     157           0 :         JAMI_ERR("start sink failed");
     158             : 
     159          65 :     JAMI_DBG("VideoInput ready to capture");
     160             : 
     161          65 :     return true;
     162             : }
     163             : 
     164             : void
     165          69 : VideoInput::process()
     166             : {
     167          69 :     if (playingFile_) {
     168           9 :         if (paused_ || !decoder_->emitFrame(false)) {
     169           9 :             std::this_thread::sleep_for(std::chrono::milliseconds(20));
     170             :         }
     171           9 :         return;
     172             :     }
     173          60 :     if (switchPending_)
     174          60 :         createDecoder();
     175             : 
     176          60 :     if (not captureFrame()) {
     177          60 :         loop_.stop();
     178          60 :         return;
     179             :     }
     180             : }
     181             : 
     182             : void
     183           9 : VideoInput::setSeekTime(int64_t time)
     184             : {
     185           9 :     if (decoder_) {
     186           9 :         decoder_->setSeekTime(time);
     187             :     }
     188           9 : }
     189             : 
     190             : void
     191          65 : VideoInput::cleanup()
     192             : {
     193          65 :     deleteDecoder(); // do it first to let a chance to last frame to be displayed
     194          65 :     stopSink();
     195          65 :     JAMI_DBG("VideoInput closed");
     196          65 : }
     197             : 
     198             : bool
     199          60 : VideoInput::captureFrame()
     200             : {
     201             :     // Return true if capture could continue, false if must be stop
     202          60 :     if (not decoder_)
     203          60 :         return false;
     204             : 
     205           0 :     switch (decoder_->decode()) {
     206           0 :     case MediaDemuxer::Status::EndOfFile:
     207           0 :         createDecoder();
     208           0 :         return static_cast<bool>(decoder_);
     209           0 :     case MediaDemuxer::Status::ReadError:
     210           0 :         JAMI_ERR() << "Failed to decode frame";
     211           0 :         return false;
     212           0 :     default:
     213           0 :         return true;
     214             :     }
     215             : }
     216             : void
     217           9 : VideoInput::flushBuffers()
     218             : {
     219           9 :     if (decoder_) {
     220           9 :         decoder_->flushBuffers();
     221             :     }
     222           9 : }
     223             : 
     224             : void
     225           5 : VideoInput::configureFilePlayback(const std::string&, std::shared_ptr<MediaDemuxer>& demuxer, int index)
     226             : {
     227           5 :     deleteDecoder();
     228           5 :     clearOptions();
     229             : 
     230           0 :     auto decoder = std::make_unique<MediaDecoder>(demuxer, index, [this](std::shared_ptr<MediaFrame>&& frame) {
     231           0 :         publishFrame(std::static_pointer_cast<VideoFrame>(frame));
     232           5 :     });
     233           5 :     decoder->setInterruptCallback([](void* data) -> int { return not static_cast<VideoInput*>(data)->isCapturing(); },
     234             :                                   this);
     235           5 :     decoder->emulateRate();
     236             : 
     237           5 :     decoder_ = std::move(decoder);
     238           5 :     playingFile_ = true;
     239             : 
     240             :     // For DBUS it is imperative that we start the sink before setting the frame size
     241           5 :     sink_->start();
     242             :     /* Signal the client about readable sink */
     243           5 :     sink_->setFrameSize(decoder_->getWidth(), decoder_->getHeight());
     244             : 
     245           5 :     loop_.start();
     246             : 
     247           5 :     decOpts_.width = ((decoder_->getWidth() >> 3) << 3);
     248           5 :     decOpts_.height = ((decoder_->getHeight() >> 3) << 3);
     249           5 :     decOpts_.framerate = decoder_->getFps();
     250           5 :     AVPixelFormat fmt = decoder_->getPixelFormat();
     251           5 :     if (fmt != AV_PIX_FMT_NONE) {
     252           5 :         decOpts_.pixel_format = av_get_pix_fmt_name(fmt);
     253             :     } else {
     254           0 :         JAMI_WARN("Unable to determine pixel format, using default");
     255           0 :         decOpts_.pixel_format = av_get_pix_fmt_name(AV_PIX_FMT_YUV420P);
     256             :     }
     257             : 
     258           5 :     if (onSuccessfulSetup_)
     259           0 :         onSuccessfulSetup_(MEDIA_VIDEO, 0);
     260           5 :     foundDecOpts(decOpts_);
     261           5 :     futureDecOpts_ = foundDecOpts_.get_future().share();
     262           5 : }
     263             : 
     264             : void
     265         100 : VideoInput::setRecorderCallback(const std::function<void(const MediaStream& ms)>& cb)
     266             : {
     267         100 :     recorderCallback_ = cb;
     268         100 :     if (decoder_)
     269           0 :         decoder_->setContextCallback([this]() {
     270           0 :             if (recorderCallback_)
     271           0 :                 recorderCallback_(getInfo());
     272           0 :         });
     273         100 : }
     274             : 
     275             : void
     276          60 : VideoInput::createDecoder()
     277             : {
     278          60 :     deleteDecoder();
     279             : 
     280          60 :     switchPending_ = false;
     281             : 
     282          60 :     if (decOpts_.input.empty()) {
     283          60 :         foundDecOpts(decOpts_);
     284          60 :         return;
     285             :     }
     286             : 
     287           0 :     auto decoder = std::make_unique<MediaDecoder>([this](const std::shared_ptr<MediaFrame>& frame) mutable {
     288           0 :         publishFrame(std::static_pointer_cast<VideoFrame>(frame));
     289           0 :     });
     290             : 
     291           0 :     if (emulateRate_)
     292           0 :         decoder->emulateRate();
     293             : 
     294           0 :     decoder->setInterruptCallback([](void* data) -> int { return not static_cast<VideoInput*>(data)->isCapturing(); },
     295             :                                   this);
     296             : 
     297           0 :     bool ready = false, restartSink = false;
     298           0 :     if ((decOpts_.format == "x11grab" || decOpts_.format == "dxgigrab" || decOpts_.format == "pipewiregrab")
     299           0 :         && !decOpts_.is_area) {
     300           0 :         decOpts_.width = 0;
     301           0 :         decOpts_.height = 0;
     302             :     }
     303           0 :     while (!ready && !isStopped_) {
     304             :         // Retry to open the video till the input is opened
     305           0 :         int ret = decoder->openInput(decOpts_);
     306           0 :         ready = ret >= 0;
     307           0 :         if (ret < 0 && -ret != EBUSY) {
     308           0 :             JAMI_ERR("Unable to open input \"%s\" with status %i", decOpts_.input.c_str(), ret);
     309           0 :             foundDecOpts(decOpts_);
     310           0 :             return;
     311           0 :         } else if (-ret == EBUSY) {
     312             :             // If the device is busy, this means that it can be used by another call.
     313             :             // If this is the case, cleanup() can occurs and this will erase shmPath_
     314             :             // So, be sure to regenerate a correct shmPath for clients.
     315           0 :             restartSink = true;
     316             :         }
     317           0 :         std::this_thread::sleep_for(std::chrono::milliseconds(10));
     318             :     }
     319             : 
     320           0 :     if (isStopped_)
     321           0 :         return;
     322             : 
     323           0 :     if (restartSink && !isStopped_) {
     324           0 :         sink_->start();
     325             :     }
     326             : 
     327             :     /* Data available, finish the decoding */
     328           0 :     if (decoder->setupVideo() < 0) {
     329           0 :         JAMI_ERR("decoder IO startup failed");
     330           0 :         foundDecOpts(decOpts_);
     331           0 :         return;
     332             :     }
     333             : 
     334           0 :     auto ret = decoder->decode(); // Populate AVCodecContext fields
     335           0 :     if (ret == MediaDemuxer::Status::ReadError) {
     336           0 :         JAMI_INFO() << "Decoder error";
     337           0 :         return;
     338             :     }
     339             : 
     340           0 :     decOpts_.width = ((decoder->getWidth() >> 3) << 3);
     341           0 :     decOpts_.height = ((decoder->getHeight() >> 3) << 3);
     342           0 :     decOpts_.framerate = decoder->getFps();
     343           0 :     AVPixelFormat fmt = decoder->getPixelFormat();
     344           0 :     if (fmt != AV_PIX_FMT_NONE) {
     345           0 :         decOpts_.pixel_format = av_get_pix_fmt_name(fmt);
     346             :     } else {
     347           0 :         JAMI_WARN("Unable to determine pixel format, using default");
     348           0 :         decOpts_.pixel_format = av_get_pix_fmt_name(AV_PIX_FMT_YUV420P);
     349             :     }
     350             : 
     351           0 :     JAMI_DBG("created decoder with video params : size=%dX%d, fps=%lf pix=%s",
     352             :              decOpts_.width,
     353             :              decOpts_.height,
     354             :              decOpts_.framerate.real(),
     355             :              decOpts_.pixel_format.c_str());
     356           0 :     if (onSuccessfulSetup_)
     357           0 :         onSuccessfulSetup_(MEDIA_VIDEO, 0);
     358             : 
     359           0 :     decoder_ = std::move(decoder);
     360             : 
     361           0 :     foundDecOpts(decOpts_);
     362             : 
     363             :     /* Signal the client about readable sink */
     364           0 :     sink_->setFrameSize(decoder_->getWidth(), decoder_->getHeight());
     365             : 
     366           0 :     decoder_->setContextCallback([this]() {
     367           0 :         if (recorderCallback_)
     368           0 :             recorderCallback_(getInfo());
     369           0 :     });
     370           0 : }
     371             : 
     372             : void
     373         130 : VideoInput::deleteDecoder()
     374             : {
     375         130 :     if (not decoder_)
     376         125 :         return;
     377           5 :     flushFrames();
     378           5 :     decoder_.reset();
     379             : }
     380             : 
     381             : void
     382          65 : VideoInput::clearOptions()
     383             : {
     384          65 :     decOpts_ = {};
     385          65 :     emulateRate_ = false;
     386          65 : }
     387             : 
     388             : bool
     389           0 : VideoInput::isCapturing() const noexcept
     390             : {
     391           0 :     if (videoManagedByClient()) {
     392           0 :         return capturing_;
     393             :     }
     394           0 :     return loop_.isRunning();
     395             : }
     396             : 
     397             : bool
     398           0 : VideoInput::initCamera(const std::string& device)
     399             : {
     400           0 :     if (auto* dm = jami::getVideoDeviceMonitor()) {
     401           0 :         decOpts_ = dm->getDeviceParams(device);
     402           0 :         return true;
     403             :     }
     404           0 :     return false;
     405             : }
     406             : 
     407             : static constexpr unsigned
     408           0 : round2pow(unsigned i, unsigned n)
     409             : {
     410           0 :     return (i >> n) << n;
     411             : }
     412             : 
     413             : #if !defined(WIN32) && !defined(__APPLE__)
     414             : bool
     415           0 : VideoInput::initLinuxGrab(const std::string& display)
     416             : {
     417           0 :     auto* deviceMonitor = jami::getVideoDeviceMonitor();
     418           0 :     if (!deviceMonitor)
     419           0 :         return false;
     420             :     // Patterns (all platforms except Linux with Wayland)
     421             :     // full screen sharing:            :1+0,0 2560x1440                (SCREEN 1, POSITION 0X0, RESOLUTION 2560X1440)
     422             :     // area sharing:                   :1+882,211 1532x779             (SCREEN 1, POSITION 882x211, RESOLUTION 1532x779)
     423             :     // window sharing:                 :+1,0 0x0 window-id:0x0340021e  (POSITION 0X0)
     424             :     //
     425             :     // Pattern (Linux with Wayland)
     426             :     // full screen or window sharing:  pipewire pid:2861 fd:23 node:68
     427           0 :     size_t space = display.find(' ');
     428           0 :     std::string windowIdStr = "window-id:";
     429           0 :     size_t winIdPos = display.find(windowIdStr);
     430             : 
     431           0 :     DeviceParams p = deviceMonitor->getDeviceParams(DEVICE_DESKTOP);
     432           0 :     if (winIdPos != std::string::npos) {
     433           0 :         size_t endPos = display.find(' ', winIdPos + windowIdStr.size());
     434           0 :         p.window_id = display.substr(winIdPos + windowIdStr.size(),
     435           0 :                                      endPos - (winIdPos + windowIdStr.size())); // "0x0340021e";
     436           0 :         p.is_area = 0;
     437             :     }
     438           0 :     std::string fpsStr = "fps:";
     439           0 :     if (display.find(fpsStr) != std::string::npos) {
     440           0 :         size_t fpsPos = display.find(fpsStr) + fpsStr.size();
     441           0 :         int fps = std::stoi(display.substr(fpsPos));
     442           0 :         p.framerate = fps;
     443           0 :         JAMI_LOG("Custom framerate set to {} fps", fps);
     444             :     }
     445           0 :     if (display.find("pipewire") != std::string::npos) {
     446           0 :         std::string pidStr = "pid:";
     447           0 :         std::string fdStr = "fd:";
     448           0 :         std::string nodeStr = "node:";
     449             : 
     450           0 :         size_t pidPos = display.find(pidStr) + pidStr.size();
     451           0 :         size_t fdPos = display.find(fdStr) + fdStr.size();
     452           0 :         size_t nodePos = display.find(nodeStr) + nodeStr.size();
     453             : 
     454           0 :         pid_t pid = static_cast<pid_t>(std::stol(display.substr(pidPos)));
     455           0 :         int fd = std::stoi(display.substr(fdPos));
     456           0 :         if (pid != getpid()) {
     457             : #ifdef SYS_pidfd_getfd
     458             :             // We are unable to directly use a file descriptor that was opened in a different
     459             :             // process, so we try to duplicate it in the current process.
     460           0 :             int pidfd = static_cast<int>(syscall(SYS_pidfd_open, pid, 0));
     461           0 :             if (pidfd < 0) {
     462           0 :                 JAMI_ERROR("Unable to duplicate PipeWire fd: call to pidfd_open failed (errno = {})", errno);
     463           0 :                 return false;
     464             :             }
     465           0 :             fd = static_cast<int>(syscall(SYS_pidfd_getfd, pidfd, fd, 0));
     466           0 :             if (fd < 0) {
     467           0 :                 JAMI_ERROR("Unable to duplicate PipeWire fd: call to pidfd_getfd failed (errno = {})", errno);
     468           0 :                 return false;
     469             :             }
     470             : #else
     471             :             JAMI_ERROR("Unable to duplicate PipeWire fd: pidfd_getfd syscall not available");
     472             :             return false;
     473             : #endif
     474             :         }
     475           0 :         p.fd = fd;
     476           0 :         p.node = display.substr(nodePos);
     477           0 :     } else if (space != std::string::npos) {
     478           0 :         p.input = display.substr(1, space);
     479           0 :         if (p.window_id.empty()) {
     480           0 :             p.input = display.substr(0, space);
     481           0 :             auto splits = jami::split_string_to_unsigned(display.substr(space + 1), 'x');
     482             :             // round to 8 pixel block
     483           0 :             p.width = round2pow(splits[0], 3);
     484           0 :             p.height = round2pow(splits[1], 3);
     485           0 :             p.is_area = 1;
     486           0 :         }
     487             :     } else {
     488           0 :         p.input = display;
     489           0 :         p.width = default_grab_width;
     490           0 :         p.height = default_grab_height;
     491           0 :         p.is_area = 1;
     492             :     }
     493             : 
     494           0 :     decOpts_ = p;
     495           0 :     emulateRate_ = false;
     496             : 
     497           0 :     return true;
     498           0 : }
     499             : #endif
     500             : 
     501             : #ifdef __APPLE__
     502             : bool
     503             : VideoInput::initAVFoundation(const std::string& display)
     504             : {
     505             :     auto deviceMonitor = jami::getVideoDeviceMonitor();
     506             :     if (!deviceMonitor)
     507             :         return false;
     508             : 
     509             :     size_t space = display.find(' ');
     510             : 
     511             :     clearOptions();
     512             :     decOpts_.format = "avfoundation";
     513             :     decOpts_.pixel_format = "nv12";
     514             :     decOpts_.name = "Capture screen 0";
     515             :     decOpts_.input = "Capture screen 0";
     516             :     decOpts_.framerate = deviceMonitor->getDeviceParams(DEVICE_DESKTOP).framerate;
     517             : 
     518             :     if (space != std::string::npos) {
     519             :         std::istringstream iss(display.substr(space + 1));
     520             :         char sep;
     521             :         unsigned w, h;
     522             :         iss >> w >> sep >> h;
     523             :         decOpts_.width = round2pow(w, 3);
     524             :         decOpts_.height = round2pow(h, 3);
     525             :     } else {
     526             :         decOpts_.width = default_grab_width;
     527             :         decOpts_.height = default_grab_height;
     528             :     }
     529             :     return true;
     530             : }
     531             : #endif
     532             : 
     533             : #ifdef WIN32
     534             : bool
     535             : VideoInput::initWindowsGrab(const std::string& display)
     536             : {
     537             :     auto deviceMonitor = jami::getVideoDeviceMonitor();
     538             :     if (!deviceMonitor)
     539             :         return false;
     540             : 
     541             :     // Patterns
     542             :     // full screen sharing : :1+0,0 2560x1440 - SCREEN 1, POSITION 0X0, RESOLUTION 2560X1440
     543             :     // area sharing : :1+882,211 1532x779 - SCREEN 1, POSITION 882x211, RESOLUTION 1532x779
     544             :     // window sharing : :+1,0 0x0 window-id:HANDLE - POSITION 0X0
     545             :     size_t space = display.find(' ');
     546             :     std::string windowIdStr = "window-id:";
     547             :     size_t winHandlePos = display.find(windowIdStr);
     548             : 
     549             :     DeviceParams p = deviceMonitor->getDeviceParams(DEVICE_DESKTOP);
     550             :     if (winHandlePos != std::string::npos) {
     551             :         size_t endPos = display.find(' ', winHandlePos + windowIdStr.size());
     552             :         p.input = display.substr(winHandlePos + windowIdStr.size(),
     553             :                                  endPos - (winHandlePos + windowIdStr.size())); // "HANDLE";
     554             :         p.name = display.substr(winHandlePos + windowIdStr.size(),
     555             :                                 endPos - (winHandlePos + windowIdStr.size())); // "HANDLE";
     556             :         p.is_area = 0;
     557             :     } else {
     558             :         p.input = display.substr(1);
     559             :         p.name = display.substr(1);
     560             :         p.is_area = 1;
     561             :         if (space != std::string::npos) {
     562             :             auto splits = jami::split_string_to_unsigned(display.substr(space + 1), 'x');
     563             :             if (splits.size() != 2)
     564             :                 return false;
     565             : 
     566             :             // round to 8 pixel block
     567             :             p.width = splits[0];
     568             :             p.height = splits[1];
     569             : 
     570             :             size_t plus = display.find('+');
     571             :             auto position = display.substr(plus + 1, space - plus - 1);
     572             :             splits = jami::split_string_to_unsigned(position, ',');
     573             :             if (splits.size() != 2)
     574             :                 return false;
     575             :             p.offset_x = splits[0];
     576             :             p.offset_y = splits[1];
     577             :         } else {
     578             :             p.width = default_grab_width;
     579             :             p.height = default_grab_height;
     580             :         }
     581             :     }
     582             :     std::string fpsStr = "fps:";
     583             :     if (display.find(fpsStr) != std::string::npos) {
     584             :         size_t fpsPos = display.find(fpsStr) + fpsStr.size();
     585             :         int fps = std::stoi(display.substr(fpsPos));
     586             :         p.framerate = fps;
     587             :         JAMI_LOG("Custom framerate set to {} fps", fps);
     588             :     }
     589             : 
     590             :     auto dec = std::make_unique<MediaDecoder>();
     591             :     if (dec->openInput(p) < 0 || dec->setupVideo() < 0)
     592             :         return initCamera(deviceMonitor->getDefaultDevice());
     593             : 
     594             :     clearOptions();
     595             :     decOpts_ = p;
     596             :     decOpts_.width = dec->getStream().width;
     597             :     decOpts_.height = dec->getStream().height;
     598             : 
     599             :     return true;
     600             : }
     601             : #endif
     602             : 
     603             : bool
     604           0 : VideoInput::initFile(const std::string& path)
     605             : {
     606           0 :     auto* deviceMonitor = jami::getVideoDeviceMonitor();
     607           0 :     if (!deviceMonitor)
     608           0 :         return false;
     609             : 
     610           0 :     size_t dot = path.find_last_of('.');
     611           0 :     std::string ext = dot == std::string::npos ? "" : path.substr(dot + 1);
     612             : 
     613             :     /* File exists? */
     614           0 :     if (access(path.c_str(), R_OK) != 0) {
     615           0 :         JAMI_ERR("file '%s' unavailable\n", path.c_str());
     616           0 :         return false;
     617             :     }
     618             : 
     619             :     // check if file has video, fall back to default device if none
     620             :     // FIXME the way this is done is hackish, but it is unable to be done in createDecoder
     621             :     // because that would break the promise returned in switchInput
     622           0 :     DeviceParams p;
     623           0 :     p.input = path;
     624           0 :     p.name = path;
     625           0 :     auto dec = std::make_unique<MediaDecoder>();
     626           0 :     if (dec->openInput(p) < 0 || dec->setupVideo() < 0) {
     627           0 :         return initCamera(deviceMonitor->getDefaultDevice());
     628             :     }
     629             : 
     630           0 :     clearOptions();
     631           0 :     emulateRate_ = true;
     632           0 :     decOpts_.input = path;
     633           0 :     decOpts_.name = path;
     634           0 :     decOpts_.loop = "1";
     635             : 
     636             :     // Force 1fps for static image
     637           0 :     if (ext == "jpeg" || ext == "jpg" || ext == "png") {
     638           0 :         decOpts_.format = "image2";
     639           0 :         decOpts_.framerate = 1;
     640             :     } else {
     641           0 :         JAMI_WARN("Guessing file type for %s", path.c_str());
     642             :     }
     643             : 
     644           0 :     return false;
     645           0 : }
     646             : 
     647             : void
     648          30 : VideoInput::restart()
     649             : {
     650          30 :     if (loop_.isStopping())
     651          16 :         switchInput(resource_);
     652          30 : }
     653             : 
     654             : std::shared_future<DeviceParams>
     655          79 : VideoInput::switchInput(const std::string& resource)
     656             : {
     657          79 :     JAMI_DBG("MRL: '%s'", resource.c_str());
     658             : 
     659          79 :     if (switchPending_.exchange(true)) {
     660           0 :         JAMI_ERR("Video switch already requested");
     661           0 :         return {};
     662             :     }
     663             : 
     664          79 :     resource_ = resource;
     665          79 :     decOptsFound_ = false;
     666             : 
     667          79 :     std::promise<DeviceParams> p;
     668          79 :     foundDecOpts_.swap(p);
     669             : 
     670             :     // Switch off video input?
     671          79 :     if (resource_.empty()) {
     672          60 :         clearOptions();
     673          60 :         futureDecOpts_ = foundDecOpts_.get_future();
     674          60 :         startLoop();
     675          60 :         return futureDecOpts_;
     676             :     }
     677             : 
     678             :     // Supported MRL schemes
     679          19 :     static const std::string sep = libjami::Media::VideoProtocolPrefix::SEPARATOR;
     680             : 
     681          19 :     const auto pos = resource_.find(sep);
     682          19 :     if (pos == std::string::npos)
     683          19 :         return {};
     684             : 
     685           0 :     const auto prefix = resource_.substr(0, pos);
     686           0 :     if ((pos + sep.size()) >= resource_.size())
     687           0 :         return {};
     688             : 
     689           0 :     const auto suffix = resource_.substr(pos + sep.size());
     690             : 
     691           0 :     bool ready = false;
     692             : 
     693           0 :     if (prefix == libjami::Media::VideoProtocolPrefix::CAMERA) {
     694             :         /* Video4Linux2 */
     695           0 :         ready = initCamera(suffix);
     696           0 :     } else if (prefix == libjami::Media::VideoProtocolPrefix::DISPLAY) {
     697             :         /* X11 display name */
     698             : #ifdef __APPLE__
     699             :         ready = initAVFoundation(suffix);
     700             : #elif defined(WIN32)
     701             :         ready = initWindowsGrab(suffix);
     702             : #else
     703           0 :         ready = initLinuxGrab(suffix);
     704             : #endif
     705           0 :     } else if (prefix == libjami::Media::VideoProtocolPrefix::FILE) {
     706             :         /* Pathname */
     707           0 :         ready = initFile(suffix);
     708             :     }
     709             : 
     710           0 :     if (ready) {
     711           0 :         foundDecOpts(decOpts_);
     712             :     }
     713           0 :     futureDecOpts_ = foundDecOpts_.get_future().share();
     714           0 :     startLoop();
     715           0 :     return futureDecOpts_;
     716          79 : }
     717             : 
     718             : MediaStream
     719           3 : VideoInput::getInfo() const
     720             : {
     721           3 :     if (!videoManagedByClient()) {
     722           3 :         if (decoder_)
     723           0 :             return decoder_->getStream("v:local");
     724             :     }
     725           3 :     auto opts = futureDecOpts_.get();
     726           3 :     rational<int> fr(static_cast<int>(opts.framerate.numerator()), static_cast<int>(opts.framerate.denominator()));
     727             :     return MediaStream("v:local",
     728           3 :                        av_get_pix_fmt(opts.pixel_format.c_str()),
     729             :                        1 / fr,
     730           3 :                        static_cast<int>(opts.width),
     731           3 :                        static_cast<int>(opts.height),
     732             :                        0,
     733           6 :                        fr);
     734           3 : }
     735             : 
     736             : void
     737          65 : VideoInput::foundDecOpts(const DeviceParams& params)
     738             : {
     739          65 :     if (not decOptsFound_) {
     740          65 :         decOptsFound_ = true;
     741          65 :         foundDecOpts_.set_value(params);
     742             :     }
     743          65 : }
     744             : 
     745             : void
     746           0 : VideoInput::setSink(const std::string& sinkId)
     747             : {
     748           0 :     sink_ = Manager::instance().createSinkClient(sinkId);
     749           0 : }
     750             : 
     751             : void
     752           0 : VideoInput::setupSink(const int width, const int height)
     753             : {
     754           0 :     setup();
     755             :     /* Signal the client about readable sink */
     756           0 :     sink_->setFrameSize(width, height);
     757           0 : }
     758             : 
     759             : void
     760          65 : VideoInput::stopSink()
     761             : {
     762          65 :     detach(sink_.get());
     763          65 :     sink_->stop();
     764          65 : }
     765             : 
     766             : void
     767          18 : VideoInput::updateStartTime(int64_t startTime)
     768             : {
     769          18 :     if (decoder_) {
     770          18 :         decoder_->updateStartTime(startTime);
     771             :     }
     772          18 : }
     773             : 
     774             : } // namespace video
     775             : } // namespace jami

Generated by: LCOV version 1.14