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 : #include "videomanager_interface.h"
19 : #include "videomanager.h"
20 : #include "localrecorder.h"
21 : #include "localrecordermanager.h"
22 : #include "libav_utils.h"
23 : #include "video/video_input.h"
24 : #include "video/video_device_monitor.h"
25 : #include "logger.h"
26 : #include "manager.h"
27 : #ifdef ENABLE_VIDEO
28 : #endif
29 : #include "audio/ringbufferpool.h"
30 : #include "jami/media_const.h"
31 : #include "libav_utils.h"
32 :
33 : #include <functional>
34 : #include <memory>
35 : #include <string>
36 : #include <vector>
37 : #include <new> // std::bad_alloc
38 : #include <algorithm>
39 : #include <cstdlib>
40 : #include <cstring> // std::memset
41 :
42 : extern "C" {
43 : #include <libavutil/display.h>
44 : }
45 :
46 : namespace libjami {
47 :
48 59117 : MediaFrame::MediaFrame()
49 59117 : : frame_ {av_frame_alloc()}
50 : {
51 59117 : if (not frame_)
52 0 : throw std::bad_alloc();
53 59117 : }
54 :
55 : void
56 8901 : MediaFrame::copyFrom(const MediaFrame& o)
57 : {
58 8901 : reset();
59 8901 : if (o.frame_) {
60 8901 : av_frame_ref(frame_.get(), o.frame_.get());
61 8901 : av_frame_copy_props(frame_.get(), o.frame_.get());
62 : }
63 :
64 8901 : if (o.packet_) {
65 0 : packet_.reset(av_packet_alloc());
66 0 : av_packet_ref(packet_.get(), o.packet_.get());
67 : }
68 8901 : }
69 :
70 : void
71 8961 : MediaFrame::reset() noexcept
72 : {
73 8961 : if (frame_)
74 8961 : av_frame_unref(frame_.get());
75 8961 : packet_.reset();
76 8961 : }
77 :
78 : void
79 0 : MediaFrame::setPacket(PacketBuffer&& pkt)
80 : {
81 0 : packet_ = std::move(pkt);
82 0 : }
83 :
84 19926 : AudioFrame::AudioFrame(const jami::AudioFormat& format, size_t nb_samples)
85 19926 : : MediaFrame()
86 : {
87 19926 : setFormat(format);
88 19926 : if (nb_samples)
89 8 : reserve(nb_samples);
90 19926 : }
91 :
92 : void
93 19926 : AudioFrame::setFormat(const jami::AudioFormat& format)
94 : {
95 19926 : auto* d = pointer();
96 19926 : av_channel_layout_default(&d->ch_layout, static_cast<int>(format.nb_channels));
97 19926 : d->sample_rate = static_cast<int>(format.sample_rate);
98 19926 : d->format = format.sampleFormat;
99 19926 : }
100 :
101 : jami::AudioFormat
102 0 : AudioFrame::getFormat() const
103 : {
104 0 : return {(unsigned) frame_->sample_rate, (unsigned) frame_->ch_layout.nb_channels, (AVSampleFormat) frame_->format};
105 : }
106 :
107 : size_t
108 0 : AudioFrame::getFrameSize() const
109 : {
110 0 : return frame_->nb_samples;
111 : }
112 :
113 : void
114 8 : AudioFrame::reserve(size_t nb_samples)
115 : {
116 8 : if (nb_samples != 0) {
117 8 : auto* d = pointer();
118 8 : d->nb_samples = static_cast<int>(nb_samples);
119 : int err;
120 8 : if ((err = av_frame_get_buffer(d, 0)) < 0) {
121 0 : throw std::bad_alloc();
122 : }
123 : }
124 8 : }
125 :
126 : void
127 1 : AudioFrame::mix(const AudioFrame& frame)
128 : {
129 1 : auto& f = *pointer();
130 1 : auto& fIn = *frame.pointer();
131 1 : if (f.ch_layout.nb_channels != fIn.ch_layout.nb_channels || f.format != fIn.format
132 1 : || f.sample_rate != fIn.sample_rate) {
133 0 : throw std::invalid_argument("Unable to mix frames with different formats");
134 : }
135 1 : if (f.nb_samples == 0) {
136 0 : reserve(fIn.nb_samples);
137 0 : jami::libav_utils::fillWithSilence(&f);
138 1 : } else if (f.nb_samples != fIn.nb_samples) {
139 0 : throw std::invalid_argument("Unable to mix frames with different length");
140 : }
141 1 : AVSampleFormat fmt = (AVSampleFormat) f.format;
142 1 : bool isPlanar = av_sample_fmt_is_planar(fmt);
143 1 : unsigned samplesPerChannel = isPlanar ? f.nb_samples : f.nb_samples * f.ch_layout.nb_channels;
144 1 : unsigned channels = isPlanar ? f.ch_layout.nb_channels : 1;
145 1 : if (fmt == AV_SAMPLE_FMT_S16 || fmt == AV_SAMPLE_FMT_S16P) {
146 2 : for (unsigned i = 0; i < channels; i++) {
147 1 : auto* c = (int16_t*) f.extended_data[i];
148 1 : auto* cIn = (int16_t*) fIn.extended_data[i];
149 1921 : for (unsigned s = 0; s < samplesPerChannel; s++) {
150 0 : c[s] = static_cast<int16_t>(std::clamp((int32_t) c[s] + (int32_t) cIn[s],
151 1920 : (int32_t) std::numeric_limits<int16_t>::min(),
152 3840 : (int32_t) std::numeric_limits<int16_t>::max()));
153 : }
154 : }
155 1 : } else if (fmt == AV_SAMPLE_FMT_FLT || fmt == AV_SAMPLE_FMT_FLTP) {
156 0 : for (unsigned i = 0; i < channels; i++) {
157 0 : auto* c = (float*) f.extended_data[i];
158 0 : auto* cIn = (float*) fIn.extended_data[i];
159 0 : for (unsigned s = 0; s < samplesPerChannel; s++) {
160 0 : c[s] += cIn[s];
161 : }
162 : }
163 0 : } else {
164 0 : throw std::invalid_argument(std::string("Unsupported format for mixing: ") + av_get_sample_fmt_name(fmt));
165 : }
166 1 : }
167 :
168 : float
169 0 : AudioFrame::calcRMS() const
170 : {
171 0 : double rms = 0.0;
172 0 : auto fmt = static_cast<AVSampleFormat>(frame_->format);
173 0 : bool planar = av_sample_fmt_is_planar(fmt);
174 0 : int perChannel = planar ? frame_->nb_samples : frame_->nb_samples * frame_->ch_layout.nb_channels;
175 0 : int channels = planar ? frame_->ch_layout.nb_channels : 1;
176 0 : if (fmt == AV_SAMPLE_FMT_S16 || fmt == AV_SAMPLE_FMT_S16P) {
177 0 : for (int c = 0; c < channels; ++c) {
178 0 : auto* buf = reinterpret_cast<int16_t*>(frame_->extended_data[c]);
179 0 : for (int i = 0; i < perChannel; ++i) {
180 0 : auto sample = static_cast<float>(buf[i]) * 0.000030517578125f;
181 0 : rms += sample * sample;
182 : }
183 : }
184 0 : } else if (fmt == AV_SAMPLE_FMT_FLT || fmt == AV_SAMPLE_FMT_FLTP) {
185 0 : for (int c = 0; c < channels; ++c) {
186 0 : auto* buf = reinterpret_cast<float*>(frame_->extended_data[c]);
187 0 : for (int i = 0; i < perChannel; ++i) {
188 0 : rms += buf[i] * buf[i];
189 : }
190 : }
191 0 : } else {
192 : // Should not happen
193 0 : JAMI_ERROR("Unsupported format for getting volume level: {}", av_get_sample_fmt_name(fmt));
194 0 : return 0.0;
195 : }
196 : // divide by the number of multi-byte samples
197 0 : return static_cast<float>(sqrt(rms / (frame_->nb_samples * frame_->ch_layout.nb_channels)));
198 : }
199 :
200 : #ifdef ENABLE_VIDEO
201 :
202 54790 : VideoFrame::~VideoFrame()
203 : {
204 39019 : if (releaseBufferCb_)
205 0 : releaseBufferCb_(ptr_);
206 54790 : }
207 :
208 : void
209 8961 : VideoFrame::reset() noexcept
210 : {
211 8961 : MediaFrame::reset();
212 8961 : allocated_ = false;
213 8961 : releaseBufferCb_ = {};
214 8961 : }
215 :
216 : void
217 8901 : VideoFrame::copyFrom(const VideoFrame& o)
218 : {
219 8901 : MediaFrame::copyFrom(o);
220 8901 : ptr_ = o.ptr_;
221 8901 : allocated_ = o.allocated_;
222 8901 : }
223 :
224 : size_t
225 0 : VideoFrame::size() const noexcept
226 : {
227 0 : return av_image_get_buffer_size((AVPixelFormat) frame_->format, frame_->width, frame_->height, 1);
228 : }
229 :
230 : int
231 35605 : VideoFrame::format() const noexcept
232 : {
233 35605 : return frame_->format;
234 : }
235 :
236 : int
237 36341 : VideoFrame::width() const noexcept
238 : {
239 36341 : return frame_->width;
240 : }
241 :
242 : int
243 50554 : VideoFrame::height() const noexcept
244 : {
245 50554 : return frame_->height;
246 : }
247 :
248 : void
249 30051 : VideoFrame::setGeometry(int format, int width, int height) noexcept
250 : {
251 30051 : frame_->format = format;
252 30051 : frame_->width = width;
253 30051 : frame_->height = height;
254 30051 : }
255 :
256 : void
257 29991 : VideoFrame::reserve(int format, int width, int height)
258 : {
259 29991 : auto* libav_frame = frame_.get();
260 :
261 29991 : if (allocated_) {
262 : // nothing to do if same properties
263 0 : if (width == libav_frame->width and height == libav_frame->height and format == libav_frame->format)
264 0 : av_frame_unref(libav_frame);
265 : }
266 :
267 29991 : setGeometry(format, width, height);
268 29991 : if (av_frame_get_buffer(libav_frame, 32))
269 0 : throw std::bad_alloc();
270 29991 : allocated_ = true;
271 29991 : releaseBufferCb_ = {};
272 29991 : }
273 :
274 : void
275 60 : VideoFrame::setFromMemory(uint8_t* ptr, int format, int width, int height) noexcept
276 : {
277 60 : reset();
278 60 : setGeometry(format, width, height);
279 60 : if (not ptr)
280 0 : return;
281 60 : av_image_fill_arrays(frame_->data,
282 60 : frame_->linesize,
283 : (uint8_t*) ptr,
284 60 : (AVPixelFormat) frame_->format,
285 : width,
286 : height,
287 : 1);
288 : }
289 :
290 : void
291 0 : VideoFrame::setFromMemory(
292 : uint8_t* ptr, int format, int width, int height, const std::function<void(uint8_t*)>& cb) noexcept
293 : {
294 0 : setFromMemory(ptr, format, width, height);
295 0 : if (cb) {
296 0 : releaseBufferCb_ = cb;
297 0 : ptr_ = ptr;
298 : }
299 0 : }
300 :
301 : void
302 0 : VideoFrame::setReleaseCb(const std::function<void(uint8_t*)>& cb) noexcept
303 : {
304 0 : if (cb) {
305 0 : releaseBufferCb_ = cb;
306 : }
307 0 : }
308 :
309 : void
310 0 : VideoFrame::noise()
311 : {
312 0 : auto* f = frame_.get();
313 0 : if (f->data[0] == nullptr)
314 0 : return;
315 0 : for (std::size_t i = 0; i < size(); ++i) {
316 0 : f->data[0][i] = std::rand() & 255;
317 : }
318 : }
319 :
320 : int
321 23849 : VideoFrame::getOrientation() const
322 : {
323 23849 : int32_t* matrix {nullptr};
324 23849 : if (auto* p = packet()) {
325 0 : matrix = reinterpret_cast<int32_t*>(av_packet_get_side_data(p, AV_PKT_DATA_DISPLAYMATRIX, nullptr));
326 23849 : } else if (const auto* p = pointer()) {
327 23849 : if (AVFrameSideData* side_data = av_frame_get_side_data(p, AV_FRAME_DATA_DISPLAYMATRIX)) {
328 0 : matrix = reinterpret_cast<int32_t*>(side_data->data);
329 : }
330 : }
331 23849 : if (matrix) {
332 0 : double angle = av_display_rotation_get(matrix);
333 0 : return std::isnan(angle) ? 0 : -(int) angle;
334 : }
335 23849 : return 0;
336 : }
337 :
338 : VideoFrame*
339 0 : getNewFrame(std::string_view id)
340 : {
341 0 : if (auto* vm = jami::Manager::instance().getVideoManager())
342 0 : if (auto input = vm->getVideoInput(id))
343 0 : return &input->getNewFrame();
344 0 : JAMI_WARNING("getNewFrame: Unable to find input {}", id);
345 0 : return nullptr;
346 : }
347 :
348 : void
349 0 : publishFrame(std::string_view id)
350 : {
351 0 : if (auto* vm = jami::Manager::instance().getVideoManager())
352 0 : if (auto input = vm->getVideoInput(id))
353 0 : input->publishFrame();
354 0 : }
355 :
356 : void
357 0 : registerVideoHandlers(const std::map<std::string, std::shared_ptr<CallbackWrapperBase>>& handlers)
358 : {
359 0 : registerSignalHandlers(handlers);
360 0 : }
361 :
362 : std::vector<std::string>
363 0 : getDeviceList()
364 : {
365 0 : if (auto* vm = jami::Manager::instance().getVideoManager())
366 0 : return vm->videoDeviceMonitor.getDeviceList();
367 0 : return {};
368 : }
369 :
370 : VideoCapabilities
371 0 : getCapabilities(const std::string& deviceId)
372 : {
373 0 : if (auto* vm = jami::Manager::instance().getVideoManager())
374 0 : return vm->videoDeviceMonitor.getCapabilities(deviceId);
375 0 : return {};
376 : }
377 :
378 : std::string
379 0 : getDefaultDevice()
380 : {
381 0 : if (auto* vm = jami::Manager::instance().getVideoManager())
382 0 : return vm->videoDeviceMonitor.getDefaultDevice();
383 0 : return {};
384 : }
385 :
386 : void
387 0 : setDefaultDevice(const std::string& deviceId)
388 : {
389 0 : JAMI_LOG("Setting default device to {}", deviceId);
390 0 : if (auto* vm = jami::Manager::instance().getVideoManager()) {
391 0 : if (vm->videoDeviceMonitor.setDefaultDevice(deviceId))
392 0 : jami::Manager::instance().saveConfig();
393 : }
394 0 : }
395 :
396 : void
397 0 : setDeviceOrientation(const std::string& deviceId, int angle)
398 : {
399 0 : if (auto* vm = jami::Manager::instance().getVideoManager())
400 0 : vm->setDeviceOrientation(deviceId, angle);
401 0 : }
402 :
403 : std::map<std::string, std::string>
404 0 : getDeviceParams(const std::string& deviceId)
405 : {
406 0 : if (auto* vm = jami::Manager::instance().getVideoManager()) {
407 0 : auto params = vm->videoDeviceMonitor.getDeviceParams(deviceId);
408 : return {{"format", params.format},
409 0 : {"width", std::to_string(params.width)},
410 0 : {"height", std::to_string(params.height)},
411 0 : {"rate", params.framerate.to_string()}};
412 0 : }
413 0 : return {};
414 0 : }
415 :
416 : std::map<std::string, std::string>
417 0 : getSettings(const std::string& deviceId)
418 : {
419 0 : if (auto* vm = jami::Manager::instance().getVideoManager()) {
420 0 : return vm->videoDeviceMonitor.getSettings(deviceId).to_map();
421 : }
422 0 : return {};
423 : }
424 :
425 : void
426 0 : applySettings(const std::string& deviceId, const std::map<std::string, std::string>& settings)
427 : {
428 0 : if (auto* vm = jami::Manager::instance().getVideoManager()) {
429 0 : vm->videoDeviceMonitor.applySettings(deviceId, settings);
430 0 : jami::Manager::instance().saveConfig();
431 : }
432 0 : }
433 :
434 : std::string
435 0 : openVideoInput(const std::string& path)
436 : {
437 0 : if (auto* vm = jami::Manager::instance().getVideoManager()) {
438 0 : auto id = path.empty() ? vm->videoDeviceMonitor.getMRLForDefaultDevice() : path;
439 0 : auto& input = vm->clientVideoInputs[id];
440 0 : if (not input) {
441 0 : input = jami::getVideoInput(id);
442 : }
443 0 : return id;
444 0 : }
445 0 : return {};
446 : }
447 :
448 : bool
449 0 : closeVideoInput(const std::string& id)
450 : {
451 0 : if (auto* vm = jami::Manager::instance().getVideoManager())
452 0 : return vm->clientVideoInputs.erase(id) > 0;
453 0 : return false;
454 : }
455 : #endif
456 :
457 : void
458 0 : startAudioDevice()
459 : {
460 0 : if (auto newPreview = jami::getAudioInput(jami::RingBufferPool::DEFAULT_ID)) {
461 0 : if (auto* vm = jami::Manager::instance().getVideoManager())
462 0 : vm->audioPreview = newPreview;
463 0 : newPreview->switchInput("");
464 0 : }
465 0 : }
466 :
467 : void
468 0 : stopAudioDevice()
469 : {
470 0 : if (auto* vm = jami::Manager::instance().getVideoManager())
471 0 : vm->audioPreview.reset();
472 0 : }
473 :
474 : std::string
475 0 : startLocalMediaRecorder(const std::string& videoInputId, const std::string& filepath)
476 : {
477 0 : auto rec = std::make_unique<jami::LocalRecorder>(videoInputId);
478 0 : rec->setPath(filepath);
479 :
480 : // retrieve final path (containing file extension)
481 0 : auto path = rec->getPath();
482 :
483 0 : auto& recordManager = jami::LocalRecorderManager::instance();
484 :
485 : try {
486 0 : recordManager.insertRecorder(path, std::move(rec));
487 0 : } catch (const std::invalid_argument&) {
488 0 : return "";
489 0 : }
490 :
491 0 : auto ret = recordManager.getRecorderByPath(path)->start();
492 0 : if (!ret) {
493 0 : recordManager.removeRecorderByPath(filepath);
494 0 : return "";
495 : }
496 :
497 0 : return path;
498 0 : }
499 :
500 : void
501 0 : stopLocalRecorder(const std::string& filepath)
502 : {
503 0 : jami::LocalRecorder* rec = jami::LocalRecorderManager::instance().getRecorderByPath(filepath);
504 0 : if (!rec) {
505 0 : JAMI_WARNING("Unable to stop non existing local recorder.");
506 0 : return;
507 : }
508 :
509 0 : rec->stopRecording();
510 0 : jami::LocalRecorderManager::instance().removeRecorderByPath(filepath);
511 : }
512 :
513 : bool
514 0 : registerSinkTarget(const std::string& sinkId, SinkTarget target)
515 : {
516 : #ifdef ENABLE_VIDEO
517 0 : if (auto sink = jami::Manager::instance().getSinkClient(sinkId)) {
518 0 : sink->registerTarget(std::move(target));
519 0 : return true;
520 : } else
521 0 : JAMI_WARNING("No sink found for id '{}'", sinkId);
522 : #endif
523 0 : return false;
524 : }
525 :
526 : #ifdef ENABLE_SHM
527 : void
528 0 : startShmSink(const std::string& sinkId, bool value)
529 : {
530 : #ifdef ENABLE_VIDEO
531 0 : if (auto sink = jami::Manager::instance().getSinkClient(sinkId))
532 0 : sink->enableShm(value);
533 : else
534 0 : JAMI_WARNING("No sink found for id '{}'", sinkId);
535 : #endif
536 0 : }
537 : #endif
538 :
539 : std::map<std::string, std::string>
540 0 : getRenderer(const std::string& callId)
541 : {
542 : #ifdef ENABLE_VIDEO
543 0 : if (auto sink = jami::Manager::instance().getSinkClient(callId))
544 : return {
545 : {libjami::Media::Details::CALL_ID, callId},
546 0 : {libjami::Media::Details::SHM_PATH, sink->openedName()},
547 0 : {libjami::Media::Details::WIDTH, std::to_string(sink->getWidth())},
548 0 : {libjami::Media::Details::HEIGHT, std::to_string(sink->getHeight())},
549 0 : };
550 : else
551 : #endif
552 : return {
553 : {libjami::Media::Details::CALL_ID, callId},
554 : {libjami::Media::Details::SHM_PATH, ""},
555 : {libjami::Media::Details::WIDTH, "0"},
556 : {libjami::Media::Details::HEIGHT, "0"},
557 0 : };
558 0 : }
559 :
560 : std::string
561 0 : createMediaPlayer(const std::string& path)
562 : {
563 0 : return jami::createMediaPlayer(path);
564 : }
565 :
566 : bool
567 0 : closeMediaPlayer(const std::string& id)
568 : {
569 0 : return jami::closeMediaPlayer(id);
570 : }
571 :
572 : bool
573 0 : pausePlayer(const std::string& id, const bool& pause)
574 : {
575 0 : return jami::pausePlayer(id, pause);
576 : }
577 :
578 : bool
579 0 : mutePlayerAudio(const std::string& id, const bool& mute)
580 : {
581 0 : return jami::mutePlayerAudio(id, mute);
582 : }
583 :
584 : bool
585 0 : playerSeekToTime(const std::string& id, const int& time)
586 : {
587 0 : return jami::playerSeekToTime(id, time);
588 : }
589 :
590 : int64_t
591 0 : getPlayerPosition(const std::string& id)
592 : {
593 0 : return jami::getPlayerPosition(id);
594 : }
595 :
596 : int64_t
597 0 : getPlayerDuration(const std::string& id)
598 : {
599 0 : return jami::getPlayerDuration(id);
600 : }
601 :
602 : void
603 0 : setAutoRestart(const std::string& id, const bool& restart)
604 : {
605 0 : jami::setAutoRestart(id, restart);
606 0 : }
607 :
608 : MediaPreview
609 0 : getMediaPreview(const std::string& path)
610 : {
611 0 : return jami::getMediaPreview(path);
612 : }
613 :
614 : bool
615 0 : getDecodingAccelerated()
616 : {
617 : #ifdef ENABLE_HWACCEL
618 0 : return jami::Manager::instance().videoPreferences.getDecodingAccelerated();
619 : #else
620 : return false;
621 : #endif
622 : }
623 :
624 : void
625 0 : setDecodingAccelerated(bool state)
626 : {
627 : #ifdef ENABLE_HWACCEL
628 0 : JAMI_LOG("{} hardware acceleration", (state ? "Enabling" : "Disabling"));
629 0 : if (jami::Manager::instance().videoPreferences.setDecodingAccelerated(state))
630 0 : jami::Manager::instance().saveConfig();
631 : #endif
632 0 : }
633 :
634 : bool
635 0 : getEncodingAccelerated()
636 : {
637 : #ifdef ENABLE_HWACCEL
638 0 : return jami::Manager::instance().videoPreferences.getEncodingAccelerated();
639 : #else
640 : return false;
641 : #endif
642 : }
643 :
644 : void
645 0 : setEncodingAccelerated(bool state)
646 : {
647 : #ifdef ENABLE_HWACCEL
648 0 : JAMI_LOG("{} hardware acceleration", (state ? "Enabling" : "Disabling"));
649 0 : if (jami::Manager::instance().videoPreferences.setEncodingAccelerated(state))
650 0 : jami::Manager::instance().saveConfig();
651 : else
652 0 : return;
653 : #endif
654 0 : for (const auto& acc : jami::Manager::instance().getAllAccounts()) {
655 0 : if (state)
656 0 : acc->setCodecActive(AV_CODEC_ID_HEVC);
657 : else
658 0 : acc->setCodecInactive(AV_CODEC_ID_HEVC);
659 : // Update and sort codecs
660 0 : acc->setActiveCodecs(acc->getActiveCodecs());
661 0 : jami::Manager::instance().saveConfig(acc);
662 0 : }
663 : }
664 :
665 : #if defined(__ANDROID__) || (defined(TARGET_OS_IOS) && TARGET_OS_IOS)
666 : void
667 : addVideoDevice(const std::string& node, const std::vector<std::map<std::string, std::string>>& devInfo)
668 : {
669 : if (auto videoManager = jami::Manager::instance().getVideoManager()) {
670 : videoManager->videoDeviceMonitor.addDevice(node, devInfo);
671 : }
672 : }
673 :
674 : void
675 : removeVideoDevice(const std::string& node)
676 : {
677 : if (auto videoManager = jami::Manager::instance().getVideoManager()) {
678 : videoManager->videoDeviceMonitor.removeDevice(node);
679 : }
680 : }
681 : #endif
682 :
683 : } // namespace libjami
684 :
685 : namespace jami {
686 :
687 : #ifdef ENABLE_VIDEO
688 : // How long getVideoInput() waits for a VideoInput that is being destroyed to
689 : // release its capture device before giving up and creating a new one anyway.
690 : static constexpr auto VIDEO_INPUT_RELEASE_TIMEOUT = std::chrono::seconds(10);
691 :
692 : video::VideoDeviceMonitor*
693 3580 : getVideoDeviceMonitor()
694 : {
695 3580 : if (auto* vm = jami::Manager::instance().getVideoManager())
696 3574 : return &vm->videoDeviceMonitor;
697 6 : return {};
698 : }
699 :
700 : std::shared_ptr<video::VideoInput>
701 173 : getVideoInput(const std::string& resource, video::VideoInputMode inputMode, const std::string& sink)
702 : {
703 173 : auto sinkId = sink.empty() ? resource : sink;
704 173 : auto* vmgr = Manager::instance().getVideoManager();
705 173 : if (!vmgr)
706 0 : return {};
707 173 : std::unique_lock<std::mutex> lk(vmgr->videoMutex);
708 :
709 : // A registered entry whose weak pointer has expired means that a VideoInput
710 : // for this sink is still being destroyed: its capture thread may be stuck in
711 : // a blocking driver call and it therefore still holds the device open.
712 : // Creating a new one right away would open the capture device a second time,
713 : // which wedges some webcams badly enough that they drop off the USB bus.
714 : // Wait for the teardown to complete instead. The deadline is computed once so
715 : // that notifications for other sinks, which share this condition variable,
716 : // don't restart the timeout.
717 173 : std::shared_ptr<video::VideoInput> current;
718 173 : const auto deadline = std::chrono::steady_clock::now() + VIDEO_INPUT_RELEASE_TIMEOUT;
719 173 : const auto released = vmgr->videoInputsCv.wait_until(lk, deadline, [&] {
720 173 : current.reset();
721 173 : auto it = vmgr->videoInputs.find(sinkId);
722 173 : if (it == vmgr->videoInputs.end())
723 75 : return true;
724 98 : current = it->second.input.lock();
725 98 : return current != nullptr;
726 : });
727 173 : if (current)
728 98 : return current;
729 75 : if (!released)
730 0 : JAMI_WARNING("Timed out waiting for the previous input on {} to be released", sinkId);
731 :
732 : // Unregister the input only once it is really destroyed, so that a concurrent
733 : // getVideoInput() waits for the capture device to be released. The generation
734 : // guards against a deleter that ran late erasing a newer entry: matching it
735 : // proves the registered entry is still the one this instance created.
736 75 : const auto generation = ++vmgr->nextVideoInputGeneration;
737 75 : auto unregister = [sinkId, generation](video::VideoInput* input) {
738 75 : delete input;
739 75 : auto* vmgr = Manager::instance().getVideoManager();
740 75 : if (!vmgr)
741 0 : return;
742 : {
743 75 : std::lock_guard<std::mutex> lk(vmgr->videoMutex);
744 75 : auto it = vmgr->videoInputs.find(sinkId);
745 75 : if (it != vmgr->videoInputs.end() && it->second.generation == generation)
746 75 : vmgr->videoInputs.erase(it);
747 75 : }
748 75 : vmgr->videoInputsCv.notify_all();
749 75 : };
750 75 : auto* raw = new video::VideoInput(inputMode, resource, sinkId);
751 75 : auto input = std::shared_ptr<video::VideoInput>(raw, std::move(unregister));
752 75 : vmgr->videoInputs[sinkId] = {input, generation};
753 75 : return input;
754 248 : }
755 :
756 : void
757 0 : VideoManager::setDeviceOrientation(const std::string& deviceId, int angle)
758 : {
759 0 : videoDeviceMonitor.setDeviceOrientation(deviceId, angle);
760 0 : }
761 : #endif
762 :
763 : std::shared_ptr<AudioInput>
764 348 : getAudioInput(const std::string& device)
765 : {
766 348 : auto* vmgr = Manager::instance().getVideoManager();
767 348 : if (!vmgr)
768 0 : return {};
769 348 : std::lock_guard<std::mutex> lk(vmgr->audioMutex);
770 :
771 : // erase expired audio inputs
772 1456 : for (auto it = vmgr->audioInputs.cbegin(); it != vmgr->audioInputs.cend();) {
773 1108 : if (it->second.expired())
774 272 : it = vmgr->audioInputs.erase(it);
775 : else
776 836 : ++it;
777 : }
778 :
779 348 : auto it = vmgr->audioInputs.find(device);
780 348 : if (it != vmgr->audioInputs.end()) {
781 45 : if (auto input = it->second.lock()) {
782 45 : return input;
783 45 : }
784 : }
785 :
786 303 : auto input = std::make_shared<AudioInput>(device);
787 303 : vmgr->audioInputs[device] = input;
788 303 : return input;
789 348 : }
790 :
791 : bool
792 0 : VideoManager::hasRunningPlayers()
793 : {
794 0 : if (auto* vmgr = Manager::instance().getVideoManager()) {
795 0 : std::scoped_lock<std::mutex> lk(vmgr->mediaPlayersMutex);
796 0 : return !vmgr->mediaPlayers.empty();
797 0 : }
798 0 : return false;
799 : }
800 :
801 : std::shared_ptr<MediaPlayer>
802 11 : getMediaPlayer(const std::string& id)
803 : {
804 11 : if (auto* vmgr = Manager::instance().getVideoManager()) {
805 11 : std::scoped_lock<std::mutex> lk(vmgr->mediaPlayersMutex);
806 11 : auto it = vmgr->mediaPlayers.find(id);
807 11 : if (it != vmgr->mediaPlayers.end()) {
808 11 : return it->second;
809 : }
810 11 : }
811 0 : return {};
812 : }
813 :
814 : std::string
815 14 : createMediaPlayer(const std::string& path)
816 : {
817 14 : if (auto* vmgr = Manager::instance().getVideoManager()) {
818 14 : std::scoped_lock<std::mutex> lk(vmgr->mediaPlayersMutex);
819 14 : auto& player = vmgr->mediaPlayers[path];
820 14 : if (!player) {
821 11 : player = std::make_shared<MediaPlayer>(path);
822 : }
823 14 : return path;
824 14 : }
825 0 : return {};
826 : }
827 :
828 : bool
829 0 : pausePlayer(const std::string& id, bool pause)
830 : {
831 0 : if (auto player = getMediaPlayer(id)) {
832 0 : player->pause(pause);
833 0 : return true;
834 0 : }
835 0 : return false;
836 : }
837 :
838 : bool
839 601 : closeMediaPlayer(const std::string& id)
840 : {
841 601 : if (auto* vm = Manager::instance().getVideoManager()) {
842 601 : std::scoped_lock<std::mutex> lk(vm->mediaPlayersMutex);
843 601 : return vm->mediaPlayers.erase(id) > 0;
844 601 : }
845 0 : return false;
846 : }
847 :
848 : bool
849 0 : mutePlayerAudio(const std::string& id, bool mute)
850 : {
851 0 : if (auto player = getMediaPlayer(id)) {
852 0 : player->muteAudio(mute);
853 0 : return true;
854 0 : }
855 0 : return false;
856 : }
857 :
858 : bool
859 0 : playerSeekToTime(const std::string& id, int time)
860 : {
861 0 : if (auto player = getMediaPlayer(id))
862 0 : return player->seekToTime(time);
863 0 : return false;
864 : }
865 :
866 : int64_t
867 0 : getPlayerPosition(const std::string& id)
868 : {
869 0 : if (auto player = getMediaPlayer(id))
870 0 : return player->getPlayerPosition();
871 0 : return -1;
872 : }
873 :
874 : int64_t
875 0 : getPlayerDuration(const std::string& id)
876 : {
877 0 : if (auto player = getMediaPlayer(id))
878 0 : return player->getPlayerDuration();
879 0 : return -1;
880 : }
881 :
882 : void
883 0 : setAutoRestart(const std::string& id, bool restart)
884 : {
885 0 : if (auto player = getMediaPlayer(id))
886 0 : player->setAutoRestart(restart);
887 0 : }
888 :
889 : } // namespace jami
|