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: 142 362 39.2 %
Date: 2025-08-24 09:11:10 Functions: 22 46 47.8 %

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

Generated by: LCOV version 1.14