LCOV - code coverage report
Current view: top level - src/media/video - video_mixer.cpp (source / functions) Coverage Total Hit
Test: jami-coverage-filtered.info Lines: 70.7 % 328 232
Test Date: 2026-07-06 08:25:38 Functions: 71.7 % 53 38

            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 "libav_deps.h" // MUST BE INCLUDED FIRST
      19              : 
      20              : #include "video_mixer.h"
      21              : #include "media_buffer.h"
      22              : #include "client/videomanager.h"
      23              : #include "manager.h"
      24              : #include "media_filter.h"
      25              : #include "sinkclient.h"
      26              : #include "logger.h"
      27              : #include "filter_transpose.h"
      28              : #ifdef ENABLE_HWACCEL
      29              : #include "accel.h"
      30              : #endif
      31              : #include "connectivity/sip_utils.h"
      32              : 
      33              : #include <cmath>
      34              : #include <unistd.h>
      35              : #include <mutex>
      36              : 
      37              : #include <opendht/thread_pool.h>
      38              : 
      39              : static constexpr auto MIN_LINE_ZOOM = 6; // Used by the ONE_BIG_WITH_SMALL layout for the small previews
      40              : 
      41              : namespace jami {
      42              : namespace video {
      43              : 
      44              : struct VideoMixer::VideoMixerSource
      45              : {
      46              :     Observable<std::shared_ptr<MediaFrame>>* source {nullptr};
      47              :     int rotation {0};
      48              :     std::unique_ptr<MediaFilter> rotationFilter {nullptr};
      49              :     std::shared_ptr<VideoFrame> render_frame;
      50            0 :     void atomic_copy(const VideoFrame& other)
      51              :     {
      52            0 :         std::lock_guard lock(mutex_);
      53            0 :         auto newFrame = std::make_shared<VideoFrame>();
      54            0 :         newFrame->copyFrom(other);
      55            0 :         render_frame = newFrame;
      56            0 :     }
      57              : 
      58         8830 :     std::shared_ptr<VideoFrame> getRenderFrame()
      59              :     {
      60         8830 :         std::lock_guard lock(mutex_);
      61        17660 :         return render_frame;
      62         8830 :     }
      63              : 
      64              :     // Current render information
      65              :     int x {};
      66              :     int y {};
      67              :     int w {};
      68              :     int h {};
      69              :     bool hasVideo {true};
      70              : 
      71              : private:
      72              :     std::mutex mutex_;
      73              : };
      74              : 
      75              : static constexpr const auto MIXER_FRAMERATE = 30;
      76              : static constexpr const auto FRAME_DURATION = std::chrono::duration<double>(1. / MIXER_FRAMERATE);
      77              : 
      78           38 : VideoMixer::VideoMixer(const std::string& id, const std::string& localInput, bool attachHost)
      79              :     : VideoGenerator::VideoGenerator()
      80           38 :     , id_(id)
      81           38 :     , sink_(Manager::instance().createSinkClient(id, true))
      82          151 :     , loop_([] { return true; }, std::bind(&VideoMixer::process, this), [] {})
      83              : {
      84              :     // Participant frames are mostly downscaled into their cell: area averaging
      85              :     // gives a much cleaner result than the default fast bilinear.
      86           38 :     scaler_.setScalingAlgorithm(SWS_AREA);
      87              :     // Local video camera is the main participant
      88           38 :     if (not localInput.empty() && attachHost) {
      89            0 :         auto videoInput = getVideoInput(localInput);
      90            0 :         localInputs_.emplace_back(videoInput);
      91            0 :         attachVideo(videoInput.get(), "", sip_utils::streamId("", sip_utils::DEFAULT_VIDEO_STREAMID));
      92            0 :     }
      93           38 :     loop_.start();
      94           38 :     nextProcess_ = std::chrono::steady_clock::now();
      95              : 
      96          152 :     JAMI_LOG("[mixer:{}] New instance created", id_);
      97           38 : }
      98              : 
      99           76 : VideoMixer::~VideoMixer()
     100              : {
     101           38 :     stopSink();
     102           38 :     stopInputs();
     103              : 
     104           38 :     loop_.join();
     105              : 
     106          152 :     JAMI_LOG("[mixer:{}] Instance destroyed", id_);
     107           38 : }
     108              : 
     109              : void
     110           29 : VideoMixer::switchInputs(const std::vector<std::string>& inputs)
     111              : {
     112              :     // Do not stop video inputs that are already there
     113              :     // But only detach it to get new index
     114           29 :     std::lock_guard lk(localInputsMtx_);
     115           29 :     decltype(localInputs_) newInputs;
     116           29 :     newInputs.reserve(inputs.size());
     117           59 :     for (const auto& input : inputs) {
     118           30 :         auto videoInput = getVideoInput(input);
     119              :         // Note, video can be a previously stopped device (eg. restart a screen sharing)
     120              :         // in this case, the videoInput will be found and must be restarted
     121           30 :         videoInput->restart();
     122           30 :         auto it = std::find(localInputs_.cbegin(), localInputs_.cend(), videoInput);
     123           30 :         auto onlyDetach = it != localInputs_.cend();
     124           30 :         if (onlyDetach) {
     125            1 :             videoInput->detach(this);
     126            1 :             localInputs_.erase(it);
     127              :         }
     128           30 :         newInputs.emplace_back(std::move(videoInput));
     129           30 :     }
     130              :     // Stop other video inputs
     131           29 :     stopInputs();
     132           29 :     localInputs_ = std::move(newInputs);
     133              : 
     134              :     // Re-attach videoInput to mixer
     135           59 :     for (size_t i = 0; i < localInputs_.size(); ++i) {
     136           30 :         auto& input = localInputs_[i];
     137          180 :         attachVideo(input.get(), "", sip_utils::streamId("", fmt::format("video_{}", i)));
     138              :     }
     139           29 : }
     140              : 
     141              : void
     142           29 : VideoMixer::stopInput(const std::shared_ptr<VideoFrameActiveWriter>& input)
     143              : {
     144              :     // Detach videoInputs from mixer
     145           29 :     input->detach(this);
     146           29 : }
     147              : 
     148              : void
     149           93 : VideoMixer::stopInputs()
     150              : {
     151          122 :     for (auto& input : localInputs_)
     152           29 :         stopInput(input);
     153           93 :     localInputs_.clear();
     154           93 : }
     155              : 
     156              : void
     157            1 : VideoMixer::setActiveStream(const std::string& id)
     158              : {
     159            1 :     activeStream_ = id;
     160            1 :     updateLayout();
     161            1 : }
     162              : 
     163              : void
     164          395 : VideoMixer::updateLayout()
     165              : {
     166          395 :     if (activeStream_ == "")
     167          394 :         currentLayout_ = Layout::GRID;
     168          395 :     layoutUpdated_ += 1;
     169          395 : }
     170              : 
     171              : void
     172           80 : VideoMixer::attachVideo(Observable<std::shared_ptr<MediaFrame>>* frame,
     173              :                         const std::string& callId,
     174              :                         const std::string& streamId)
     175              : {
     176           80 :     if (!frame)
     177            0 :         return;
     178          320 :     JAMI_LOG("Attaching video with streamId {}", streamId);
     179              :     {
     180           80 :         std::lock_guard lk(videoToStreamInfoMtx_);
     181           80 :         videoToStreamInfo_[frame] = StreamInfo {callId, streamId};
     182           80 :     }
     183           80 :     frame->attach(this);
     184              : }
     185              : 
     186              : void
     187           49 : VideoMixer::detachVideo(Observable<std::shared_ptr<MediaFrame>>* frame)
     188              : {
     189           49 :     if (!frame)
     190            0 :         return;
     191           49 :     bool detach = false;
     192           49 :     std::unique_lock lk(videoToStreamInfoMtx_);
     193           49 :     auto it = videoToStreamInfo_.find(frame);
     194           49 :     if (it != videoToStreamInfo_.end()) {
     195          196 :         JAMI_LOG("Detaching video of call {}", it->second.callId);
     196           49 :         detach = true;
     197              :         // Handle the case where the current shown source leave the conference
     198              :         // Note, do not call resetActiveStream() to avoid multiple updates
     199           49 :         if (verifyActive(it->second.streamId))
     200            0 :             activeStream_ = {};
     201           49 :         videoToStreamInfo_.erase(it);
     202              :     }
     203           49 :     lk.unlock();
     204           49 :     if (detach)
     205           49 :         frame->detach(this);
     206           49 : }
     207              : 
     208              : void
     209           80 : VideoMixer::attached(Observable<std::shared_ptr<MediaFrame>>* ob)
     210              : {
     211           80 :     std::unique_lock lock(rwMutex_);
     212              : 
     213           80 :     auto src = std::unique_ptr<VideoMixerSource>(new VideoMixerSource);
     214           80 :     src->render_frame = std::make_shared<VideoFrame>();
     215           80 :     src->source = ob;
     216          320 :     JAMI_LOG("Add new source [{}]", fmt::ptr(src.get()));
     217           80 :     sources_.emplace_back(std::move(src));
     218          320 :     JAMI_DEBUG("Total sources: {:d}", sources_.size());
     219           80 :     updateLayout();
     220           80 : }
     221              : 
     222              : void
     223           80 : VideoMixer::detached(Observable<std::shared_ptr<MediaFrame>>* ob)
     224              : {
     225           80 :     std::unique_lock lock(rwMutex_);
     226              : 
     227          124 :     for (const auto& x : sources_) {
     228          124 :         if (x->source == ob) {
     229          320 :             JAMI_LOG("Remove source [{}]", fmt::ptr(x.get()));
     230           80 :             sources_.remove(x);
     231          320 :             JAMI_DEBUG("Total sources: {:d}", sources_.size());
     232           80 :             updateLayout();
     233           80 :             break;
     234              :         }
     235              :     }
     236           80 : }
     237              : 
     238              : void
     239            0 : VideoMixer::update(Observable<std::shared_ptr<MediaFrame>>* ob, const std::shared_ptr<MediaFrame>& frame_p)
     240              : {
     241            0 :     std::shared_lock lock(rwMutex_);
     242              : 
     243            0 :     for (const auto& x : sources_) {
     244            0 :         if (x->source == ob) {
     245              : #ifdef ENABLE_HWACCEL
     246            0 :             std::shared_ptr<VideoFrame> frame;
     247              :             try {
     248            0 :                 frame = HardwareAccel::transferToMainMemory(*std::static_pointer_cast<VideoFrame>(frame_p),
     249            0 :                                                             AV_PIX_FMT_NV12);
     250            0 :                 x->atomic_copy(*std::static_pointer_cast<VideoFrame>(frame));
     251            0 :             } catch (const std::runtime_error& e) {
     252            0 :                 JAMI_ERROR("[mixer:{}] Accel failure: {}", id_, e.what());
     253            0 :                 return;
     254            0 :             }
     255              : #else
     256              :             x->atomic_copy(*std::static_pointer_cast<VideoFrame>(frame_p));
     257              : #endif
     258            0 :             return;
     259            0 :         }
     260              :     }
     261            0 : }
     262              : 
     263              : void
     264         3755 : VideoMixer::process()
     265              : {
     266         3755 :     nextProcess_ += std::chrono::duration_cast<std::chrono::microseconds>(FRAME_DURATION);
     267         3755 :     const auto delay = nextProcess_ - std::chrono::steady_clock::now();
     268         3755 :     if (delay.count() > 0)
     269         3755 :         std::this_thread::sleep_for(delay);
     270              : 
     271              :     // Nothing to do.
     272         3755 :     if (width_ == 0 or height_ == 0) {
     273            0 :         return;
     274              :     }
     275              : 
     276         3755 :     VideoFrame& output = getNewFrame();
     277              :     try {
     278         3755 :         output.reserve(format_, width_, height_);
     279            0 :     } catch (const std::bad_alloc& e) {
     280            0 :         JAMI_ERROR("[mixer:{}] VideoFrame::allocBuffer() failed", id_);
     281            0 :         return;
     282            0 :     }
     283              : 
     284         3755 :     libav_utils::fillWithBlack(output.pointer());
     285              : 
     286              :     {
     287         3755 :         std::lock_guard lk(audioOnlySourcesMtx_);
     288         3755 :         std::shared_lock lock(rwMutex_);
     289              : 
     290         3755 :         int i = 0;
     291         3755 :         bool activeFound = false;
     292         3755 :         bool needsUpdate = layoutUpdated_ > 0;
     293         3755 :         bool successfullyRendered = audioOnlySources_.size() != 0 && sources_.size() == 0;
     294         3755 :         std::vector<SourceInfo> sourcesInfo;
     295         3755 :         sourcesInfo.reserve(sources_.size() + audioOnlySources_.size());
     296              :         // add all audioonlysources
     297         4685 :         for (auto& [callId, streamId] : audioOnlySources_) {
     298          930 :             auto active = verifyActive(streamId);
     299          930 :             if (currentLayout_ != Layout::ONE_BIG or active) {
     300          930 :                 sourcesInfo.emplace_back(SourceInfo {{}, 0, 0, 10, 10, false, callId, streamId});
     301              :             }
     302          930 :             if (currentLayout_ == Layout::ONE_BIG) {
     303            0 :                 if (active)
     304            0 :                     successfullyRendered = true;
     305              :                 else
     306            0 :                     sourcesInfo.emplace_back(SourceInfo {{}, 0, 0, 0, 0, false, callId, streamId});
     307              :                 // Add all participants info even in ONE_BIG layout.
     308              :                 // The width and height set to 0 here will led the peer to filter them out.
     309              :             }
     310              :         }
     311              :         // add video sources
     312        12585 :         for (auto& x : sources_) {
     313              :             /* thread stop pending? */
     314         8830 :             if (!loop_.isRunning())
     315            0 :                 return;
     316              : 
     317         8830 :             auto sinfo = streamInfo(x->source);
     318         8830 :             auto activeSource = verifyActive(sinfo.streamId);
     319         8830 :             if (currentLayout_ != Layout::ONE_BIG or activeSource) {
     320              :                 // make rendered frame temporarily unavailable for update()
     321              :                 // to avoid concurrent access.
     322         8830 :                 std::shared_ptr<VideoFrame> input = x->getRenderFrame();
     323         8830 :                 std::shared_ptr<VideoFrame> fooInput = std::make_shared<VideoFrame>();
     324              : 
     325         8830 :                 auto wantedIndex = i;
     326         8830 :                 if (currentLayout_ == Layout::ONE_BIG) {
     327            0 :                     wantedIndex = 0;
     328            0 :                     activeFound = true;
     329         8830 :                 } else if (currentLayout_ == Layout::ONE_BIG_WITH_SMALL) {
     330            0 :                     if (activeSource) {
     331            0 :                         wantedIndex = 0;
     332            0 :                         activeFound = true;
     333            0 :                     } else if (not activeFound) {
     334            0 :                         wantedIndex += 1;
     335              :                     }
     336              :                 }
     337              : 
     338         8830 :                 auto hasVideo = x->hasVideo;
     339         8830 :                 bool blackFrame = false;
     340              : 
     341         8830 :                 if (!input->height() or !input->width()) {
     342         8830 :                     successfullyRendered = true;
     343         8830 :                     fooInput->reserve(format_, width_, height_);
     344         8830 :                     blackFrame = true;
     345              :                 } else {
     346            0 :                     fooInput.swap(input);
     347              :                 }
     348              : 
     349              :                 // If orientation changed or if the first valid frame for source
     350              :                 // is received -> trigger layout calculation and confInfo update
     351         8830 :                 if (x->rotation != fooInput->getOrientation() or !x->w or !x->h) {
     352           78 :                     updateLayout();
     353           78 :                     needsUpdate = true;
     354              :                 }
     355              : 
     356         8830 :                 if (needsUpdate)
     357          630 :                     calc_position(x, fooInput, wantedIndex);
     358              : 
     359         8830 :                 if (!blackFrame) {
     360            0 :                     if (fooInput)
     361            0 :                         successfullyRendered |= render_frame(output, fooInput, x);
     362              :                     else
     363            0 :                         JAMI_WARNING("[mixer:{}] Nothing to render for {}", id_, fmt::ptr(x->source));
     364              :                 }
     365              : 
     366         8830 :                 x->hasVideo = !blackFrame && successfullyRendered;
     367         8830 :                 if (hasVideo != x->hasVideo) {
     368           78 :                     updateLayout();
     369           78 :                     needsUpdate = true;
     370              :                 }
     371         8830 :             } else if (needsUpdate) {
     372            0 :                 x->x = 0;
     373            0 :                 x->y = 0;
     374            0 :                 x->w = 0;
     375            0 :                 x->h = 0;
     376            0 :                 x->hasVideo = false;
     377              :             }
     378              : 
     379         8830 :             ++i;
     380         8830 :         }
     381         3755 :         if (needsUpdate and successfullyRendered) {
     382          308 :             layoutUpdated_ -= 1;
     383          308 :             if (layoutUpdated_ == 0) {
     384          233 :                 for (auto& x : sources_) {
     385          158 :                     auto sinfo = streamInfo(x->source);
     386          158 :                     sourcesInfo.emplace_back(
     387          158 :                         SourceInfo {x->source, x->x, x->y, x->w, x->h, x->hasVideo, sinfo.callId, sinfo.streamId});
     388          158 :                 }
     389           75 :                 if (onSourcesUpdated_)
     390           75 :                     onSourcesUpdated_(std::move(sourcesInfo));
     391              :             }
     392              :         }
     393         3757 :     }
     394              : 
     395         3754 :     output.pointer()->pts = av_rescale_q_rnd(av_gettime() - startTime_,
     396              :                                              {1, AV_TIME_BASE},
     397              :                                              {1, MIXER_FRAMERATE},
     398              :                                              static_cast<AVRounding>(AV_ROUND_NEAR_INF | AV_ROUND_PASS_MINMAX));
     399         3754 :     lastTimestamp_ = output.pointer()->pts;
     400         3754 :     publishFrame();
     401              : }
     402              : 
     403              : bool
     404            0 : VideoMixer::render_frame(VideoFrame& output,
     405              :                          const std::shared_ptr<VideoFrame>& input,
     406              :                          std::unique_ptr<VideoMixerSource>& source)
     407              : {
     408            0 :     if (!width_ or !height_ or !input->pointer() or input->pointer()->format == -1)
     409            0 :         return false;
     410              : 
     411            0 :     int cell_width = source->w;
     412            0 :     int cell_height = source->h;
     413            0 :     int xoff = source->x;
     414            0 :     int yoff = source->y;
     415              : 
     416            0 :     int angle = input->getOrientation();
     417            0 :     const constexpr char filterIn[] = "mixin";
     418            0 :     if (angle != source->rotation) {
     419            0 :         source->rotationFilter
     420            0 :             = video::getTransposeFilter(angle, filterIn, input->width(), input->height(), input->format(), false);
     421            0 :         source->rotation = angle;
     422              :     }
     423            0 :     std::shared_ptr<VideoFrame> frame;
     424            0 :     if (source->rotationFilter) {
     425            0 :         source->rotationFilter->feedInput(input->pointer(), filterIn);
     426            0 :         frame = std::static_pointer_cast<VideoFrame>(std::shared_ptr<MediaFrame>(source->rotationFilter->readOutput()));
     427              :     } else {
     428            0 :         frame = input;
     429              :     }
     430              : 
     431            0 :     scaler_.scale_and_pad(*frame, output, xoff, yoff, cell_width, cell_height, true);
     432            0 :     return true;
     433            0 : }
     434              : 
     435              : void
     436          630 : VideoMixer::calc_position(std::unique_ptr<VideoMixerSource>& source, const std::shared_ptr<VideoFrame>& input, int index)
     437              : {
     438          630 :     if (!width_ or !height_)
     439            0 :         return;
     440              : 
     441              :     // Compute cell size/position
     442              :     int cell_width, cell_height, cellW_off, cellH_off;
     443          630 :     const int n = currentLayout_ == Layout::ONE_BIG ? 1 : static_cast<int>(sources_.size());
     444          630 :     const int zoom = currentLayout_ == Layout::ONE_BIG_WITH_SMALL ? std::max(MIN_LINE_ZOOM, n)
     445          630 :                                                                   : static_cast<int>(ceil(sqrt(n)));
     446          630 :     if (currentLayout_ == Layout::ONE_BIG_WITH_SMALL && index == 0) {
     447              :         // In ONE_BIG_WITH_SMALL, the first line at the top is the previews
     448              :         // The rest is the active source
     449            0 :         cell_width = width_;
     450            0 :         cell_height = height_ - height_ / zoom;
     451              :     } else {
     452          630 :         cell_width = width_ / zoom;
     453          630 :         cell_height = height_ / zoom;
     454              : 
     455          630 :         if (n == 1) {
     456              :             // On some platforms (at least macOS/android) - Having one frame at the same
     457              :             // size of the mixer cause it to be grey.
     458              :             // Removing some pixels solve this. We use 16 because it's a multiple of 8
     459              :             // (value that we prefer for video management)
     460           56 :             cell_width -= 16;
     461           56 :             cell_height -= 16;
     462              :         }
     463              :     }
     464          630 :     if (currentLayout_ == Layout::ONE_BIG_WITH_SMALL) {
     465            0 :         if (index == 0) {
     466            0 :             cellW_off = 0;
     467            0 :             cellH_off = height_ / zoom; // First line height
     468              :         } else {
     469            0 :             cellW_off = (index - 1) * cell_width;
     470              :             // Show sources in center
     471            0 :             cellW_off += (width_ - (n - 1) * cell_width) / 2;
     472            0 :             cellH_off = 0;
     473              :         }
     474              :     } else {
     475          630 :         cellW_off = (index % zoom) * cell_width;
     476          630 :         if (currentLayout_ == Layout::GRID && n % zoom != 0 && index >= (zoom * ((n - 1) / zoom))) {
     477              :             // Last line, center participants if not full
     478           68 :             cellW_off += (width_ - (n % zoom) * cell_width) / 2;
     479              :         }
     480          630 :         cellH_off = (index / zoom) * cell_height;
     481          630 :         if (n == 1) {
     482              :             // Centerize (cellwidth = width_ - 16)
     483           56 :             cellW_off += 8;
     484           56 :             cellH_off += 8;
     485              :         }
     486              :     }
     487              : 
     488              :     // Compute frame size/position
     489              :     int frameW, frameH, frameW_off, frameH_off;
     490              :     float zoomW, zoomH, denom;
     491              : 
     492          630 :     float inputW = static_cast<float>(input->width());
     493          630 :     float inputH = static_cast<float>(input->height());
     494              : 
     495          630 :     if (input->getOrientation() % 180) {
     496              :         // Rotated frame
     497            0 :         zoomW = inputH / static_cast<float>(cell_width);
     498            0 :         zoomH = inputW / static_cast<float>(cell_height);
     499            0 :         denom = std::max(zoomW, zoomH);
     500            0 :         frameH = static_cast<int>(std::lround(inputW / denom));
     501            0 :         frameW = static_cast<int>(std::lround(inputH / denom));
     502              :     } else {
     503          630 :         zoomW = inputW / static_cast<float>(cell_width);
     504          630 :         zoomH = inputH / static_cast<float>(cell_height);
     505          630 :         denom = std::max(zoomW, zoomH);
     506          630 :         frameW = static_cast<int>(std::lround(inputW / denom));
     507          630 :         frameH = static_cast<int>(std::lround(inputH / denom));
     508              :     }
     509              : 
     510              :     // Center the frame in the cell
     511          630 :     frameW_off = cellW_off + (cell_width - frameW) / 2;
     512          630 :     frameH_off = cellH_off + (cell_height - frameH) / 2;
     513              : 
     514              :     // Update source's cache
     515          630 :     source->w = frameW;
     516          630 :     source->h = frameH;
     517          630 :     source->x = frameW_off;
     518          630 :     source->y = frameH_off;
     519              : }
     520              : 
     521              : void
     522           38 : VideoMixer::setParameters(int width, int height, AVPixelFormat format)
     523              : {
     524           38 :     std::unique_lock lock(rwMutex_);
     525              : 
     526           38 :     width_ = width;
     527           38 :     height_ = height;
     528           38 :     format_ = format;
     529              : 
     530              :     // cleanup the previous frame to have a nice copy in rendering method
     531           38 :     std::shared_ptr<VideoFrame> previous_p(obtainLastFrame());
     532           38 :     if (previous_p)
     533            0 :         libav_utils::fillWithBlack(previous_p->pointer());
     534              : 
     535           38 :     startSink();
     536           38 :     updateLayout();
     537           38 :     startTime_ = av_gettime();
     538           38 : }
     539              : 
     540              : void
     541           38 : VideoMixer::startSink()
     542              : {
     543           38 :     stopSink();
     544              : 
     545           38 :     if (width_ == 0 or height_ == 0) {
     546            0 :         JAMI_WARNING("[mixer:{}] MX: unable to start with zero-sized output", id_);
     547            0 :         return;
     548              :     }
     549              : 
     550           38 :     if (not sink_->start()) {
     551            0 :         JAMI_ERROR("[mixer:{}] MX: sink startup failed", id_);
     552            0 :         return;
     553              :     }
     554              : 
     555           38 :     if (this->attach(sink_.get()))
     556           38 :         sink_->setFrameSize(width_, height_);
     557              : }
     558              : 
     559              : void
     560           76 : VideoMixer::stopSink()
     561              : {
     562           76 :     this->detach(sink_.get());
     563           76 :     sink_->stop();
     564           76 : }
     565              : 
     566              : int
     567          107 : VideoMixer::getWidth() const
     568              : {
     569          107 :     return width_;
     570              : }
     571              : 
     572              : int
     573          107 : VideoMixer::getHeight() const
     574              : {
     575          107 :     return height_;
     576              : }
     577              : 
     578              : AVPixelFormat
     579            0 : VideoMixer::getPixelFormat() const
     580              : {
     581            0 :     return format_;
     582              : }
     583              : 
     584              : MediaStream
     585           50 : VideoMixer::getStream(const std::string& name) const
     586              : {
     587           50 :     MediaStream ms;
     588           50 :     ms.name = name;
     589           50 :     ms.format = format_;
     590           50 :     ms.isVideo = true;
     591           50 :     ms.height = height_;
     592           50 :     ms.width = width_;
     593           50 :     ms.frameRate = {MIXER_FRAMERATE, 1};
     594           50 :     ms.timeBase = {1, MIXER_FRAMERATE};
     595           50 :     ms.firstTimestamp = lastTimestamp_;
     596              : 
     597           50 :     return ms;
     598            0 : }
     599              : 
     600              : } // namespace video
     601              : } // namespace jami
        

Generated by: LCOV version 2.0-1