diff --git a/bin/dbus/cx.ring.Ring.CallManager.xml b/bin/dbus/cx.ring.Ring.CallManager.xml index 100e178c4738e7071a0d3afcd86d7833736e5ccf..a8adc1e659a337e3507f7c8044d44bfcd2e1d965 100644 --- a/bin/dbus/cx.ring.Ring.CallManager.xml +++ b/bin/dbus/cx.ring.Ring.CallManager.xml @@ -996,7 +996,7 @@ <tp:docstring> The acceptable states are: <ul> - <li>NEGOTIATION_SUCCESS: Media negitiation succeded</li> + <li>NEGOTIATION_SUCCESS: Media negotiation succeeded</li> <li>NEGOTIATION_FAIL: Media negotiation failed</li> </ul> </tp:docstring> diff --git a/src/conference.cpp b/src/conference.cpp index 2c5c35eebb7112d04aa863902615fa0c7bdd692f..b92093ba1d2b94c6c664fe66ee16fded1003a786 100644 --- a/src/conference.cpp +++ b/src/conference.cpp @@ -52,15 +52,20 @@ using namespace std::literals; namespace jami { -Conference::Conference() +Conference::Conference(bool enableVideo) : id_(Manager::instance().callFactory.getNewCallID()) #ifdef ENABLE_VIDEO , mediaInput_(Manager::instance().getVideoManager().videoDeviceMonitor.getMRLForDefaultDevice()) + , videoEnabled_(enableVideo) #endif { JAMI_INFO("Create new conference %s", id_.c_str()); #ifdef ENABLE_VIDEO + // We are done if the video is disabled. + if (not videoEnabled_) + return; + getVideoMixer()->setOnSourcesUpdated([this](std::vector<video::SourceInfo>&& infos) { runOnMainThread([w = weak(), infos = std::move(infos)] { auto shared = w.lock(); @@ -376,7 +381,7 @@ Conference::takeOverMediaSourceControl(const std::string& callId) } void -Conference::add(const std::string& participant_id) +Conference::addParticipant(const std::string& participant_id) { JAMI_DBG("Adding call %s to conference %s", participant_id.c_str(), id_.c_str()); @@ -574,7 +579,7 @@ Conference::detachVideo(Observable<std::shared_ptr<MediaFrame>>* frame) } void -Conference::remove(const std::string& participant_id) +Conference::removeParticipant(const std::string& participant_id) { if (participants_.erase(participant_id)) { if (auto call = getCall(participant_id)) { @@ -589,7 +594,7 @@ Conference::remove(const std::string& participant_id) } void -Conference::attach() +Conference::attachLocalParticipant() { JAMI_INFO("Attach local participant to conference %s", id_.c_str()); @@ -610,10 +615,12 @@ Conference::attach() rbPool.flush(RingBufferPool::DEFAULT_ID); #ifdef ENABLE_VIDEO - if (auto mixer = getVideoMixer()) { - mixer->switchInput(mediaInput_); - if (not mediaSecondaryInput_.empty()) - mixer->switchSecondaryInput(mediaSecondaryInput_); + if (isVideoEnabled()) { + if (auto mixer = getVideoMixer()) { + mixer->switchInput(mediaInput_); + if (not mediaSecondaryInput_.empty()) + mixer->switchSecondaryInput(mediaSecondaryInput_); + } } #endif setState(State::ACTIVE_ATTACHED); @@ -626,7 +633,7 @@ Conference::attach() } void -Conference::detach() +Conference::detachLocalParticipant() { JAMI_INFO("Detach local participant from conference %s", id_.c_str()); @@ -636,8 +643,10 @@ Conference::detach() RingBufferPool::DEFAULT_ID); } #ifdef ENABLE_VIDEO - if (auto mixer = getVideoMixer()) { - mixer->stopInput(); + if (isVideoEnabled()) { + if (auto mixer = getVideoMixer()) { + mixer->stopInput(); + } } #endif setState(State::ACTIVE_DETACHED); @@ -747,6 +756,11 @@ Conference::switchInput(const std::string& input) { #ifdef ENABLE_VIDEO mediaInput_ = input; + + // Done if the video is disabled + if (not isVideoEnabled()) + return; + if (auto mixer = getVideoMixer()) { mixer->switchInput(input); #ifdef ENABLE_PLUGIN @@ -766,10 +780,18 @@ Conference::switchSecondaryInput(const std::string& input) { #ifdef ENABLE_VIDEO mediaSecondaryInput_ = input; - getVideoMixer()->switchSecondaryInput(input); + if (isVideoEnabled()) { + getVideoMixer()->switchSecondaryInput(input); + } #endif } +bool +Conference::isVideoEnabled() const +{ + return videoEnabled_; +} + #ifdef ENABLE_VIDEO std::shared_ptr<video::VideoMixer> Conference::getVideoMixer() @@ -846,7 +868,7 @@ Conference::onConfOrder(const std::string& callId, const std::string& confOrder) peerID.data()); return; } - if (root.isMember("layout")) { + if (isVideoEnabled() and root.isMember("layout")) { setLayout(root["layout"].asUInt()); } if (root.isMember("activeParticipant")) { @@ -1107,6 +1129,11 @@ Conference::muteLocalHost(bool is_muted, const std::string& mediaType) return; } else if (mediaType.compare(DRing::Media::Details::MEDIA_TYPE_VIDEO) == 0) { #ifdef ENABLE_VIDEO + if (not isVideoEnabled()) { + JAMI_ERR("Cant't mute, the video is disabled!"); + return; + } + if (is_muted == (isMediaSourceMuted(MediaType::MEDIA_VIDEO))) { JAMI_DBG("Local video source already in [%s] state", is_muted ? "muted" : "un-muted"); return; @@ -1210,7 +1237,7 @@ Conference::mergeConfInfo(ConfInfo& newInfo, const std::string& peerURI) updateNeeded = true; } // Send confInfo only if needed to avoid loops - if (updateNeeded) { + if (updateNeeded and isVideoEnabled()) { // Trigger the layout update in the mixer because the frame resolution may // change from participant to conference and cause a mismatch between // confInfo layout and rendering layout. diff --git a/src/conference.h b/src/conference.h index 0a2b185240da84eb2b09d1a061a5c860c40179eb..143319f1cf077960f739746962747026e8203aea 100644 --- a/src/conference.h +++ b/src/conference.h @@ -183,7 +183,7 @@ public: /** * Constructor for this class, increment static counter */ - Conference(); + explicit Conference(bool enableVideo); /** * Destructor for this class, decrement static counter @@ -246,22 +246,22 @@ public: /** * Add a new participant to the conference */ - void add(const std::string& participant_id); + void addParticipant(const std::string& participant_id); /** * Remove a participant from the conference */ - void remove(const std::string& participant_id); + void removeParticipant(const std::string& participant_id); /** * Attach local audio/video to the conference */ - void attach(); + void attachLocalParticipant(); /** * Detach local audio/video from the conference */ - void detach(); + void detachLocalParticipant(); /** * Bind a participant to the conference @@ -304,6 +304,8 @@ public: void onConfOrder(const std::string& callId, const std::string& order); + bool isVideoEnabled() const; + #ifdef ENABLE_VIDEO std::shared_ptr<video::VideoMixer> getVideoMixer(); std::string getVideoInput() const { return mediaInput_; } @@ -338,6 +340,7 @@ private: std::string id_; State confState_ {State::ACTIVE_ATTACHED}; ParticipantSet participants_; + bool videoEnabled_ {false}; mutable std::mutex confInfoMutex_ {}; ConfInfo confInfo_ {}; diff --git a/src/manager.cpp b/src/manager.cpp index a6922ce09bc91749621982445fb434e8c741067a..305cc12925f41c57823a6f8bbc03c24841fa1d0d 100644 --- a/src/manager.cpp +++ b/src/manager.cpp @@ -693,7 +693,7 @@ Manager::ManagerPimpl::bindCallToConference(Call& call, Conference& conf) base_.getRingBufferPool().unBindAll(call_id); - conf.add(call_id); + conf.addParticipant(call_id); call.setConfId(conf_id); if (state == "HOLD") { @@ -1477,7 +1477,7 @@ Manager::holdConference(const std::string& id) JAMI_INFO("Hold conference %s", id.c_str()); if (auto conf = getConferenceFromID(id)) { - conf->detach(); + conf->detachLocalParticipant(); emitSignal<DRing::CallSignal::ConferenceChanged>(conf->getConfID(), conf->getStateStr()); return true; } @@ -1567,7 +1567,7 @@ Manager::ManagerPimpl::addMainParticipant(Conference& conf) { { std::lock_guard<std::mutex> lock(audioLayerMutex_); - conf.attach(); + conf.attachLocalParticipant(); } emitSignal<DRing::CallSignal::ConferenceChanged>(conf.getConfID(), conf.getStateStr()); switchCall(conf.getConfID()); @@ -1617,8 +1617,19 @@ Manager::joinParticipant(const std::string& callId1, const std::string& callId2, return false; } - auto conf = std::make_shared<Conference>(); + // NOTE: The current API does not provide the account that owns + // the conference that is about to be created. Thus, the video + // will be enabled if enabled on one account. + bool videoEnabled {false}; + { + auto acc1 = call1->getAccount().lock(); + auto acc2 = call2->getAccount().lock(); + if (acc1 and acc2) { + videoEnabled = acc1->isVideoEnabled() or acc2->isVideoEnabled(); + } + } + auto conf = std::make_shared<Conference>(videoEnabled); pimpl_->conferenceMap_.emplace(conf->getConfID(), conf); // Bind calls according to their state @@ -1630,7 +1641,7 @@ Manager::joinParticipant(const std::string& callId1, const std::string& callId2, pimpl_->switchCall(conf->getConfID()); conf->setState(Conference::State::ACTIVE_ATTACHED); } else { - conf->detach(); + conf->detachLocalParticipant(); } emitSignal<DRing::CallSignal::ConferenceCreated>(conf->getConfID()); @@ -1646,7 +1657,21 @@ Manager::createConfFromParticipantList(const std::vector<std::string>& participa return; } - auto conf = std::make_shared<Conference>(); + // Check if we need to enable video. + // NOTE: The current API does not provide the account that owns + // the conference that is about to be created. Thus, the video + // will be enabled if enabled on one account. + bool videoEnabled {false}; + for (const auto& numberaccount : participantList) { + std::string tostr(numberaccount.substr(0, numberaccount.find(','))); + std::string accountId(numberaccount.substr(numberaccount.find(',') + 1, numberaccount.size())); + auto account = getAccount(accountId); + if (account) { + videoEnabled |= account->isVideoEnabled(); + } + } + + auto conf = std::make_shared<Conference>(videoEnabled); int successCounter = 0; @@ -1662,7 +1687,7 @@ Manager::createConfFromParticipantList(const std::vector<std::string>& participa continue; // Manager methods may behave differently if the call id participates in a conference - conf->add(call_id); + conf->addParticipant(call_id); successCounter++; } @@ -1677,7 +1702,8 @@ void Manager::setConferenceLayout(const std::string& confId, int layout) { if (auto conf = getConferenceFromID(confId)) { - conf->setLayout(layout); + if (conf->isVideoEnabled()) + conf->setLayout(layout); } else if (auto call = getCallFromCallID(confId)) { std::map<std::string, std::string> messages; Json::Value root; @@ -1718,7 +1744,7 @@ Manager::detachLocalParticipant(const std::string& conf_id) JAMI_INFO("Detach local participant from conference %s", conf_id.c_str()); if (auto conf = getConferenceFromID(conf_id.empty() ? getCurrentCallId() : conf_id)) { - conf->detach(); + conf->detachLocalParticipant(); emitSignal<DRing::CallSignal::ConferenceChanged>(conf->getConfID(), conf->getStateStr()); pimpl_->unsetCurrentCall(); return true; @@ -1770,7 +1796,7 @@ Manager::removeParticipant(const std::string& call_id) return; } - conf->remove(call_id); + conf->removeParticipant(call_id); call->setConfId(""); removeAudio(*call); @@ -1811,7 +1837,7 @@ Manager::joinConference(const std::string& conf_id1, const std::string& conf_id2 continue; } - conf->remove(p); + conf->removeParticipant(p); call->setConfId(""); removeAudio(*call); } @@ -2802,13 +2828,20 @@ Manager::ManagerPimpl::processIncomingCall(Call& incomCall, const std::string& a } } } + auto acc = call->getAccount().lock(); + if (not acc) { + JAMI_ERR("No account detected"); + return; + } + // First call - auto conf = std::make_shared<Conference>(); + auto conf = std::make_shared<Conference>(acc->isVideoEnabled()); + conferenceMap_.emplace(conf->getConfID(), conf); // Bind calls according to their state bindCallToConference(*call, *conf); - conf->detach(); + conf->detachLocalParticipant(); emitSignal<DRing::CallSignal::ConferenceCreated>(conf->getConfID()); }); @@ -2850,6 +2883,7 @@ Manager::ManagerPimpl::processIncomingCall(Call& incomCall, const std::string& a } } + AudioFormat Manager::hardwareAudioFormatChanged(AudioFormat format) { diff --git a/src/media/video/video_mixer.cpp b/src/media/video/video_mixer.cpp index f7f6950f8c85af3d9bce103167da00912233d538..9271534a7d77b0099de07fd77776b59d94359bcd 100644 --- a/src/media/video/video_mixer.cpp +++ b/src/media/video/video_mixer.cpp @@ -90,6 +90,8 @@ VideoMixer::VideoMixer(const std::string& id) videoLocal_->attach(this); loop_.start(); nextProcess_ = std::chrono::steady_clock::now(); + + JAMI_DBG("[mixer:%s] New instance created", id_.c_str()); } VideoMixer::~VideoMixer() @@ -108,6 +110,8 @@ VideoMixer::~VideoMixer() } loop_.join(); + + JAMI_DBG("[mixer:%s] Instance destroyed", id_.c_str()); } void @@ -127,7 +131,7 @@ VideoMixer::switchInput(const std::string& input) } if (input.empty()) { - JAMI_DBG("Input is empty, don't add it in the mixer"); + JAMI_DBG("[mixer:%s] Input is empty, don't add it in the mixer", id_.c_str()); return; } @@ -156,7 +160,7 @@ VideoMixer::switchSecondaryInput(const std::string& input) videoLocalSecondary_ = getVideoInput(input); if (input.empty()) { - JAMI_DBG("Input is empty, don't add it in the mixer"); + JAMI_DBG("[mixer:%s] Input is empty, don't add it in the mixer", id_.c_str()); return; } @@ -244,7 +248,7 @@ VideoMixer::update(Observable<std::shared_ptr<MediaFrame>>* ob, AV_PIX_FMT_NV12); x->atomic_copy(*std::static_pointer_cast<VideoFrame>(frame)); } catch (const std::runtime_error& e) { - JAMI_ERR("Accel failure: %s", e.what()); + JAMI_ERR("[mixer:%s] Accel failure: %s", id_.c_str(), e.what()); return; } #else @@ -267,7 +271,7 @@ VideoMixer::process() try { output.reserve(format_, width_, height_); } catch (const std::bad_alloc& e) { - JAMI_ERR("VideoFrame::allocBuffer() failed"); + JAMI_ERR("[mixer:%s] VideoFrame::allocBuffer() failed", id_.c_str()); return; } @@ -329,7 +333,7 @@ VideoMixer::process() if (fooInput) successfullyRendered |= render_frame(output, fooInput, x); else - JAMI_WARN("Nothing to render for %p", x->source); + JAMI_WARN("[mixer:%s] Nothing to render for %p", id_.c_str(), x->source); } x->hasVideo = !blackFrame && successfullyRendered; @@ -502,12 +506,12 @@ VideoMixer::start_sink() stop_sink(); if (width_ == 0 or height_ == 0) { - JAMI_WARN("MX: unable to start with zero-sized output"); + JAMI_WARN("[mixer:%s] MX: unable to start with zero-sized output", id_.c_str()); return; } if (not sink_->start()) { - JAMI_ERR("MX: sink startup failed"); + JAMI_ERR("[mixer:%s] MX: sink startup failed", id_.c_str()); return; } diff --git a/src/sip/sipcall.cpp b/src/sip/sipcall.cpp index 1c2e1eeab03c62a30d99928625b280dedc4edad1..6d5d6af44d7186589ce1a6e1c216efc173607919 100644 --- a/src/sip/sipcall.cpp +++ b/src/sip/sipcall.cpp @@ -1979,7 +1979,7 @@ SIPCall::startAllMedia() readyToRecord_ = false; resetMediaReady(); #ifdef ENABLE_VIDEO - bool isVideoEnabled = false; + bool hasActiveVideo = false; #endif int currentCompId = 1; @@ -1991,7 +1991,7 @@ SIPCall::startAllMedia() #ifdef ENABLE_VIDEO if (iter->mediaAttribute_->type_ == MEDIA_VIDEO) - isVideoEnabled |= iter->mediaAttribute_->enabled_; + hasActiveVideo |= iter->mediaAttribute_->enabled_; #endif // Not restarting media loop on hold as it's a huge waste of CPU ressources @@ -2014,9 +2014,10 @@ SIPCall::startAllMedia() #ifdef ENABLE_VIDEO // TODO. Move this elsewhere (when adding participant to conf?) - if (!isVideoEnabled && !getConfId().empty()) { + if (!hasActiveVideo && !getConfId().empty()) { auto conference = Manager::instance().getConferenceFromID(getConfId()); - conference->attachVideo(getReceiveVideoFrameActiveWriter().get(), getCallId()); + if (conference->isVideoEnabled()) + conference->attachVideo(getReceiveVideoFrameActiveWriter().get(), getCallId()); } #endif @@ -2708,17 +2709,15 @@ SIPCall::enterConference(const std::string& confId) return; } - auto videoRtp = getVideoRtp(); - if (not videoRtp) { - // In conference, we need to have a video RTP session even - // if it's an audio only call - videoRtp = addDummyVideoRtpSession(); + if (conf->isVideoEnabled()) { + auto videoRtp = getVideoRtp(); if (not videoRtp) { - throw std::runtime_error("Failed to create dummy RTP video session"); + JAMI_ERR("[call:%s] Failed to get a valid video RTP session", getCallId().c_str()); + throw std::runtime_error("Failed to get a valid video RTP session"); } + videoRtp->enterConference(conf.get()); } - videoRtp->enterConference(conf.get()); #endif #ifdef ENABLE_PLUGIN