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