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