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