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