LCOV - code coverage report
Current view: top level - src/media/video - video_input.cpp (source / functions) Coverage Total Hit
Test: jami-coverage-filtered.info Lines: 38.7 % 367 142
Test Date: 2026-08-23 08:52:56 Functions: 57.5 % 40 23

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

Generated by: LCOV version 2.0-1