LCOV - code coverage report
Current view: top level - src/media/video - video_input.cpp (source / functions) Hit Total Coverage
Test: jami-coverage-filtered.info Lines: 145 350 41.4 %
Date: 2024-04-19 08:05:40 Functions: 23 45 51.1 %

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

Generated by: LCOV version 1.14