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-04-18 08:01:59 Functions: 26 31 83.9 %

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

Generated by: LCOV version 1.14