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 : #pragma once 18 : 19 : #include "noncopyable.h" 20 : #include "video_base.h" 21 : #include "video_scaler.h" 22 : #include "threadloop.h" 23 : #include "media_stream.h" 24 : 25 : #include <list> 26 : #include <chrono> 27 : #include <memory> 28 : #include <shared_mutex> 29 : 30 : namespace jami { 31 : namespace video { 32 : 33 : class SinkClient; 34 : 35 : struct StreamInfo 36 : { 37 : std::string callId; 38 : std::string streamId; 39 : }; 40 : 41 : struct SourceInfo 42 : { 43 : Observable<std::shared_ptr<MediaFrame>>* source; 44 : int x; 45 : int y; 46 : int w; 47 : int h; 48 : bool hasVideo; 49 : std::string callId; 50 : std::string streamId; 51 : }; 52 : using OnSourcesUpdatedCb = std::function<void(std::vector<SourceInfo>&&)>; 53 : 54 : enum class Layout { GRID, ONE_BIG_WITH_SMALL, ONE_BIG }; 55 : 56 : class VideoMixer : public VideoGenerator, public VideoFramePassiveReader 57 : { 58 : public: 59 : VideoMixer(const std::string& id, const std::string& localInput = {}, bool attachHost = false); 60 : ~VideoMixer(); 61 : 62 : void setParameters(int width, int height, AVPixelFormat format = AV_PIX_FMT_YUV422P); 63 : 64 : int getWidth() const override; 65 : int getHeight() const override; 66 : AVPixelFormat getPixelFormat() const override; 67 : 68 : // as VideoFramePassiveReader 69 : void update(Observable<std::shared_ptr<MediaFrame>>* ob, const std::shared_ptr<MediaFrame>& v) override; 70 : void attached(Observable<std::shared_ptr<MediaFrame>>* ob) override; 71 : void detached(Observable<std::shared_ptr<MediaFrame>>* ob) override; 72 : 73 : /** 74 : * Set all inputs at once 75 : * @param inputs New inputs 76 : * @note previous inputs will be stopped 77 : */ 78 : void switchInputs(const std::vector<std::string>& inputs); 79 : /** 80 : * Stop all inputs 81 : */ 82 : void stopInputs(); 83 : 84 : void setActiveStream(const std::string& id); 85 1 : void resetActiveStream() 86 : { 87 1 : activeStream_ = {}; 88 1 : updateLayout(); 89 1 : } 90 : 91 10250 : bool verifyActive(const std::string& id) { return activeStream_ == id; } 92 : 93 0 : void setVideoLayout(Layout newLayout) 94 : { 95 0 : currentLayout_ = newLayout; 96 0 : if (currentLayout_ == Layout::GRID) 97 0 : resetActiveStream(); 98 0 : layoutUpdated_ += 1; 99 0 : } 100 : 101 : Layout getVideoLayout() const { return currentLayout_; } 102 : 103 37 : void setOnSourcesUpdated(OnSourcesUpdatedCb&& cb) { onSourcesUpdated_ = std::move(cb); } 104 : 105 : MediaStream getStream(const std::string& name) const; 106 : 107 68 : std::shared_ptr<VideoFrameActiveWriter> getVideoLocal() const 108 : { 109 68 : if (!localInputs_.empty()) 110 51 : return *localInputs_.begin(); 111 17 : return {}; 112 : } 113 : 114 : void updateLayout(); 115 : 116 186 : std::shared_ptr<SinkClient>& getSink() { return sink_; } 117 : 118 23 : void addAudioOnlySource(const std::string& callId, const std::string& streamId) 119 : { 120 23 : std::unique_lock lk(audioOnlySourcesMtx_); 121 23 : audioOnlySources_.insert({callId, streamId}); 122 23 : lk.unlock(); 123 23 : updateLayout(); 124 23 : } 125 : 126 98 : void removeAudioOnlySource(const std::string& callId, const std::string& streamId) 127 : { 128 98 : std::unique_lock lk(audioOnlySourcesMtx_); 129 98 : if (audioOnlySources_.erase({callId, streamId})) { 130 14 : lk.unlock(); 131 14 : updateLayout(); 132 : } 133 98 : } 134 : 135 : void attachVideo(Observable<std::shared_ptr<MediaFrame>>* frame, 136 : const std::string& callId, 137 : const std::string& streamId); 138 : void detachVideo(Observable<std::shared_ptr<MediaFrame>>* frame); 139 : 140 9218 : StreamInfo streamInfo(Observable<std::shared_ptr<MediaFrame>>* frame) const 141 : { 142 9218 : std::lock_guard lk(videoToStreamInfoMtx_); 143 9218 : auto it = videoToStreamInfo_.find(frame); 144 9218 : if (it == videoToStreamInfo_.end()) 145 11 : return {}; 146 9207 : return it->second; 147 9218 : } 148 : 149 : private: 150 : NON_COPYABLE(VideoMixer); 151 : struct VideoMixerSource; 152 : 153 : bool render_frame(VideoFrame& output, 154 : const std::shared_ptr<VideoFrame>& input, 155 : std::unique_ptr<VideoMixerSource>& source); 156 : 157 : void calc_position(std::unique_ptr<VideoMixerSource>& source, const std::shared_ptr<VideoFrame>& input, int index); 158 : 159 : void startSink(); 160 : void stopSink(); 161 : 162 : void process(); 163 : 164 : const std::string id_; 165 : int width_ = 0; 166 : int height_ = 0; 167 : AVPixelFormat format_ = AV_PIX_FMT_YUV422P; 168 : std::shared_mutex rwMutex_; 169 : 170 : std::shared_ptr<SinkClient> sink_; 171 : 172 : std::chrono::time_point<std::chrono::steady_clock> nextProcess_; 173 : std::mutex localInputsMtx_; 174 : std::vector<std::shared_ptr<VideoFrameActiveWriter>> localInputs_ {}; 175 : void stopInput(const std::shared_ptr<VideoFrameActiveWriter>& input); 176 : 177 : VideoScaler scaler_; 178 : 179 : ThreadLoop loop_; // as to be last member 180 : 181 : Layout currentLayout_ {Layout::GRID}; 182 : std::list<std::unique_ptr<VideoMixerSource>> sources_; 183 : 184 : // We need to convert call to frame 185 : mutable std::mutex videoToStreamInfoMtx_ {}; 186 : std::map<Observable<std::shared_ptr<MediaFrame>>*, StreamInfo> videoToStreamInfo_ {}; 187 : 188 : std::mutex audioOnlySourcesMtx_; 189 : std::set<std::pair<std::string, std::string>> audioOnlySources_; 190 : std::string activeStream_ {}; 191 : 192 : std::atomic_int layoutUpdated_ {0}; 193 : OnSourcesUpdatedCb onSourcesUpdated_ {}; 194 : 195 : int64_t startTime_; 196 : int64_t lastTimestamp_; 197 : }; 198 : 199 : } // namespace video 200 : } // namespace jami