diff --git a/src/client/callmanager.cpp b/src/client/callmanager.cpp index 11a65f1d324ce7d81d9e71a94c4e0b2b020c087d..4aa66f0dae6f72957f5a5f3ab536dd3098f968bc 100644 --- a/src/client/callmanager.cpp +++ b/src/client/callmanager.cpp @@ -457,12 +457,7 @@ switchSecondaryInput(const std::string& accountId, const std::string& confId, const std::string& resource) { - if (const auto account = jami::Manager::instance().getAccount(accountId)) { - if (auto conf = account->getConference(confId)) { - conf->switchSecondaryInput(resource); - return true; - } - } + JAMI_ERR("Use requestMediaChange"); return false; } diff --git a/src/conference.cpp b/src/conference.cpp index 7583bcf3cf8914fbd91a8e949be52e9181350960..ec0e2569cf62c24e5a40b7fd7663f0091bd7378b 100644 --- a/src/conference.cpp +++ b/src/conference.cpp @@ -155,7 +155,8 @@ Conference::Conference(const std::shared_ptr<Account>& account) isModeratorMuted = shared->isMuted(streamId); if (auto videoMixer = shared->videoMixer_) active = videoMixer->verifyActive(streamId); - if (auto call = std::dynamic_pointer_cast<SIPCall>(getCall(streamInfo.callId))) { + if (auto call = std::dynamic_pointer_cast<SIPCall>( + getCall(streamInfo.callId))) { uri = call->getPeerNumber(); isLocalMuted = call->isPeerMuted(); if (auto* transport = call->getTransport()) @@ -212,21 +213,17 @@ Conference::Conference(const std::shared_ptr<Account>& account) parser_.onHangupParticipant([&](const auto& accountUri, const auto& deviceId) { hangupParticipant(accountUri, deviceId); }); - parser_.onRaiseHand( - [&](const auto& deviceId, bool state) { setHandRaised(deviceId, state); }); - parser_.onSetActiveStream([&](const auto& streamId, bool state) { - setActiveStream(streamId, state); - }); - parser_.onMuteStreamAudio - ( + parser_.onRaiseHand([&](const auto& deviceId, bool state) { setHandRaised(deviceId, state); }); + parser_.onSetActiveStream( + [&](const auto& streamId, bool state) { setActiveStream(streamId, state); }); + parser_.onMuteStreamAudio( [&](const auto& accountUri, const auto& deviceId, const auto& streamId, bool state) { muteStream(accountUri, deviceId, streamId, state); }); parser_.onSetLayout([&](int layout) { setLayout(layout); }); // Version 0, deprecated - parser_.onKickParticipant( - [&](const auto& participantId) { hangupParticipant(participantId); }); + parser_.onKickParticipant([&](const auto& participantId) { hangupParticipant(participantId); }); parser_.onSetActiveParticipant( [&](const auto& participantId) { setActiveParticipant(participantId); }); parser_.onMuteParticipant( @@ -371,7 +368,7 @@ Conference::createConfAVStreams() createConfAVStream(receiveStreamData, *videoMixer_, receiveSubject); // Preview - if (auto& videoPreview = videoMixer_->getVideoLocal()) { + if (auto videoPreview = videoMixer_->getVideoLocal()) { auto previewSubject = std::make_shared<MediaStreamSubject>(pluginVideoMap_); StreamData previewStreamData {getConfId(), false, @@ -541,24 +538,7 @@ Conference::requestMediaChange(const std::vector<DRing::MediaMap>& mediaList) mediaAttr.toString(true).c_str()); } - // NOTE: - // The current design support only one stream per media type. The - // request will be ignored if this condition is not respected. - for (auto mediaType : {MediaType::MEDIA_AUDIO, MediaType::MEDIA_VIDEO}) { - auto count = std::count_if(mediaAttrList.begin(), - mediaAttrList.end(), - [&mediaType](auto const& attr) { - return attr.type_ == mediaType; - }); - - if (count > 1) { - JAMI_ERR("[conf %s] Cant handle more than 1 stream per media type (found %lu)", - getConfId().c_str(), - count); - return false; - } - } - + uint32_t videoIdx = 0; for (auto const& mediaAttr : mediaAttrList) { #ifdef ENABLE_VIDEO auto& mediaSource = mediaAttr.type_ == MediaType::MEDIA_AUDIO ? hostAudioSource_ @@ -586,8 +566,9 @@ Conference::requestMediaChange(const std::vector<DRing::MediaMap>& mediaList) ? DRing::Media::Details::MEDIA_TYPE_AUDIO : DRing::Media::Details::MEDIA_TYPE_VIDEO); } else { - switchInput(mediaSource.sourceUri_); + videoMixer_->switchInput(mediaAttr.sourceUri_, videoIdx); } + videoIdx++; } // Update the mute state if changed. @@ -610,12 +591,15 @@ Conference::handleMediaChangeRequest(const std::shared_ptr<Call>& call, #ifdef ENABLE_VIDEO // If the new media list has video, remove the participant from audioonlylist. - if (videoMixer_ && MediaAttribute::hasMediaType( - MediaAttribute::buildMediaAttributesList(remoteMediaList, false), - MediaType::MEDIA_VIDEO)) { + if (videoMixer_ + && MediaAttribute::hasMediaType(MediaAttribute::buildMediaAttributesList(remoteMediaList, + false), + MediaType::MEDIA_VIDEO)) { auto callId = call->getCallId(); videoMixer_->removeAudioOnlySource(callId, - std::string(sip_utils::streamId(callId, 0, MediaType::MEDIA_VIDEO))); + std::string(sip_utils::streamId(callId, + 0, + MediaType::MEDIA_VIDEO))); } #endif @@ -704,7 +688,10 @@ Conference::addParticipant(const std::string& participant_id) // call, it must be listed in the audioonlylist. auto mediaList = call->getMediaAttributeList(); if (videoMixer_ && not MediaAttribute::hasMediaType(mediaList, MediaType::MEDIA_VIDEO)) { - videoMixer_->addAudioOnlySource(call->getCallId(), sip_utils::streamId(call->getCallId(), 0, MediaType::MEDIA_AUDIO)); + videoMixer_->addAudioOnlySource(call->getCallId(), + sip_utils::streamId(call->getCallId(), + 0, + MediaType::MEDIA_AUDIO)); } call->enterConference(shared_from_this()); // Continue the recording for the conference if one participant was recording @@ -736,7 +723,8 @@ Conference::setActiveParticipant(const std::string& participant_id) return; } if (auto call = getCallFromPeerID(participant_id)) { - videoMixer_->setActiveStream(sip_utils::streamId(call->getCallId(), 0, MediaType::MEDIA_VIDEO)); + videoMixer_->setActiveStream( + sip_utils::streamId(call->getCallId(), 0, MediaType::MEDIA_VIDEO)); return; } @@ -869,7 +857,8 @@ Conference::removeParticipant(const std::string& participant_id) auto sinkId = getConfId() + peerId; // Remove if active // TODO all streams - if (videoMixer_->verifyActive(sip_utils::streamId(participant_id, 0, MediaType::MEDIA_VIDEO))) + if (videoMixer_->verifyActive( + sip_utils::streamId(participant_id, 0, MediaType::MEDIA_VIDEO))) videoMixer_->resetActiveStream(); call->exitConference(); if (call->isPeerRecording()) @@ -904,9 +893,10 @@ Conference::attachLocalParticipant() #ifdef ENABLE_VIDEO if (videoMixer_) { - videoMixer_->switchInput(hostVideoSource_.sourceUri_); + std::vector<std::string> videoInputs = {hostVideoSource_.sourceUri_}; if (not mediaSecondaryInput_.empty()) - videoMixer_->switchSecondaryInput(mediaSecondaryInput_); + videoInputs.emplace_back(mediaSecondaryInput_); + videoMixer_->switchInputs(videoInputs); } #endif } else { @@ -933,7 +923,7 @@ Conference::detachLocalParticipant() #ifdef ENABLE_VIDEO if (videoMixer_) - videoMixer_->stopInput(); + videoMixer_->stopInputs(); // Reset local video source hostVideoSource_ = {}; @@ -1054,10 +1044,10 @@ Conference::switchInput(const std::string& input) return; if (auto mixer = videoMixer_) { - mixer->switchInput(input); + mixer->switchInputs({input}); #ifdef ENABLE_PLUGIN // Preview - if (auto& videoPreview = mixer->getVideoLocal()) { + if (auto videoPreview = mixer->getVideoLocal()) { auto previewSubject = std::make_shared<MediaStreamSubject>(pluginVideoMap_); StreamData previewStreamData {getConfId(), false, @@ -1071,17 +1061,6 @@ Conference::switchInput(const std::string& input) #endif } -void -Conference::switchSecondaryInput(const std::string& input) -{ -#ifdef ENABLE_VIDEO - mediaSecondaryInput_ = input; - if (videoMixer_) { - videoMixer_->switchSecondaryInput(input); - } -#endif -} - bool Conference::isVideoEnabled() const { @@ -1518,13 +1497,13 @@ Conference::muteLocalHost(bool is_muted, const std::string& mediaType) setLocalHostMuteState(MediaType::MEDIA_VIDEO, is_muted); if (is_muted) { if (auto mixer = videoMixer_) { - JAMI_DBG("Muting local video source"); - mixer->stopInput(); + JAMI_DBG("Muting local video sources"); + mixer->stopInputs(); } } else { if (auto mixer = videoMixer_) { JAMI_DBG("Un-muting local video source"); - switchInput(hostVideoSource_.sourceUri_); + mixer->switchInputs({hostVideoSource_.sourceUri_}); } } emitSignal<DRing::CallSignal::VideoMuted>(id_, is_muted); diff --git a/src/conference.h b/src/conference.h index 683a965badea0ede12e5b64887c01e5516f5f50a..2d695a8156004a893d1301088b4adcecd294b55e 100644 --- a/src/conference.h +++ b/src/conference.h @@ -323,8 +323,6 @@ public: bool toggleRecording() override; void switchInput(const std::string& input); - void switchSecondaryInput(const std::string& input); - void setActiveParticipant(const std::string& participant_id); void setActiveStream(const std::string& streamId, bool state); void setLayout(int layout); diff --git a/src/media/video/video_mixer.cpp b/src/media/video/video_mixer.cpp index 8ece82fbbd192a9194cc4555b28d21a383170499..2d56657c89d4ee67f84a06ec8c94db180a3b95bf 100644 --- a/src/media/video/video_mixer.cpp +++ b/src/media/video/video_mixer.cpp @@ -87,9 +87,11 @@ VideoMixer::VideoMixer(const std::string& id, const std::string& localInput) , loop_([] { return true; }, std::bind(&VideoMixer::process, this), [] {}) { // Local video camera is the main participant - if (not localInput.empty()) - videoLocal_ = getVideoInput(localInput); - attachVideo(videoLocal_.get(), "", sip_utils::streamId("", 0, MediaType::MEDIA_VIDEO)); + if (not localInput.empty()) { + auto videoInput = getVideoInput(localInput); + localInputs_.emplace_back(videoInput); + attachVideo(videoInput.get(), "", sip_utils::streamId("", 0, MediaType::MEDIA_VIDEO)); + } loop_.start(); nextProcess_ = std::chrono::steady_clock::now(); @@ -98,12 +100,8 @@ VideoMixer::VideoMixer(const std::string& id, const std::string& localInput) VideoMixer::~VideoMixer() { - stop_sink(); - - detachVideo(videoLocal_.get()); - videoLocal_.reset(); - detachVideo(videoLocalSecondary_.get()); - videoLocalSecondary_.reset(); + stopSink(); + stopInputs(); loop_.join(); @@ -111,61 +109,62 @@ VideoMixer::~VideoMixer() } void -VideoMixer::switchInput(const std::string& input) +VideoMixer::switchInput(const std::string& input, unsigned idx) { - JAMI_DBG("Set new input %s", input.c_str()); + std::shared_ptr<VideoFrameActiveWriter> oldInput; + auto newInput = getVideoInput(input); + std::unique_lock<std::mutex> lk(localInputsMtx_); + if (idx < localInputs_.size()) + oldInput = std::move(localInputs_[idx]); + else + localInputs_.resize(idx + 1); + localInputs_[idx] = newInput; + lk.unlock(); + if (oldInput) + stopInput(oldInput); + attachVideo(newInput.get(), "", sip_utils::streamId("", idx, MediaType::MEDIA_VIDEO)); +} - if (auto local = videoLocal_) { - // Detach videoInput from mixer - local->detach(this); -#if !VIDEO_CLIENT_INPUT - if (auto localInput = std::dynamic_pointer_cast<VideoInput>(local)) { - // Stop old VideoInput - localInput->stopInput(); - } -#endif - } +void +VideoMixer::switchInputs(const std::vector<std::string>& inputs) +{ + stopInputs(); - if (input.empty()) { - JAMI_DBG("[mixer:%s] Input is empty, don't add it to the mixer", id_.c_str()); + if (inputs.empty()) { + JAMI_DBG("[mixer:%s] Inputs is empty, don't add it to the mixer", id_.c_str()); return; } // Re-attach videoInput to mixer - videoLocal_ = getVideoInput(input); - attachVideo(videoLocal_.get(), "", sip_utils::streamId("", 0, MediaType::MEDIA_VIDEO)); + for (auto i = 0u; i != inputs.size(); ++i) { + auto videoInput = getVideoInput(inputs[i]); + { + std::lock_guard<std::mutex> lk(localInputsMtx_); + localInputs_.emplace_back(videoInput); + } + attachVideo(videoInput.get(), "", sip_utils::streamId("", i, MediaType::MEDIA_VIDEO)); + } } void -VideoMixer::switchSecondaryInput(const std::string& input) +VideoMixer::stopInput(const std::shared_ptr<VideoFrameActiveWriter>& input) { - if (auto local = videoLocalSecondary_) { - // Detach videoInput from mixer - local->detach(this); + // Detach videoInputs from mixer + input->detach(this); #if !VIDEO_CLIENT_INPUT - if (auto localInput = std::dynamic_pointer_cast<VideoInput>(local)) { - // Stop old VideoInput - localInput->stopInput(); - } + // Stop old VideoInput + if (auto oldInput = std::dynamic_pointer_cast<VideoInput>(input)) + oldInput->stopInput(); #endif - } - - if (input.empty()) { - JAMI_DBG("[mixer:%s] Input is empty, don't add it in the mixer", id_.c_str()); - return; - } - - // Re-attach videoInput to mixer - videoLocalSecondary_ = getVideoInput(input); - attachVideo(videoLocalSecondary_.get(), "", sip_utils::streamId("", 1, MediaType::MEDIA_VIDEO)); } void -VideoMixer::stopInput() +VideoMixer::stopInputs() { - if (auto local = std::move(videoLocal_)) { - local->detach(this); - } + std::lock_guard<std::mutex> lk(localInputsMtx_); + for (auto& input: localInputs_) + stopInput(input); + localInputs_.clear(); } void @@ -527,15 +526,15 @@ VideoMixer::setParameters(int width, int height, AVPixelFormat format) if (previous_p) libav_utils::fillWithBlack(previous_p->pointer()); - start_sink(); + startSink(); updateLayout(); startTime_ = av_gettime(); } void -VideoMixer::start_sink() +VideoMixer::startSink() { - stop_sink(); + stopSink(); if (width_ == 0 or height_ == 0) { JAMI_WARN("[mixer:%s] MX: unable to start with zero-sized output", id_.c_str()); @@ -552,7 +551,7 @@ VideoMixer::start_sink() } void -VideoMixer::stop_sink() +VideoMixer::stopSink() { this->detach(sink_.get()); sink_->stop(); diff --git a/src/media/video/video_mixer.h b/src/media/video/video_mixer.h index 6c014ea8c2f2f50bcfa53e08ac400507acf85952..edf41a2d454b9eacce9cdf273bc243382eb08670 100644 --- a/src/media/video/video_mixer.h +++ b/src/media/video/video_mixer.h @@ -75,9 +75,22 @@ public: void attached(Observable<std::shared_ptr<MediaFrame>>* ob) override; void detached(Observable<std::shared_ptr<MediaFrame>>* ob) override; - void switchInput(const std::string& input); - void switchSecondaryInput(const std::string& input); - void stopInput(); + /** + * Set all inputs at once + * @param inputs New inputs + * @note previous inputs will be stopped + */ + void switchInputs(const std::vector<std::string>& inputs); + /** + * Update one specific output + * @param input New input for this index + * @param idx Media's index + */ + void switchInput(const std::string& input, unsigned idx); + /** + * Stop all inputs + */ + void stopInputs(); void setActiveStream(const std::string& id); void resetActiveStream() @@ -105,7 +118,11 @@ public: MediaStream getStream(const std::string& name) const; - std::shared_ptr<VideoFrameActiveWriter>& getVideoLocal() { return videoLocal_; } + std::shared_ptr<VideoFrameActiveWriter> getVideoLocal() const { + if (!localInputs_.empty()) + return *localInputs_.begin(); + return {}; + } void updateLayout(); @@ -152,8 +169,8 @@ private: const std::shared_ptr<VideoFrame>& input, int index); - void start_sink(); - void stop_sink(); + void startSink(); + void stopSink(); void process(); @@ -166,8 +183,9 @@ private: std::shared_ptr<SinkClient> sink_; std::chrono::time_point<std::chrono::steady_clock> nextProcess_; - std::shared_ptr<VideoFrameActiveWriter> videoLocal_; - std::shared_ptr<VideoFrameActiveWriter> videoLocalSecondary_; + std::mutex localInputsMtx_; + std::vector<std::shared_ptr<VideoFrameActiveWriter>> localInputs_ {}; + void stopInput(const std::shared_ptr<VideoFrameActiveWriter>& input); VideoScaler scaler_;