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.6 % 360 139
Test Date: 2026-06-13 09:18:46 Functions: 35.9 % 78 28

            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           65 : VideoInput::VideoInput(VideoInputMode inputMode, const std::string& resource, const std::string& sink)
      53              :     : VideoGenerator::VideoGenerator()
      54          130 :     , loop_(std::bind(&VideoInput::setup, this),
      55          130 :             std::bind(&VideoInput::process, this),
      56          130 :             std::bind(&VideoInput::cleanup, this))
      57              : {
      58           65 :     inputMode_ = inputMode;
      59           65 :     if (inputMode_ == VideoInputMode::Undefined) {
      60              : #if (defined(__ANDROID__) || (defined(TARGET_OS_IOS) && TARGET_OS_IOS))
      61              :         inputMode_ = VideoInputMode::ManagedByClient;
      62              : #else
      63           59 :         inputMode_ = VideoInputMode::ManagedByDaemon;
      64              : #endif
      65              :     }
      66           65 :     sink_ = Manager::instance().createSinkClient(sink.empty() ? resource : sink);
      67           65 :     switchInput(resource);
      68           65 : }
      69              : 
      70           65 : VideoInput::~VideoInput()
      71              : {
      72           65 :     isStopped_ = true;
      73           65 :     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           65 :     loop_.join();
      81           65 : }
      82              : 
      83              : void
      84           59 : VideoInput::startLoop()
      85              : {
      86           59 :     if (videoManagedByClient()) {
      87            0 :         switchDevice();
      88            0 :         return;
      89              :     }
      90           59 :     if (!loop_.isRunning())
      91           59 :         loop_.start();
      92              : }
      93              : 
      94              : void
      95            0 : VideoInput::switchDevice()
      96              : {
      97            0 :     if (switchPending_.exchange(false)) {
      98            0 :         JAMI_LOG("Switching input to '{}'", decOpts_.input);
      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           64 : VideoInput::setup()
     150              : {
     151           64 :     if (not attach(sink_.get())) {
     152            0 :         JAMI_ERROR("attach sink failed");
     153            0 :         return false;
     154              :     }
     155              : 
     156           64 :     if (!sink_->start())
     157            0 :         JAMI_ERROR("start sink failed");
     158              : 
     159          256 :     JAMI_LOG("VideoInput ready to capture");
     160              : 
     161           64 :     return true;
     162              : }
     163              : 
     164              : void
     165           69 : VideoInput::process()
     166              : {
     167           69 :     if (playingFile_) {
     168           10 :         if (paused_ || !decoder_->emitFrame(false)) {
     169           10 :             std::this_thread::sleep_for(std::chrono::milliseconds(20));
     170              :         }
     171           10 :         return;
     172              :     }
     173           59 :     if (switchPending_)
     174           59 :         createDecoder();
     175              : 
     176           59 :     if (not captureFrame()) {
     177           59 :         loop_.stop();
     178           59 :         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           64 : VideoInput::cleanup()
     192              : {
     193           64 :     deleteDecoder(); // do it first to let a chance to last frame to be displayed
     194           64 :     stopSink();
     195          256 :     JAMI_LOG("VideoInput closed");
     196           64 : }
     197              : 
     198              : bool
     199           59 : VideoInput::captureFrame()
     200              : {
     201              :     // Return true if capture could continue, false if must be stop
     202           59 :     if (not decoder_)
     203           59 :         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_ERROR("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_WARNING("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          102 : VideoInput::setRecorderCallback(const std::function<void(const MediaStream& ms)>& cb)
     266              : {
     267          102 :     recorderCallback_ = cb;
     268          102 :     if (decoder_)
     269            0 :         decoder_->setContextCallback([this]() {
     270            0 :             if (recorderCallback_)
     271            0 :                 recorderCallback_(getInfo());
     272            0 :         });
     273          102 : }
     274              : 
     275              : void
     276           59 : VideoInput::createDecoder()
     277              : {
     278           59 :     deleteDecoder();
     279              : 
     280           59 :     switchPending_ = false;
     281              : 
     282           59 :     if (decOpts_.input.empty()) {
     283           59 :         foundDecOpts(decOpts_);
     284           59 :         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_ERROR("Unable to open input \"{}\" with status {}", decOpts_.input, 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_ERROR("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_LOG("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_WARNING("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_LOG("created decoder with video params : size={}X{}, fps={} pix={}",
     352              :              decOpts_.width,
     353              :              decOpts_.height,
     354              :              decOpts_.framerate.real(),
     355              :              decOpts_.pixel_format);
     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          128 : VideoInput::deleteDecoder()
     374              : {
     375          128 :     if (not decoder_)
     376          123 :         return;
     377            5 :     flushFrames();
     378            5 :     decoder_.reset();
     379              : }
     380              : 
     381              : void
     382           64 : VideoInput::clearOptions()
     383              : {
     384           64 :     decOpts_ = {};
     385           64 :     emulateRate_ = false;
     386           64 : }
     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_ERROR("file '{}' unavailable", path);
     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_WARNING("Guessing file type for {}", path);
     642              :     }
     643              : 
     644            0 :     return false;
     645            0 : }
     646              : 
     647              : void
     648           30 : VideoInput::restart()
     649              : {
     650           30 :     if (loop_.isStopping())
     651           13 :         switchInput(resource_);
     652           30 : }
     653              : 
     654              : std::shared_future<DeviceParams>
     655           78 : VideoInput::switchInput(const std::string& resource)
     656              : {
     657          312 :     JAMI_LOG("MRL: '{}'", resource);
     658              : 
     659           78 :     if (switchPending_.exchange(true)) {
     660            0 :         JAMI_ERROR("Video switch already requested");
     661            0 :         return {};
     662              :     }
     663              : 
     664           78 :     resource_ = resource;
     665           78 :     decOptsFound_ = false;
     666              : 
     667           78 :     std::promise<DeviceParams> p;
     668           78 :     foundDecOpts_.swap(p);
     669              : 
     670              :     // Switch off video input?
     671           78 :     if (resource_.empty()) {
     672           59 :         clearOptions();
     673           59 :         futureDecOpts_ = foundDecOpts_.get_future();
     674           59 :         startLoop();
     675           59 :         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            0 : }
     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           64 : VideoInput::foundDecOpts(const DeviceParams& params)
     738              : {
     739           64 :     if (not decOptsFound_) {
     740           64 :         decOptsFound_ = true;
     741           64 :         foundDecOpts_.set_value(params);
     742              :     }
     743           64 : }
     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           64 : VideoInput::stopSink()
     761              : {
     762           64 :     detach(sink_.get());
     763           64 :     sink_->stop();
     764           64 : }
     765              : 
     766              : void
     767           26 : VideoInput::updateStartTime(int64_t startTime)
     768              : {
     769           26 :     if (decoder_) {
     770           26 :         decoder_->updateStartTime(startTime);
     771              :     }
     772           26 : }
     773              : 
     774              : } // namespace video
     775              : } // namespace jami
        

Generated by: LCOV version 2.0-1