Ring Daemon
Loading...
Searching...
No Matches
video_input.cpp
Go to the documentation of this file.
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
42extern "C" {
43#include <libavutil/display.h>
44}
45
46namespace jami {
47namespace video {
48
49static constexpr unsigned default_grab_width = 640;
50static constexpr unsigned default_grab_height = 480;
51
52VideoInput::VideoInput(VideoInputMode inputMode, const std::string& resource, const std::string& sink)
54 , loop_(std::bind(&VideoInput::setup, this),
55 std::bind(&VideoInput::process, this),
56 std::bind(&VideoInput::cleanup, this))
57{
58 inputMode_ = inputMode;
59 if (inputMode_ == VideoInputMode::Undefined) {
60#if (defined(__ANDROID__) || (defined(TARGET_OS_IOS) && TARGET_OS_IOS))
62#else
64#endif
65 }
66 sink_ = Manager::instance().createSinkClient(sink.empty() ? resource : sink);
67 switchInput(resource);
68}
69
71{
72 isStopped_ = true;
73 if (videoManagedByClient()) {
74 // Stop the video sink for videos that are managed by the client.
75 cleanup();
77 capturing_ = false;
78 return;
79 }
80 loop_.join();
81}
82
83void
84VideoInput::startLoop()
85{
86 if (videoManagedByClient()) {
87 switchDevice();
88 return;
89 }
90 if (!loop_.isRunning())
91 loop_.start();
92}
93
94void
95VideoInput::switchDevice()
96{
97 if (switchPending_.exchange(false)) {
98 JAMI_DBG("Switching input to '%s'", decOpts_.input.c_str());
99 if (decOpts_.input.empty()) {
100 capturing_ = false;
101 return;
102 }
103
105 capturing_ = true;
106 }
107}
108
109int
111{
112 if (videoManagedByClient()) {
113 return static_cast<int>(decOpts_.width);
114 }
115 return decoder_ ? decoder_->getWidth() : 0;
116}
117
118int
120{
121 if (videoManagedByClient()) {
122 return static_cast<int>(decOpts_.height);
123 }
124 return decoder_ ? decoder_->getHeight() : 0;
125}
126
129{
130 if (!videoManagedByClient()) {
131 return decoder_->getPixelFormat();
132 }
133 return (AVPixelFormat) std::stoi(decOpts_.format);
134}
135
136void
137VideoInput::setRotation(int angle)
138{
139 std::shared_ptr<AVBufferRef> displayMatrix {av_buffer_alloc(sizeof(int32_t) * 9), [](AVBufferRef* buf) {
141 }};
142 if (displayMatrix) {
143 av_display_rotation_set(reinterpret_cast<int32_t*>(displayMatrix->data), angle);
144 displayMatrix_ = std::move(displayMatrix);
145 }
146}
147
148bool
149VideoInput::setup()
150{
151 if (not attach(sink_.get())) {
152 JAMI_ERR("attach sink failed");
153 return false;
154 }
155
156 if (!sink_->start())
157 JAMI_ERR("start sink failed");
158
159 JAMI_DBG("VideoInput ready to capture");
160
161 return true;
162}
163
164void
165VideoInput::process()
166{
167 if (playingFile_) {
168 if (paused_ || !decoder_->emitFrame(false)) {
169 std::this_thread::sleep_for(std::chrono::milliseconds(20));
170 }
171 return;
172 }
173 if (switchPending_)
174 createDecoder();
175
176 if (not captureFrame()) {
177 loop_.stop();
178 return;
179 }
180}
181
182void
184{
185 if (decoder_) {
186 decoder_->setSeekTime(time);
187 }
188}
189
190void
191VideoInput::cleanup()
192{
193 deleteDecoder(); // do it first to let a chance to last frame to be displayed
194 stopSink();
195 JAMI_DBG("VideoInput closed");
196}
197
198bool
199VideoInput::captureFrame()
200{
201 // Return true if capture could continue, false if must be stop
202 if (not decoder_)
203 return false;
204
205 switch (decoder_->decode()) {
207 createDecoder();
208 return static_cast<bool>(decoder_);
210 JAMI_ERR() << "Failed to decode frame";
211 return false;
212 default:
213 return true;
214 }
215}
216void
218{
219 if (decoder_) {
220 decoder_->flushBuffers();
221 }
222}
223
224void
225VideoInput::configureFilePlayback(const std::string&, std::shared_ptr<MediaDemuxer>& demuxer, int index)
226{
227 deleteDecoder();
228 clearOptions();
229
230 auto decoder = std::make_unique<MediaDecoder>(demuxer, index, [this](std::shared_ptr<MediaFrame>&& frame) {
231 publishFrame(std::static_pointer_cast<VideoFrame>(frame));
232 });
233 decoder->setInterruptCallback([](void* data) -> int { return not static_cast<VideoInput*>(data)->isCapturing(); },
234 this);
235 decoder->emulateRate();
236
237 decoder_ = std::move(decoder);
238 playingFile_ = true;
239
240 // For DBUS it is imperative that we start the sink before setting the frame size
241 sink_->start();
242 /* Signal the client about readable sink */
243 sink_->setFrameSize(decoder_->getWidth(), decoder_->getHeight());
244
245 loop_.start();
246
247 decOpts_.width = ((decoder_->getWidth() >> 3) << 3);
248 decOpts_.height = ((decoder_->getHeight() >> 3) << 3);
249 decOpts_.framerate = decoder_->getFps();
250 AVPixelFormat fmt = decoder_->getPixelFormat();
251 if (fmt != AV_PIX_FMT_NONE) {
252 decOpts_.pixel_format = av_get_pix_fmt_name(fmt);
253 } else {
254 JAMI_WARN("Unable to determine pixel format, using default");
256 }
257
258 if (onSuccessfulSetup_)
259 onSuccessfulSetup_(MEDIA_VIDEO, 0);
260 foundDecOpts(decOpts_);
261 futureDecOpts_ = foundDecOpts_.get_future().share();
262}
263
264void
265VideoInput::setRecorderCallback(const std::function<void(const MediaStream& ms)>& cb)
266{
267 recorderCallback_ = cb;
268 if (decoder_)
269 decoder_->setContextCallback([this]() {
270 if (recorderCallback_)
271 recorderCallback_(getInfo());
272 });
273}
274
275void
276VideoInput::createDecoder()
277{
278 deleteDecoder();
279
280 switchPending_ = false;
281
282 if (decOpts_.input.empty()) {
283 foundDecOpts(decOpts_);
284 return;
285 }
286
287 auto decoder = std::make_unique<MediaDecoder>([this](const std::shared_ptr<MediaFrame>& frame) mutable {
288 publishFrame(std::static_pointer_cast<VideoFrame>(frame));
289 });
290
291 if (emulateRate_)
292 decoder->emulateRate();
293
294 decoder->setInterruptCallback([](void* data) -> int { return not static_cast<VideoInput*>(data)->isCapturing(); },
295 this);
296
297 bool ready = false, restartSink = false;
298 if ((decOpts_.format == "x11grab" || decOpts_.format == "dxgigrab" || decOpts_.format == "pipewiregrab")
299 && !decOpts_.is_area) {
300 decOpts_.width = 0;
301 decOpts_.height = 0;
302 }
303 while (!ready && !isStopped_) {
304 // Retry to open the video till the input is opened
305 int ret = decoder->openInput(decOpts_);
306 ready = ret >= 0;
307 if (ret < 0 && -ret != EBUSY) {
308 JAMI_ERR("Unable to open input \"%s\" with status %i", decOpts_.input.c_str(), ret);
309 foundDecOpts(decOpts_);
310 return;
311 } 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 restartSink = true;
316 }
317 std::this_thread::sleep_for(std::chrono::milliseconds(10));
318 }
319
320 if (isStopped_)
321 return;
322
323 if (restartSink && !isStopped_) {
324 sink_->start();
325 }
326
327 /* Data available, finish the decoding */
328 if (decoder->setupVideo() < 0) {
329 JAMI_ERR("decoder IO startup failed");
330 foundDecOpts(decOpts_);
331 return;
332 }
333
334 auto ret = decoder->decode(); // Populate AVCodecContext fields
336 JAMI_INFO() << "Decoder error";
337 return;
338 }
339
340 decOpts_.width = ((decoder->getWidth() >> 3) << 3);
341 decOpts_.height = ((decoder->getHeight() >> 3) << 3);
342 decOpts_.framerate = decoder->getFps();
343 AVPixelFormat fmt = decoder->getPixelFormat();
344 if (fmt != AV_PIX_FMT_NONE) {
345 decOpts_.pixel_format = av_get_pix_fmt_name(fmt);
346 } else {
347 JAMI_WARN("Unable to determine pixel format, using default");
349 }
350
351 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 if (onSuccessfulSetup_)
357 onSuccessfulSetup_(MEDIA_VIDEO, 0);
358
359 decoder_ = std::move(decoder);
360
361 foundDecOpts(decOpts_);
362
363 /* Signal the client about readable sink */
364 sink_->setFrameSize(decoder_->getWidth(), decoder_->getHeight());
365
366 decoder_->setContextCallback([this]() {
367 if (recorderCallback_)
368 recorderCallback_(getInfo());
369 });
370}
371
372void
373VideoInput::deleteDecoder()
374{
375 if (not decoder_)
376 return;
377 flushFrames();
378 decoder_.reset();
379}
380
381void
382VideoInput::clearOptions()
383{
384 decOpts_ = {};
385 emulateRate_ = false;
386}
387
388bool
389VideoInput::isCapturing() const noexcept
390{
391 if (videoManagedByClient()) {
392 return capturing_;
393 }
394 return loop_.isRunning();
395}
396
397bool
398VideoInput::initCamera(const std::string& device)
399{
400 if (auto* dm = jami::getVideoDeviceMonitor()) {
401 decOpts_ = dm->getDeviceParams(device);
402 return true;
403 }
404 return false;
405}
406
407static constexpr unsigned
408round2pow(unsigned i, unsigned n)
409{
410 return (i >> n) << n;
411}
412
413#if !defined(WIN32) && !defined(__APPLE__)
414bool
415VideoInput::initLinuxGrab(const std::string& display)
416{
418 if (!deviceMonitor)
419 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 size_t space = display.find(' ');
428 std::string windowIdStr = "window-id:";
429 size_t winIdPos = display.find(windowIdStr);
430
431 DeviceParams p = deviceMonitor->getDeviceParams(DEVICE_DESKTOP);
432 if (winIdPos != std::string::npos) {
433 size_t endPos = display.find(' ', winIdPos + windowIdStr.size());
434 p.window_id = display.substr(winIdPos + windowIdStr.size(),
435 endPos - (winIdPos + windowIdStr.size())); // "0x0340021e";
436 p.is_area = 0;
437 }
438 std::string fpsStr = "fps:";
439 if (display.find(fpsStr) != std::string::npos) {
440 size_t fpsPos = display.find(fpsStr) + fpsStr.size();
441 int fps = std::stoi(display.substr(fpsPos));
442 p.framerate = fps;
443 JAMI_LOG("Custom framerate set to {} fps", fps);
444 }
445 if (display.find("pipewire") != std::string::npos) {
446 std::string pidStr = "pid:";
447 std::string fdStr = "fd:";
448 std::string nodeStr = "node:";
449
450 size_t pidPos = display.find(pidStr) + pidStr.size();
451 size_t fdPos = display.find(fdStr) + fdStr.size();
452 size_t nodePos = display.find(nodeStr) + nodeStr.size();
453
454 pid_t pid = static_cast<pid_t>(std::stol(display.substr(pidPos)));
455 int fd = std::stoi(display.substr(fdPos));
456 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 int pidfd = static_cast<int>(syscall(SYS_pidfd_open, pid, 0));
461 if (pidfd < 0) {
462 JAMI_ERROR("Unable to duplicate PipeWire fd: call to pidfd_open failed (errno = {})", errno);
463 return false;
464 }
465 fd = static_cast<int>(syscall(SYS_pidfd_getfd, pidfd, fd, 0));
466 if (fd < 0) {
467 JAMI_ERROR("Unable to duplicate PipeWire fd: call to pidfd_getfd failed (errno = {})", errno);
468 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 p.fd = fd;
476 p.node = display.substr(nodePos);
477 } else if (space != std::string::npos) {
478 p.input = display.substr(1, space);
479 if (p.window_id.empty()) {
480 p.input = display.substr(0, space);
481 auto splits = jami::split_string_to_unsigned(display.substr(space + 1), 'x');
482 // round to 8 pixel block
483 p.width = round2pow(splits[0], 3);
484 p.height = round2pow(splits[1], 3);
485 p.is_area = 1;
486 }
487 } else {
488 p.input = display;
491 p.is_area = 1;
492 }
493
494 decOpts_ = p;
495 emulateRate_ = false;
496
497 return true;
498}
499#endif
500
501#ifdef __APPLE__
502bool
503VideoInput::initAVFoundation(const std::string& display)
504{
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
534bool
535VideoInput::initWindowsGrab(const std::string& display)
536{
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);
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
603bool
604VideoInput::initFile(const std::string& path)
605{
607 if (!deviceMonitor)
608 return false;
609
610 size_t dot = path.find_last_of('.');
611 std::string ext = dot == std::string::npos ? "" : path.substr(dot + 1);
612
613 /* File exists? */
614 if (access(path.c_str(), R_OK) != 0) {
615 JAMI_ERR("file '%s' unavailable\n", path.c_str());
616 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 DeviceParams p;
623 p.input = path;
624 p.name = path;
625 auto dec = std::make_unique<MediaDecoder>();
626 if (dec->openInput(p) < 0 || dec->setupVideo() < 0) {
627 return initCamera(deviceMonitor->getDefaultDevice());
628 }
629
630 clearOptions();
631 emulateRate_ = true;
632 decOpts_.input = path;
633 decOpts_.name = path;
634 decOpts_.loop = "1";
635
636 // Force 1fps for static image
637 if (ext == "jpeg" || ext == "jpg" || ext == "png") {
638 decOpts_.format = "image2";
639 decOpts_.framerate = 1;
640 } else {
641 JAMI_WARN("Guessing file type for %s", path.c_str());
642 }
643
644 return false;
645}
646
647void
649{
650 if (loop_.isStopping())
651 switchInput(resource_);
652}
653
654std::shared_future<DeviceParams>
655VideoInput::switchInput(const std::string& resource)
656{
657 JAMI_DBG("MRL: '%s'", resource.c_str());
658
659 if (switchPending_.exchange(true)) {
660 JAMI_ERR("Video switch already requested");
661 return {};
662 }
663
664 resource_ = resource;
665 decOptsFound_ = false;
666
667 std::promise<DeviceParams> p;
668 foundDecOpts_.swap(p);
669
670 // Switch off video input?
671 if (resource_.empty()) {
672 clearOptions();
673 futureDecOpts_ = foundDecOpts_.get_future();
674 startLoop();
675 return futureDecOpts_;
676 }
677
678 // Supported MRL schemes
679 static const std::string sep = libjami::Media::VideoProtocolPrefix::SEPARATOR;
680
681 const auto pos = resource_.find(sep);
682 if (pos == std::string::npos)
683 return {};
684
685 const auto prefix = resource_.substr(0, pos);
686 if ((pos + sep.size()) >= resource_.size())
687 return {};
688
689 const auto suffix = resource_.substr(pos + sep.size());
690
691 bool ready = false;
692
694 /* Video4Linux2 */
695 ready = initCamera(suffix);
697 /* X11 display name */
698#ifdef __APPLE__
699 ready = initAVFoundation(suffix);
700#elif defined(WIN32)
701 ready = initWindowsGrab(suffix);
702#else
703 ready = initLinuxGrab(suffix);
704#endif
706 /* Pathname */
707 ready = initFile(suffix);
708 }
709
710 if (ready) {
711 foundDecOpts(decOpts_);
712 }
713 futureDecOpts_ = foundDecOpts_.get_future().share();
714 startLoop();
715 return futureDecOpts_;
716}
717
718MediaStream
720{
721 if (!videoManagedByClient()) {
722 if (decoder_)
723 return decoder_->getStream("v:local");
724 }
725 auto opts = futureDecOpts_.get();
726 rational<int> fr(static_cast<int>(opts.framerate.numerator()), static_cast<int>(opts.framerate.denominator()));
727 return MediaStream("v:local",
728 av_get_pix_fmt(opts.pixel_format.c_str()),
729 1 / fr,
730 static_cast<int>(opts.width),
731 static_cast<int>(opts.height),
732 0,
733 fr);
734}
735
736void
737VideoInput::foundDecOpts(const DeviceParams& params)
738{
739 if (not decOptsFound_) {
740 decOptsFound_ = true;
741 foundDecOpts_.set_value(params);
742 }
743}
744
745void
746VideoInput::setSink(const std::string& sinkId)
747{
748 sink_ = Manager::instance().createSinkClient(sinkId);
749}
750
751void
752VideoInput::setupSink(const int width, const int height)
753{
754 setup();
755 /* Signal the client about readable sink */
756 sink_->setFrameSize(width, height);
757}
758
759void
761{
762 detach(sink_.get());
763 sink_->stop();
764}
765
766void
768{
769 if (decoder_) {
770 decoder_->updateStartTime(startTime);
771 }
772}
773
774} // namespace video
775} // namespace jami
static LIBJAMI_TEST_EXPORT Manager & instance()
Definition manager.cpp:694
bool attach(Observer< std::shared_ptr< MediaFrame > > *o)
Definition observer.h:69
bool detach(Observer< std::shared_ptr< MediaFrame > > *o)
Definition observer.h:100
bool isStopping() const noexcept
Definition threadloop.h:52
bool isRunning() const noexcept
virtual void stop()
Naive implementation of the boost::rational interface, described here: https://www....
Definition rational.h:39
constexpr R real() const
Definition rational.h:89
void updateStartTime(int64_t startTime)
void setSeekTime(int64_t time)
void setupSink(const int width, const int height)
void restart()
Restart stopped video input.
void configureFilePlayback(const std::string &path, std::shared_ptr< MediaDemuxer > &demuxer, int index)
void setRecorderCallback(const std::function< void(const MediaStream &ms)> &cb)
AVPixelFormat getPixelFormat() const
MediaStream getInfo() const
VideoInput(VideoInputMode inputMode=VideoInputMode::Undefined, const std::string &resource="local", const std::string &sink="")
void setSink(const std::string &sinkId)
void av_buffer_unref(AVBufferRef **buf)
#define JAMI_ERR(...)
Definition logger.h:230
#define JAMI_ERROR(formatstr,...)
Definition logger.h:243
#define JAMI_DBG(...)
Definition logger.h:228
#define JAMI_WARN(...)
Definition logger.h:229
#define JAMI_LOG(formatstr,...)
Definition logger.h:237
#define JAMI_INFO(...)
Definition logger.h:227
static constexpr const char DEVICE_DESKTOP[]
static constexpr unsigned default_grab_width
static constexpr unsigned default_grab_height
static constexpr unsigned round2pow(unsigned i, unsigned n)
void emitSignal(Args... args)
Definition jami_signal.h:64
std::vector< unsigned > split_string_to_unsigned(std::string_view str, char delim)
@ MEDIA_VIDEO
Definition media_codec.h:47
static constexpr const char * DISPLAY
Definition media_const.h:29
static constexpr const char * CAMERA
Definition media_const.h:31
static constexpr const char * SEPARATOR
Definition media_const.h:32
static constexpr const char * FILE
Definition media_const.h:30
DeviceParams Parameters used by MediaDecoder and MediaEncoder to open a LibAV device/stream.
std::string window_id
std::string format
rational< double > framerate
std::string input
std::string pixel_format