Line data Source code
1 : /*
2 : * Copyright (C) 2004-2025 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 74 : VideoInput::VideoInput(VideoInputMode inputMode, const std::string& resource, const std::string& sink)
55 : : VideoGenerator::VideoGenerator()
56 148 : , loop_(std::bind(&VideoInput::setup, this),
57 148 : std::bind(&VideoInput::process, this),
58 148 : std::bind(&VideoInput::cleanup, this))
59 : {
60 74 : inputMode_ = inputMode;
61 74 : if (inputMode_ == VideoInputMode::Undefined) {
62 : #if (defined(__ANDROID__) || (defined(TARGET_OS_IOS) && TARGET_OS_IOS))
63 : inputMode_ = VideoInputMode::ManagedByClient;
64 : #else
65 63 : inputMode_ = VideoInputMode::ManagedByDaemon;
66 : #endif
67 : }
68 74 : sink_ = Manager::instance().createSinkClient(sink.empty() ? resource : sink);
69 74 : switchInput(resource);
70 74 : }
71 :
72 74 : VideoInput::~VideoInput()
73 : {
74 74 : isStopped_ = true;
75 74 : 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 74 : loop_.join();
83 74 : }
84 :
85 : void
86 67 : VideoInput::startLoop()
87 : {
88 67 : if (videoManagedByClient()) {
89 0 : switchDevice();
90 0 : return;
91 : }
92 67 : if (!loop_.isRunning())
93 67 : 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 129 : VideoInput::getWidth() const
113 : {
114 129 : if (videoManagedByClient()) {
115 0 : return decOpts_.width;
116 : }
117 129 : return decoder_ ? decoder_->getWidth() : 0;
118 : }
119 :
120 : int
121 129 : VideoInput::getHeight() const
122 : {
123 129 : if (videoManagedByClient()) {
124 0 : return decOpts_.height;
125 : }
126 129 : 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 77 : VideoInput::setup()
153 : {
154 77 : if (not attach(sink_.get())) {
155 0 : JAMI_ERR("attach sink failed");
156 0 : return false;
157 : }
158 :
159 77 : if (!sink_->start())
160 0 : JAMI_ERR("start sink failed");
161 :
162 77 : JAMI_DBG("VideoInput ready to capture");
163 :
164 77 : return true;
165 : }
166 :
167 : void
168 5670 : VideoInput::process()
169 : {
170 5670 : if (playingFile_) {
171 5603 : if (paused_ || !decoder_->emitFrame(false)) {
172 5603 : std::this_thread::sleep_for(std::chrono::milliseconds(20));
173 : }
174 5603 : return;
175 : }
176 67 : if (switchPending_)
177 67 : createDecoder();
178 :
179 67 : if (not captureFrame()) {
180 67 : loop_.stop();
181 67 : 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 77 : VideoInput::cleanup()
195 : {
196 77 : deleteDecoder(); // do it first to let a chance to last frame to be displayed
197 77 : stopSink();
198 77 : JAMI_DBG("VideoInput closed");
199 77 : }
200 :
201 : bool
202 67 : VideoInput::captureFrame()
203 : {
204 : // Return true if capture could continue, false if must be stop
205 67 : if (not decoder_)
206 67 : 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 67 : VideoInput::createDecoder()
286 : {
287 67 : deleteDecoder();
288 :
289 67 : switchPending_ = false;
290 :
291 67 : if (decOpts_.input.empty()) {
292 67 : foundDecOpts(decOpts_);
293 67 : 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 154 : VideoInput::deleteDecoder()
383 : {
384 154 : if (not decoder_)
385 144 : return;
386 10 : flushFrames();
387 10 : decoder_.reset();
388 : }
389 :
390 : void
391 77 : VideoInput::clearOptions()
392 : {
393 77 : decOpts_ = {};
394 77 : emulateRate_ = false;
395 77 : }
396 :
397 : bool
398 0 : VideoInput::isCapturing() const noexcept
399 : {
400 0 : if (videoManagedByClient()) {
401 0 : return capturing_;
402 : }
403 0 : return loop_.isRunning();
404 : }
405 :
406 : bool
407 0 : VideoInput::initCamera(const std::string& device)
408 : {
409 0 : if (auto dm = jami::getVideoDeviceMonitor()) {
410 0 : decOpts_ = dm->getDeviceParams(device);
411 0 : return true;
412 : }
413 0 : return false;
414 : }
415 :
416 : static constexpr unsigned
417 0 : round2pow(unsigned i, unsigned n)
418 : {
419 0 : return (i >> n) << n;
420 : }
421 :
422 : #if !defined(WIN32) && !defined(__APPLE__)
423 : bool
424 0 : VideoInput::initLinuxGrab(const std::string& display)
425 : {
426 0 : auto deviceMonitor = jami::getVideoDeviceMonitor();
427 0 : if (!deviceMonitor)
428 0 : return false;
429 : // Patterns (all platforms except Linux with Wayland)
430 : // full screen sharing: :1+0,0 2560x1440 (SCREEN 1, POSITION 0X0, RESOLUTION 2560X1440)
431 : // area sharing: :1+882,211 1532x779 (SCREEN 1, POSITION 882x211, RESOLUTION 1532x779)
432 : // window sharing: :+1,0 0x0 window-id:0x0340021e (POSITION 0X0)
433 : //
434 : // Pattern (Linux with Wayland)
435 : // full screen or window sharing: pipewire pid:2861 fd:23 node:68
436 0 : size_t space = display.find(' ');
437 0 : std::string windowIdStr = "window-id:";
438 0 : size_t winIdPos = display.find(windowIdStr);
439 :
440 0 : DeviceParams p = deviceMonitor->getDeviceParams(DEVICE_DESKTOP);
441 0 : if (winIdPos != std::string::npos) {
442 0 : size_t endPos = display.find(' ', winIdPos + windowIdStr.size());
443 0 : p.window_id = display.substr(winIdPos + windowIdStr.size(),
444 0 : endPos - (winIdPos + windowIdStr.size())); // "0x0340021e";
445 0 : p.is_area = 0;
446 : }
447 0 : std::string fpsStr = "fps:";
448 0 : if (display.find(fpsStr) != std::string::npos) {
449 0 : size_t fpsPos = display.find(fpsStr) + fpsStr.size();
450 0 : int fps = std::stoi(display.substr(fpsPos));
451 0 : p.framerate = fps;
452 0 : JAMI_LOG("Custom framerate set to {} fps", fps);
453 : }
454 0 : if (display.find("pipewire") != std::string::npos) {
455 0 : std::string pidStr = "pid:";
456 0 : std::string fdStr = "fd:";
457 0 : std::string nodeStr = "node:";
458 :
459 0 : size_t pidPos = display.find(pidStr) + pidStr.size();
460 0 : size_t fdPos = display.find(fdStr) + fdStr.size();
461 0 : size_t nodePos = display.find(nodeStr) + nodeStr.size();
462 :
463 0 : pid_t pid = std::stol(display.substr(pidPos));
464 0 : int fd = std::stoi(display.substr(fdPos));
465 0 : if (pid != getpid()) {
466 : #ifdef SYS_pidfd_getfd
467 : // We are unable to directly use a file descriptor that was opened in a different
468 : // process, so we try to duplicate it in the current process.
469 0 : int pidfd = syscall(SYS_pidfd_open, pid, 0);
470 0 : if (pidfd < 0) {
471 0 : JAMI_ERROR("Unable to duplicate PipeWire fd: call to pidfd_open failed (errno = {})", errno);
472 0 : return false;
473 : }
474 0 : fd = syscall(SYS_pidfd_getfd, pidfd, fd, 0);
475 0 : if (fd < 0) {
476 0 : JAMI_ERROR("Unable to duplicate PipeWire fd: call to pidfd_getfd failed (errno = {})", errno);
477 0 : return false;
478 : }
479 : #else
480 : JAMI_ERROR("Unable to duplicate PipeWire fd: pidfd_getfd syscall not available");
481 : return false;
482 : #endif
483 : }
484 0 : p.fd = fd;
485 0 : p.node = display.substr(nodePos);
486 0 : } else if (space != std::string::npos) {
487 0 : p.input = display.substr(1, space);
488 0 : if (p.window_id.empty()) {
489 0 : p.input = display.substr(0, space);
490 0 : auto splits = jami::split_string_to_unsigned(display.substr(space + 1), 'x');
491 : // round to 8 pixel block
492 0 : p.width = round2pow(splits[0], 3);
493 0 : p.height = round2pow(splits[1], 3);
494 0 : p.is_area = 1;
495 0 : }
496 : } else {
497 0 : p.input = display;
498 0 : p.width = default_grab_width;
499 0 : p.height = default_grab_height;
500 0 : p.is_area = 1;
501 : }
502 :
503 0 : decOpts_ = p;
504 0 : emulateRate_ = false;
505 :
506 0 : return true;
507 0 : }
508 : #endif
509 :
510 : #ifdef __APPLE__
511 : bool
512 : VideoInput::initAVFoundation(const std::string& display)
513 : {
514 : auto deviceMonitor = jami::getVideoDeviceMonitor();
515 : if (!deviceMonitor)
516 : return false;
517 :
518 : size_t space = display.find(' ');
519 :
520 : clearOptions();
521 : decOpts_.format = "avfoundation";
522 : decOpts_.pixel_format = "nv12";
523 : decOpts_.name = "Capture screen 0";
524 : decOpts_.input = "Capture screen 0";
525 : decOpts_.framerate = deviceMonitor->getDeviceParams(DEVICE_DESKTOP).framerate;
526 :
527 : if (space != std::string::npos) {
528 : std::istringstream iss(display.substr(space + 1));
529 : char sep;
530 : unsigned w, h;
531 : iss >> w >> sep >> h;
532 : decOpts_.width = round2pow(w, 3);
533 : decOpts_.height = round2pow(h, 3);
534 : } else {
535 : decOpts_.width = default_grab_width;
536 : decOpts_.height = default_grab_height;
537 : }
538 : return true;
539 : }
540 : #endif
541 :
542 : #ifdef WIN32
543 : bool
544 : VideoInput::initWindowsGrab(const std::string& display)
545 : {
546 : auto deviceMonitor = jami::getVideoDeviceMonitor();
547 : if (!deviceMonitor)
548 : return false;
549 :
550 : // Patterns
551 : // full screen sharing : :1+0,0 2560x1440 - SCREEN 1, POSITION 0X0, RESOLUTION 2560X1440
552 : // area sharing : :1+882,211 1532x779 - SCREEN 1, POSITION 882x211, RESOLUTION 1532x779
553 : // window sharing : :+1,0 0x0 window-id:HANDLE - POSITION 0X0
554 : size_t space = display.find(' ');
555 : std::string windowIdStr = "window-id:";
556 : size_t winHandlePos = display.find(windowIdStr);
557 :
558 : DeviceParams p = deviceMonitor->getDeviceParams(DEVICE_DESKTOP);
559 : if (winHandlePos != std::string::npos) {
560 : size_t endPos = display.find(' ', winHandlePos + windowIdStr.size());
561 : p.input = display.substr(winHandlePos + windowIdStr.size(),
562 : endPos - (winHandlePos + windowIdStr.size())); // "HANDLE";
563 : p.name = display.substr(winHandlePos + windowIdStr.size(),
564 : endPos - (winHandlePos + windowIdStr.size())); // "HANDLE";
565 : p.is_area = 0;
566 : } else {
567 : p.input = display.substr(1);
568 : p.name = display.substr(1);
569 : p.is_area = 1;
570 : if (space != std::string::npos) {
571 : auto splits = jami::split_string_to_unsigned(display.substr(space + 1), 'x');
572 : if (splits.size() != 2)
573 : return false;
574 :
575 : // round to 8 pixel block
576 : p.width = splits[0];
577 : p.height = splits[1];
578 :
579 : size_t plus = display.find('+');
580 : auto position = display.substr(plus + 1, space - plus - 1);
581 : splits = jami::split_string_to_unsigned(position, ',');
582 : if (splits.size() != 2)
583 : return false;
584 : p.offset_x = splits[0];
585 : p.offset_y = splits[1];
586 : } else {
587 : p.width = default_grab_width;
588 : p.height = default_grab_height;
589 : }
590 : }
591 : std::string fpsStr = "fps:";
592 : if (display.find(fpsStr) != std::string::npos) {
593 : size_t fpsPos = display.find(fpsStr) + fpsStr.size();
594 : int fps = std::stoi(display.substr(fpsPos));
595 : p.framerate = fps;
596 : JAMI_LOG("Custom framerate set to {} fps", fps);
597 : }
598 :
599 : auto dec = std::make_unique<MediaDecoder>();
600 : if (dec->openInput(p) < 0 || dec->setupVideo() < 0)
601 : return initCamera(deviceMonitor->getDefaultDevice());
602 :
603 : clearOptions();
604 : decOpts_ = p;
605 : decOpts_.width = dec->getStream().width;
606 : decOpts_.height = dec->getStream().height;
607 :
608 : return true;
609 : }
610 : #endif
611 :
612 : bool
613 0 : VideoInput::initFile(std::string path)
614 : {
615 0 : auto deviceMonitor = jami::getVideoDeviceMonitor();
616 0 : if (!deviceMonitor)
617 0 : return false;
618 :
619 0 : size_t dot = path.find_last_of('.');
620 0 : std::string ext = dot == std::string::npos ? "" : path.substr(dot + 1);
621 :
622 : /* File exists? */
623 0 : if (access(path.c_str(), R_OK) != 0) {
624 0 : JAMI_ERR("file '%s' unavailable\n", path.c_str());
625 0 : return false;
626 : }
627 :
628 : // check if file has video, fall back to default device if none
629 : // FIXME the way this is done is hackish, but it is unable to be done in createDecoder
630 : // because that would break the promise returned in switchInput
631 0 : DeviceParams p;
632 0 : p.input = path;
633 0 : p.name = path;
634 0 : auto dec = std::make_unique<MediaDecoder>();
635 0 : if (dec->openInput(p) < 0 || dec->setupVideo() < 0) {
636 0 : return initCamera(deviceMonitor->getDefaultDevice());
637 : }
638 :
639 0 : clearOptions();
640 0 : emulateRate_ = true;
641 0 : decOpts_.input = path;
642 0 : decOpts_.name = path;
643 0 : decOpts_.loop = "1";
644 :
645 : // Force 1fps for static image
646 0 : if (ext == "jpeg" || ext == "jpg" || ext == "png") {
647 0 : decOpts_.format = "image2";
648 0 : decOpts_.framerate = 1;
649 : } else {
650 0 : JAMI_WARN("Guessing file type for %s", path.c_str());
651 : }
652 :
653 0 : return false;
654 0 : }
655 :
656 : void
657 35 : VideoInput::restart()
658 : {
659 35 : if (loop_.isStopping())
660 17 : switchInput(resource_);
661 35 : }
662 :
663 : std::shared_future<DeviceParams>
664 91 : VideoInput::switchInput(const std::string& resource)
665 : {
666 91 : JAMI_DBG("MRL: '%s'", resource.c_str());
667 :
668 91 : if (switchPending_.exchange(true)) {
669 0 : JAMI_ERR("Video switch already requested");
670 0 : return {};
671 : }
672 :
673 91 : resource_ = resource;
674 91 : decOptsFound_ = false;
675 :
676 91 : std::promise<DeviceParams> p;
677 91 : foundDecOpts_.swap(p);
678 :
679 : // Switch off video input?
680 91 : if (resource_.empty()) {
681 67 : clearOptions();
682 67 : futureDecOpts_ = foundDecOpts_.get_future();
683 67 : startLoop();
684 67 : return futureDecOpts_;
685 : }
686 :
687 : // Supported MRL schemes
688 24 : static const std::string sep = libjami::Media::VideoProtocolPrefix::SEPARATOR;
689 :
690 24 : const auto pos = resource_.find(sep);
691 24 : if (pos == std::string::npos)
692 24 : return {};
693 :
694 0 : const auto prefix = resource_.substr(0, pos);
695 0 : if ((pos + sep.size()) >= resource_.size())
696 0 : return {};
697 :
698 0 : const auto suffix = resource_.substr(pos + sep.size());
699 :
700 0 : bool ready = false;
701 :
702 0 : if (prefix == libjami::Media::VideoProtocolPrefix::CAMERA) {
703 : /* Video4Linux2 */
704 0 : ready = initCamera(suffix);
705 0 : } else if (prefix == libjami::Media::VideoProtocolPrefix::DISPLAY) {
706 : /* X11 display name */
707 : #ifdef __APPLE__
708 : ready = initAVFoundation(suffix);
709 : #elif defined(WIN32)
710 : ready = initWindowsGrab(suffix);
711 : #else
712 0 : ready = initLinuxGrab(suffix);
713 : #endif
714 0 : } else if (prefix == libjami::Media::VideoProtocolPrefix::FILE) {
715 : /* Pathname */
716 0 : ready = initFile(suffix);
717 : }
718 :
719 0 : if (ready) {
720 0 : foundDecOpts(decOpts_);
721 : }
722 0 : futureDecOpts_ = foundDecOpts_.get_future().share();
723 0 : startLoop();
724 0 : return futureDecOpts_;
725 91 : }
726 :
727 : MediaStream
728 5 : VideoInput::getInfo() const
729 : {
730 5 : if (!videoManagedByClient()) {
731 5 : if (decoder_)
732 2 : return decoder_->getStream("v:local");
733 : }
734 3 : auto opts = futureDecOpts_.get();
735 3 : rational<int> fr(opts.framerate.numerator(), opts.framerate.denominator());
736 : return MediaStream("v:local",
737 3 : av_get_pix_fmt(opts.pixel_format.c_str()),
738 : 1 / fr,
739 3 : opts.width,
740 3 : opts.height,
741 : 0,
742 6 : fr);
743 3 : }
744 :
745 : void
746 77 : VideoInput::foundDecOpts(const DeviceParams& params)
747 : {
748 77 : if (not decOptsFound_) {
749 77 : decOptsFound_ = true;
750 77 : foundDecOpts_.set_value(params);
751 : }
752 77 : }
753 :
754 : void
755 0 : VideoInput::setSink(const std::string& sinkId)
756 : {
757 0 : sink_ = Manager::instance().createSinkClient(sinkId);
758 0 : }
759 :
760 : void
761 0 : VideoInput::setupSink(const int width, const int height)
762 : {
763 0 : setup();
764 : /* Signal the client about readable sink */
765 0 : sink_->setFrameSize(width, height);
766 0 : }
767 :
768 : void
769 77 : VideoInput::stopSink()
770 : {
771 77 : detach(sink_.get());
772 77 : sink_->stop();
773 77 : }
774 :
775 : void
776 28 : VideoInput::updateStartTime(int64_t startTime)
777 : {
778 28 : if (decoder_) {
779 28 : decoder_->updateStartTime(startTime);
780 : }
781 28 : }
782 :
783 : } // namespace video
784 : } // namespace jami
|