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