LCOV - code coverage report
Current view: top level - src/media/video - video_mixer.cpp (source / functions) Hit Total Coverage
Test: jami-coverage-filtered.info Lines: 232 331 70.1 %
Date: 2024-11-15 09:04:49 Functions: 26 31 83.9 %

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

Generated by: LCOV version 1.14