LCOV - code coverage report
Current view: top level - foo/src/media/video - video_mixer.cpp (source / functions) Hit Total Coverage
Test: jami-coverage-filtered.info Lines: 227 322 70.5 %
Date: 2025-12-18 10:07:43 Functions: 26 31 83.9 %

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

Generated by: LCOV version 1.14