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
|