diff --git a/bin/dbus/cx.ring.Ring.CallManager.xml b/bin/dbus/cx.ring.Ring.CallManager.xml index ee796b871afc999d14050b4e9e6045afbe64d122..76c1cf8427bddbfb73705238baa0ff7a893bfe80 100644 --- a/bin/dbus/cx.ring.Ring.CallManager.xml +++ b/bin/dbus/cx.ring.Ring.CallManager.xml @@ -514,6 +514,26 @@ <arg type="b" name="isMixed" direction="in"/> </method> + + <method name="getConferenceInfos" tp:name-for-bindings="getConferenceInfos"> + <tp:docstring> + Retrieve conferences infos with the following format: + Layout = { + { + "uri": "participant", "x":"0", "y":"0", "w": "0", "h": "0" + }, + { + "uri": "participant1", "x":"0", "y":"0", "w": "0", "h": "0" + } + (...) + } + </tp:docstring> + <tp:added version="9.5.0"/> + <arg type="s" name="confId" direction="in" /> + <annotation name="org.qtproject.QtDBus.QtTypeName.Out0" value="VectorMapStringString"/> + <arg type="aa{ss}" direction="out" /> + </method> + <signal name="incomingCall" tp:name-for-bindings="incomingCall"> <tp:docstring> <p>Notify an incoming call.</p> @@ -815,5 +835,12 @@ <arg type="s" name="callID" /> <arg type="b" name="videoMuted" /> </signal> + + <signal name="onConferenceInfosUpdated" tp:name-for-bindings="onConferenceInfosUpdated"> + <tp:added version="9.5.0"/> + <arg type="s" name="confId" /> + <annotation name="org.qtproject.QtDBus.QtTypeName.In1" value="VectorMapStringString"/> + <arg type="aa{ss}" name="infos" /> + </signal> </interface> </node> diff --git a/bin/dbus/dbuscallmanager.cpp b/bin/dbus/dbuscallmanager.cpp index 68b1f12ad8255225b22fe3199c12de684c98215d..ff4da488460c2f7a75c08893fd6f49c10ec6399c 100644 --- a/bin/dbus/dbuscallmanager.cpp +++ b/bin/dbus/dbuscallmanager.cpp @@ -99,6 +99,12 @@ DBusCallManager::getCallList() -> decltype(DRing::getCallList()) return DRing::getCallList(); } +std::vector<std::map<std::string, std::string>> +DBusCallManager::getConferenceInfos(const std::string& confId) +{ + return DRing::getConferenceInfos(confId); +} + void DBusCallManager::removeConference(const std::string& conference_id) { diff --git a/bin/dbus/dbuscallmanager.h b/bin/dbus/dbuscallmanager.h index 1a3d0b03adbabacfd740a239960ce8af31465351..bb3f547c13f27b29eef16ed9ddc267de51ee7c61 100644 --- a/bin/dbus/dbuscallmanager.h +++ b/bin/dbus/dbuscallmanager.h @@ -68,6 +68,7 @@ class DRING_PUBLIC DBusCallManager : bool attendedTransfer(const std::string& transferID, const std::string& targetID); std::map<std::string, std::string> getCallDetails(const std::string& callID); std::vector<std::string> getCallList(); + std::vector<std::map<std::string, std::string>> getConferenceInfos(const std::string& confId); void removeConference(const std::string& conference_id); bool joinParticipant(const std::string& sel_callID, const std::string& drag_callID); void createConfFromParticipantList(const std::vector< std::string >& participants); diff --git a/bin/dbus/dbusclient.cpp b/bin/dbus/dbusclient.cpp index 3f6cff264c4e2e6d43bf64fbbf025cc550ff09d6..18a9c1b42e16de9aad74b5e4ceb0e233edc92a23 100644 --- a/bin/dbus/dbusclient.cpp +++ b/bin/dbus/dbusclient.cpp @@ -173,6 +173,7 @@ DBusClient::initLibrary(int flags) exportable_callback<CallSignal::SecureSdesOn>(bind(&DBusCallManager::secureSdesOn, callM, _1)), exportable_callback<CallSignal::SecureSdesOff>(bind(&DBusCallManager::secureSdesOff, callM, _1)), exportable_callback<CallSignal::RtcpReportReceived>(bind(&DBusCallManager::onRtcpReportReceived, callM, _1, _2)), + exportable_callback<CallSignal::OnConferenceInfosUpdated>(bind(&DBusCallManager::onConferenceInfosUpdated, callM, _1, _2)), exportable_callback<CallSignal::PeerHold>(bind(&DBusCallManager::peerHold, callM, _1, _2)), exportable_callback<CallSignal::AudioMuted>(bind(&DBusCallManager::audioMuted, callM, _1, _2)), exportable_callback<CallSignal::VideoMuted>(bind(&DBusCallManager::videoMuted, callM, _1, _2)), diff --git a/bin/jni/callmanager.i b/bin/jni/callmanager.i index a7008f54f399e953015298fd82422b73f2a62319..b0d5f1e9c61bb2edd475016e5d004dad1d55effc 100644 --- a/bin/jni/callmanager.i +++ b/bin/jni/callmanager.i @@ -45,6 +45,7 @@ public: virtual void recordingStateChanged(const std::string& call_id, int code){} virtual void recordStateChange(const std::string& call_id, int state){} virtual void onRtcpReportReceived(const std::string& call_id, const std::map<std::string, int>& stats){} + virtual void onConferenceInfosUpdated(const std::string& confId, const std::vector<std::map<std::string, std::string>>& infos) {} virtual void peerHold(const std::string& call_id, bool holding){} virtual void connectionUpdate(const std::string& id, int state){} }; @@ -89,6 +90,7 @@ std::vector<std::string> getParticipantList(const std::string& confID); std::vector<std::string> getDisplayNames(const std::string& confID); std::string getConferenceId(const std::string& callID); std::map<std::string, std::string> getConferenceDetails(const std::string& callID); +std::vector<std::map<std::string, std::string>> getConferenceInfos(const std::string& confId); /* File Playback methods */ bool startRecordedFilePlayback(const std::string& filepath); @@ -132,6 +134,7 @@ public: virtual void recordingStateChanged(const std::string& call_id, int code){} virtual void recordStateChange(const std::string& call_id, int state){} virtual void onRtcpReportReceived(const std::string& call_id, const std::map<std::string, int>& stats){} + virtual void onConferenceInfosUpdated(const std::string& confId, const std::vector<std::map<std::string, std::string>>& infos) {} virtual void peerHold(const std::string& call_id, bool holding){} virtual void connectionUpdate(const std::string& id, int state){} }; diff --git a/bin/jni/jni_interface.i b/bin/jni/jni_interface.i index c6df5572028abd3f33dfe08025c479e1b6dd954a..67c66631fa4fa6d9af47080cba8f7f4d53824724 100644 --- a/bin/jni/jni_interface.i +++ b/bin/jni/jni_interface.i @@ -247,6 +247,7 @@ void init(ConfigurationCallback* confM, Callback* callM, PresenceCallback* presM exportable_callback<CallSignal::ConferenceRemoved>(bind(&Callback::conferenceRemoved, callM, _1)), exportable_callback<CallSignal::RecordingStateChanged>(bind(&Callback::recordingStateChanged, callM, _1, _2)), exportable_callback<CallSignal::RtcpReportReceived>(bind(&Callback::onRtcpReportReceived, callM, _1, _2)), + exportable_callback<CallSignal::OnConferenceInfosUpdated>(bind(&Callback::onConferenceInfosUpdated, callM, _1, _2)), exportable_callback<CallSignal::PeerHold>(bind(&Callback::peerHold, callM, _1, _2)), exportable_callback<CallSignal::ConnectionUpdate>(bind(&Callback::connectionUpdate, callM, _1, _2)) }; diff --git a/bin/nodejs/callmanager.i b/bin/nodejs/callmanager.i index 6f584daf4cff158bd4240d8542d78ad0d1203de8..b213005a0b069898987e20100c5786c02a012ec7 100644 --- a/bin/nodejs/callmanager.i +++ b/bin/nodejs/callmanager.i @@ -45,6 +45,7 @@ public: virtual void recordingStateChanged(const std::string& call_id, int code){} virtual void recordStateChange(const std::string& call_id, int state){} virtual void onRtcpReportReceived(const std::string& call_id, const std::map<std::string, int>& stats){} + virtual void onConferenceInfosUpdated(const std::string& confId, const std::vector<std::map<std::string, std::string>>& infos) {} virtual void peerHold(const std::string& call_id, bool holding){} }; @@ -88,6 +89,7 @@ std::vector<std::string> getParticipantList(const std::string& confID); std::vector<std::string> getDisplayNames(const std::string& confID); std::string getConferenceId(const std::string& callID); std::map<std::string, std::string> getConferenceDetails(const std::string& callID); +std::vector<std::map<std::string, std::string>> getConferenceInfos(const std::string& confId); /* File Playback methods */ bool startRecordedFilePlayback(const std::string& filepath); @@ -131,5 +133,6 @@ public: virtual void recordingStateChanged(const std::string& call_id, int code){} virtual void recordStateChange(const std::string& call_id, int state){} virtual void onRtcpReportReceived(const std::string& call_id, const std::map<std::string, int>& stats){} + virtual void onConferenceInfosUpdated(const std::string& confId, const std::vector<std::map<std::string, std::string>>& infos) {} virtual void peerHold(const std::string& call_id, bool holding){} }; diff --git a/configure.ac b/configure.ac index 79ad5e1d3d98e5903157f14f6620123831c25cf6..455f6256a2a27faa3f2e8eed05e9129c0f6e6ad9 100644 --- a/configure.ac +++ b/configure.ac @@ -2,7 +2,7 @@ dnl Jami - configure.ac for automake 1.9 and autoconf 2.59 dnl Process this file with autoconf to produce a configure script. AC_PREREQ([2.65]) -AC_INIT([Jami Daemon],[9.4.0],[ring@gnu.org],[jami]) +AC_INIT([Jami Daemon],[9.5.0],[ring@gnu.org],[jami]) AC_COPYRIGHT([[Copyright (c) Savoir-faire Linux 2004-2019]]) AC_REVISION([$Revision$]) diff --git a/meson.build b/meson.build index b114f8088297cae2e36b7b10fbf7cfa1099fd223..9f49b57ba3e78382bc5c56466766372f870db601 100644 --- a/meson.build +++ b/meson.build @@ -1,5 +1,5 @@ project('jami-daemon', ['c', 'cpp'], - version: '9.4.0', + version: '9.5.0', license: 'GPL3+', default_options: ['cpp_std=gnu++17', 'buildtype=debugoptimized'], meson_version:'>= 0.54' diff --git a/src/account.h b/src/account.h index 4049e33a194bb0b02a233498f266e0573350da32..bffe3c409fadac3df4ef8a37c5a64f7ce1129194 100644 --- a/src/account.h +++ b/src/account.h @@ -100,6 +100,8 @@ class Account : public Serializable, public std::enable_shared_from_this<Account virtual std::map<std::string, std::string> getVolatileAccountDetails() const; + virtual std::string getFromUri() const = 0; + /** * Load the settings for this account. */ diff --git a/src/call.cpp b/src/call.cpp index 875592cf30a07f2e842c334299174ef1bbe3ee4a..ec479febcfb1afc03d88af424381e54bcd953547 100644 --- a/src/call.cpp +++ b/src/call.cpp @@ -394,6 +394,12 @@ Call::getNullDetails() void Call::onTextMessage(std::map<std::string, std::string>&& messages) { + auto it = messages.find("application/confInfo+json"); + if (it != messages.end()) { + setConferenceInfo(it->second); + return; + } + { std::lock_guard<std::recursive_mutex> lk {callMutex_}; if (parent_) { @@ -606,4 +612,32 @@ Call::safePopSubcalls() return old_value; } +void +Call::setConferenceInfo(const std::string& msg) +{ + ConfInfo newInfo; + Json::Value json; + std::string err; + Json::CharReaderBuilder rbuilder; + auto reader = std::unique_ptr<Json::CharReader>(rbuilder.newCharReader()); + if (reader->parse(msg.data(), msg.data() + msg.size(), &json, &err)) { + for (const auto& participantInfo: json) { + ParticipantInfo pInfo; + if (!participantInfo.isMember("uri")) continue; + pInfo.fromJson(participantInfo); + newInfo.emplace_back(pInfo); + } + } + + std::vector<std::map<std::string, std::string>> toSend; + { + std::lock_guard<std::mutex> lk(confInfoMutex_); + confInfo_ = std::move(newInfo); + toSend = confInfo_.toVectorMapStringString(); + } + + // Inform client that layout has changed + jami::emitSignal<DRing::CallSignal::OnConferenceInfosUpdated>(id_, std::move(toSend)); +} + } // namespace jami diff --git a/src/call.h b/src/call.h index eba94d615534f201ed07539acf17b842291e62b3..968b4537ae6793aceddefa2466834570850a6fca 100644 --- a/src/call.h +++ b/src/call.h @@ -31,6 +31,7 @@ #include "recordable.h" #include "ip_utils.h" +#include "conference.h" #include <atomic> #include <mutex> @@ -330,6 +331,20 @@ class Call : public Recordable, public std::enable_shared_from_this<Call> { bool hasVideo() const { return not isAudioOnly_; } + /** + * A Call can be in a conference. If this is the case, the other side + * will send conference informations describing the rendered image + * @msg A JSON object describing the conference + */ + void setConferenceInfo(const std::string& msg); + + std::vector<std::map<std::string, std::string>> + getConferenceInfos() const + { + return confInfo_.toVectorMapStringString(); + } + + protected: virtual void merge(Call& scall); @@ -409,6 +424,9 @@ class Call : public Recordable, public std::enable_shared_from_this<Call> { // If the call is blocked during the progressing state OnNeedFallbackCb onNeedFallback_; std::atomic_bool startFallback_ {true}; + + mutable std::mutex confInfoMutex_ {}; + mutable ConfInfo confInfo_ {}; }; // Helpers diff --git a/src/client/callmanager.cpp b/src/client/callmanager.cpp index cd6e591e2656748264d1b041f060f4f0e2267df4..28491d3a8011aa09f65fa0cad74d9ee9bbc579c8 100644 --- a/src/client/callmanager.cpp +++ b/src/client/callmanager.cpp @@ -298,6 +298,12 @@ getCallList() return jami::Manager::instance().getCallList(); } +std::vector<std::map<std::string, std::string>> +getConferenceInfos(const std::string& confId) +{ + return jami::Manager::instance().getConferenceInfos(confId); +} + void playDTMF(const std::string& key) { diff --git a/src/client/ring_signal.cpp b/src/client/ring_signal.cpp index 386d0a6f7a15dc49e896874c84fc38e13fbc6068..30c5cda0f5bc3e3c8337e3b5ef038f5487432173 100644 --- a/src/client/ring_signal.cpp +++ b/src/client/ring_signal.cpp @@ -48,6 +48,7 @@ getSignalHandlers() exported_callback<DRing::CallSignal::AudioMuted>(), exported_callback<DRing::CallSignal::SmartInfo>(), exported_callback<DRing::CallSignal::ConnectionUpdate>(), + exported_callback<DRing::CallSignal::OnConferenceInfosUpdated>(), /* Configuration */ exported_callback<DRing::ConfigurationSignal::VolumeChanged>(), diff --git a/src/conference.cpp b/src/conference.cpp index c8a22d874d9d7e23934317219008235a0f01239e..e536a7d410d8ed9e332bd59ca5d93209e3baa268 100644 --- a/src/conference.cpp +++ b/src/conference.cpp @@ -44,7 +44,47 @@ Conference::Conference() #ifdef ENABLE_VIDEO , mediaInput_(Manager::instance().getVideoManager().videoDeviceMonitor.getMRLForDefaultDevice()) #endif -{} +{ +#ifdef ENABLE_VIDEO + getVideoMixer()->setOnSourcesUpdated([this](const std::vector<video::SourceInfo>&& infos) { + runOnMainThread([w=weak(), infos=std::move(infos)]{ + auto shared = w.lock(); + if (!shared) + return; + ConfInfo newInfo; + std::unique_lock<std::mutex> lk(shared->videoToCallMtx_); + for (const auto& info: infos) { + std::string uri = "local"; + auto it = shared->videoToCall_.find(info.source); + if (it == shared->videoToCall_.end()) + it = shared->videoToCall_.emplace_hint(it, info.source, std::string()); + // If not local + if (!it->second.empty()) { + // Retrieve calls participants + // TODO: this is a first version, we assume that the peer is not + // a master of a conference and there is only one remote + // In the future, we should retrieve confInfo from the call + // To merge layouts informations + if (auto call = Manager::instance().callFactory.getCall<SIPCall>(it->second)) { + uri = call->getPeerNumber(); + } + } + newInfo.emplace_back(ParticipantInfo { + std::move(uri), info.x, info.y, info.w, info.h + }); + } + lk.unlock(); + + { + std::lock_guard<std::mutex> lk2(shared->confInfoMutex_); + shared->confInfo_ = std::move(newInfo); + } + + shared->sendConferenceInfos(); + }); + }); +#endif +} Conference::~Conference() { @@ -97,6 +137,66 @@ Conference::setActiveParticipant(const std::string &participant_id) videoMixer_->setActiveParticipant(nullptr); } +std::vector<std::map<std::string, std::string>> +ConfInfo::toVectorMapStringString() const +{ + std::vector<std::map<std::string, std::string>> infos; + infos.reserve(size()); + auto it = cbegin(); + while (it != cend()) { + infos.emplace_back(it->toMap()); + ++it; + } + return infos; +} + +void +Conference::sendConferenceInfos() +{ + Json::Value jsonArray; + std::vector<std::map<std::string, std::string>> toSend; + { + std::lock_guard<std::mutex> lk2(confInfoMutex_); + for (const auto& info: confInfo_) { + jsonArray.append(info.toJson()); + } + toSend = confInfo_.toVectorMapStringString(); + } + + Json::StreamWriterBuilder builder; + const auto confInfo = Json::writeString(builder, jsonArray); + // Inform calls that the layout has changed + for (const auto &participant_id : participants_) { + if (auto call = Manager::instance().callFactory.getCall<SIPCall>(participant_id)) { + call->sendTextMessage( + std::map<std::string, std::string> {{"application/confInfo+json", confInfo}}, + call->getAccount().getFromUri()); + } + } + + // Inform client that layout has changed + jami::emitSignal<DRing::CallSignal::OnConferenceInfosUpdated>(id_, std::move(toSend)); +} + +void +Conference::attachVideo(Observable<std::shared_ptr<MediaFrame>>* frame, const std::string& callId) +{ + std::lock_guard<std::mutex> lk(videoToCallMtx_); + videoToCall_.emplace(frame, callId); + frame->attach(getVideoMixer().get()); +} + +void +Conference::detachVideo(Observable<std::shared_ptr<MediaFrame>>* frame) +{ + std::lock_guard<std::mutex> lk(videoToCallMtx_); + auto it = videoToCall_.find(frame); + if (it != videoToCall_.end()) { + it->first->detach(getVideoMixer().get()); + videoToCall_.erase(it); + } +} + void Conference::remove(const std::string &participant_id) { diff --git a/src/conference.h b/src/conference.h index d165d47a267d54645ffb234c1f09fdf28f272936..004d885a4358008d610f32302183227d09b821cb 100644 --- a/src/conference.h +++ b/src/conference.h @@ -29,6 +29,8 @@ #include <memory> #include <vector> +#include <json/json.h> + #include "recordable.h" namespace jami { @@ -39,9 +41,54 @@ class VideoMixer; } #endif +struct ParticipantInfo +{ + std::string uri; + int x {0}; + int y {0}; + int w {0}; + int h {0}; + + void fromJson(const Json::Value& v) { + uri = v["uri"].asString(); + x = v["x"].asInt(); + y = v["y"].asInt(); + w = v["w"].asInt(); + h = v["h"].asInt(); + } + + Json::Value toJson() const { + Json::Value val; + val["uri"] = uri; + val["x"] = x; + val["y"] = y; + val["w"] = w; + val["h"] = h; + return val; + } + + std::map<std::string, std::string> toMap() const { + return { + {"uri", uri}, + {"x", std::to_string(x)}, + {"y", std::to_string(y)}, + {"w", std::to_string(w)}, + {"h", std::to_string(h)} + }; + } +}; + +struct ConfInfo : public std::vector<ParticipantInfo> +{ + std::vector<std::map<std::string, std::string>> toVectorMapStringString() const; +}; + using ParticipantSet = std::set<std::string>; -class Conference : public Recordable { +class Conference + : public Recordable + , public std::enable_shared_from_this<Conference> + { public: enum class State { ACTIVE_ATTACHED, @@ -136,16 +183,39 @@ public: void setActiveParticipant(const std::string &participant_id); + + void attachVideo(Observable<std::shared_ptr<MediaFrame>>* frame, const std::string& callId); + void detachVideo(Observable<std::shared_ptr<MediaFrame>>* frame); + #ifdef ENABLE_VIDEO std::shared_ptr<video::VideoMixer> getVideoMixer(); std::string getVideoInput() const { return mediaInput_; } #endif + std::vector<std::map<std::string, std::string>> + getConferenceInfos() const + { + std::lock_guard<std::mutex> lk(confInfoMutex_); + return confInfo_.toVectorMapStringString(); + } + private: + + std::weak_ptr<Conference> weak() { + return std::static_pointer_cast<Conference>(shared_from_this()); + } + std::string id_; State confState_ {State::ACTIVE_ATTACHED}; ParticipantSet participants_; + mutable std::mutex confInfoMutex_ {}; + mutable ConfInfo confInfo_ {}; + void sendConferenceInfos(); + // We need to convert call to frame + std::mutex videoToCallMtx_; + std::map<Observable<std::shared_ptr<MediaFrame>>*, std::string> videoToCall_ {}; + #ifdef ENABLE_VIDEO std::string mediaInput_ {}; std::shared_ptr<video::VideoMixer> videoMixer_; diff --git a/src/dring/callmanager_interface.h b/src/dring/callmanager_interface.h index 2c68d049a2269d412b04ba2a05881b3dc6276472..34ac9001fc46ca26b86f02b84b2a3a9a2111cd7c 100644 --- a/src/dring/callmanager_interface.h +++ b/src/dring/callmanager_interface.h @@ -73,6 +73,7 @@ DRING_PUBLIC std::vector<std::string> getParticipantList(const std::string& conf DRING_PUBLIC std::vector<std::string> getDisplayNames(const std::string& confID); DRING_PUBLIC std::string getConferenceId(const std::string& callID); DRING_PUBLIC std::map<std::string, std::string> getConferenceDetails(const std::string& callID); +DRING_PUBLIC std::vector<std::map<std::string, std::string>> getConferenceInfos(const std::string& confId); /* Statistic related methods */ DRING_PUBLIC void startSmartInfo(uint32_t refreshTimeMs); @@ -191,6 +192,10 @@ struct DRING_PUBLIC CallSignal { constexpr static const char* name = "ConnectionUpdate"; using cb_type = void(const std::string&, int); }; + struct DRING_PUBLIC OnConferenceInfosUpdated { + constexpr static const char* name = "OnConferenceInfosUpdated"; + using cb_type = void(const std::string&, const std::vector<std::map<std::string, std::string>>&); + }; }; } // namespace DRing diff --git a/src/jamidht/jamiaccount.h b/src/jamidht/jamiaccount.h index 6ab4cd1270f77e9f59802724868880b2bda3b22f..dddbf5e0d20893527e691a663c0691c90f4dc5ce 100644 --- a/src/jamidht/jamiaccount.h +++ b/src/jamidht/jamiaccount.h @@ -190,7 +190,7 @@ public: * of the host on which the UA is running, since these are not logical * names." */ - std::string getFromUri() const; + std::string getFromUri() const override; /** * This method adds the correct scheme, hostname and append diff --git a/src/manager.cpp b/src/manager.cpp index 66e875b189e49c1f050d37c9f25d8fbc0cb4ad30..fbb3e147a2065ee59ceffcb953454fc348cbcdc2 100644 --- a/src/manager.cpp +++ b/src/manager.cpp @@ -1943,8 +1943,9 @@ Manager::incomingMessage(const std::string& callID, // in case of a conference we must notify client using conference id emitSignal<DRing::CallSignal::IncomingMessage>(conf->getConfID(), from, messages); - } else + } else { emitSignal<DRing::CallSignal::IncomingMessage>(callID, from, messages); + } } void @@ -2912,6 +2913,16 @@ Manager::getCallList() const return results; } +std::vector<std::map<std::string, std::string>> +Manager::getConferenceInfos(const std::string& confId) const +{ + if (auto conf = getConferenceFromID(confId)) + return conf->getConferenceInfos(); + else if (auto call = getCallFromCallID(confId)) + return call->getConferenceInfos(); + return {}; +} + std::map<std::string, std::string> Manager::getConferenceDetails(const std::string& confID) const { diff --git a/src/manager.h b/src/manager.h index 44f5c527483f0723a50b271f966ea31db4df2b64..e73caf1f3ee2f649ebe811d89e4bf14245a22076 100644 --- a/src/manager.h +++ b/src/manager.h @@ -458,6 +458,13 @@ class DRING_TESTABLE Manager { */ std::vector<std::string> getCallList() const; + /** + * Get conferences informations (participant list + rendered positions in the frame) + * @param confId + * @return {{"uri":"xxx", "x":"0", "y":"0", "w":"0", "h":"0"}...} + */ + std::vector<std::map<std::string, std::string>> getConferenceInfos(const std::string& confId) const; + /** * Retrieve details about a given call * @param callID The account identifier diff --git a/src/media/video/video_mixer.cpp b/src/media/video/video_mixer.cpp index 4cc5b5b708c60d4596103cb6970c5823b926cefa..9e7b10ca6dc707101e5665274f6d73b931e5819e 100644 --- a/src/media/video/video_mixer.cpp +++ b/src/media/video/video_mixer.cpp @@ -36,6 +36,8 @@ #include <cmath> #include <unistd.h> +#include <opendht/thread_pool.h> + extern "C" { #include <libavutil/display.h> } @@ -52,6 +54,12 @@ struct VideoMixer::VideoMixerSource { std::lock_guard<std::mutex> lock(mutex_); render_frame.swap(other); } + + // Current render informations + int x {}; + int y {}; + int w {}; + int h {}; private: std::mutex mutex_; }; @@ -123,6 +131,7 @@ void VideoMixer::setActiveParticipant(Observable<std::shared_ptr<MediaFrame>>* ob) { activeSource_ = ob; + layoutUpdated_ += 1; } void @@ -133,6 +142,7 @@ VideoMixer::attached(Observable<std::shared_ptr<MediaFrame>>* ob) auto src = std::unique_ptr<VideoMixerSource>(new VideoMixerSource); src->source = ob; sources_.emplace_back(std::move(src)); + layoutUpdated_ += 1; } void @@ -148,6 +158,7 @@ VideoMixer::detached(Observable<std::shared_ptr<MediaFrame>>* ob) activeSource_ = nullptr; } sources_.remove(x); + layoutUpdated_ += 1; break; } } @@ -197,7 +208,9 @@ VideoMixer::process() int i = 0; bool activeFound = false; - for (const auto& x : sources_) { + bool needsUpdate = layoutUpdated_ > 0; + bool successfullyRendered = true; + for (auto& x : sources_) { /* thread stop pending? */ if (!loop_.isRunning()) return; @@ -227,24 +240,49 @@ VideoMixer::process() } if (input) - render_frame(output, *input, x, wantedIndex); + successfullyRendered &= render_frame(output, *input, x, wantedIndex, needsUpdate); + else + successfullyRendered = false; x->atomic_swap_render(input); + } else if (needsUpdate) { + x->x = 0; + x->y = 0; + x->w = 0; + x->h = 0; } ++i; } + if (needsUpdate and successfullyRendered) { + layoutUpdated_ -= 1; + if (layoutUpdated_ == 0) { + std::vector<SourceInfo> sourcesInfo; + sourcesInfo.reserve(sources_.size()); + for (auto& x : sources_) { + sourcesInfo.emplace_back(SourceInfo { + x->source, + x->x, + x->y, + x->w, + x->h + }); + } + if (onSourcesUpdated_) + (onSourcesUpdated_)(std::move(sourcesInfo)); + } + } } publishFrame(); } -void +bool VideoMixer::render_frame(VideoFrame& output, const VideoFrame& input, - const std::unique_ptr<VideoMixerSource>& source, int index) + std::unique_ptr<VideoMixerSource>& source, int index, bool needsUpdate) { if (!width_ or !height_ or !input.pointer() or input.pointer()->format == -1) - return; + return false; #ifdef RING_ACCEL std::shared_ptr<VideoFrame> frame { HardwareAccel::transferToMainMemory(input, AV_PIX_FMT_NV12) }; @@ -252,28 +290,44 @@ VideoMixer::render_frame(VideoFrame& output, const VideoFrame& input, std::shared_ptr<VideoFrame> frame = input; #endif - const int n = currentLayout_ == Layout::ONE_BIG? 1 : sources_.size(); - const int zoom = currentLayout_ == Layout::ONE_BIG_WITH_SMALL? std::max(6,n) : ceil(sqrt(n)); - int cell_width = width_ / zoom; - int cell_height = height_ / zoom; - if (currentLayout_ == Layout::ONE_BIG_WITH_SMALL && index == 0) { - // In ONE_BIG_WITH_SMALL, the first line at the top is the previews - // The rest is the active source - cell_width = width_; - cell_height = height_ - cell_height; - } - int xoff = (index % zoom) * cell_width; - int yoff = (index / zoom) * cell_height; - if (currentLayout_ == Layout::ONE_BIG_WITH_SMALL) { - if (index == 0) { - xoff = 0; - yoff = height_ / zoom; // First line height + int cell_width, cell_height, xoff, yoff; + if (not needsUpdate) { + cell_width = source->w; + cell_height = source->h; + xoff = source->x; + yoff = source->y; + } else { + const int n = currentLayout_ == Layout::ONE_BIG? 1 : sources_.size(); + const int zoom = currentLayout_ == Layout::ONE_BIG_WITH_SMALL? std::max(6,n) : ceil(sqrt(n)); + if (currentLayout_ == Layout::ONE_BIG_WITH_SMALL && index == 0) { + // In ONE_BIG_WITH_SMALL, the first line at the top is the previews + // The rest is the active source + cell_width = width_; + cell_height = height_ - height_ / zoom; + } else { + cell_width = width_ / zoom; + cell_height = height_ / zoom; + } + if (currentLayout_ == Layout::ONE_BIG_WITH_SMALL) { + if (index == 0) { + xoff = 0; + yoff = height_ / zoom; // First line height + } else { + xoff = (index-1) * cell_width; + // Show sources in center + xoff += (width_ - (n - 1) * cell_width) / 2; + yoff = 0; + } } else { - xoff = (index-1) * cell_width; - // Show sources in center - xoff += (width_ - (n - 1) * cell_width) / 2; - yoff = 0; + xoff = (index % zoom) * cell_width; + yoff = (index / zoom) * cell_height; } + + // Update source's cache + source->w = cell_width; + source->h = cell_height; + source->x = xoff; + source->y = yoff; } AVFrameSideData* sideData = av_frame_get_side_data(frame->pointer(), AV_FRAME_DATA_DISPLAYMATRIX); @@ -295,6 +349,7 @@ VideoMixer::render_frame(VideoFrame& output, const VideoFrame& input, } scaler_.scale_and_pad(*frame, output, xoff, yoff, cell_width, cell_height, true); + return true; } void @@ -312,6 +367,7 @@ VideoMixer::setParameters(int width, int height, AVPixelFormat format) libav_utils::fillWithBlack(previous_p->pointer()); start_sink(); + layoutUpdated_ += 1; } void diff --git a/src/media/video/video_mixer.h b/src/media/video/video_mixer.h index e75d6f82f52a74b0a849083a957b1b82344f6c70..3dd0d90ab5326cfa91b7b81b9f573262f8d3ed1f 100644 --- a/src/media/video/video_mixer.h +++ b/src/media/video/video_mixer.h @@ -35,6 +35,15 @@ namespace jami { namespace video { class SinkClient; +struct SourceInfo { + Observable<std::shared_ptr<MediaFrame>>* source; + int x; + int y; + int w; + int h; +}; +using OnSourcesUpdatedCb = std::function<void(const std::vector<SourceInfo>&&)>; + enum class Layout { GRID, @@ -68,15 +77,19 @@ public: void setVideoLayout(Layout newLayout) { currentLayout_ = newLayout; + layoutUpdated_ += 1; + } + + void setOnSourcesUpdated(OnSourcesUpdatedCb&& cb) { + onSourcesUpdated_ = std::move(cb); } private: NON_COPYABLE(VideoMixer); - struct VideoMixerSource; - void render_frame(VideoFrame& output, const VideoFrame& input, - const std::unique_ptr<VideoMixerSource>& source, int index); + bool render_frame(VideoFrame& output, const VideoFrame& input, + std::unique_ptr<VideoMixerSource>& source, int index, bool needsUpdate); void start_sink(); void stop_sink(); @@ -100,6 +113,9 @@ private: Layout currentLayout_ {Layout::GRID}; Observable<std::shared_ptr<MediaFrame>>* activeSource_ {nullptr}; std::list<std::unique_ptr<VideoMixerSource>> sources_; + + std::atomic_int layoutUpdated_ {0}; + OnSourcesUpdatedCb onSourcesUpdated_ {}; }; }} // namespace jami::video diff --git a/src/media/video/video_rtp_session.cpp b/src/media/video/video_rtp_session.cpp index e770d00ca3288f5c9a89f95b1a979dbf451526b1..c67371fbaf4d1cbfebb2eb572eb68d2a8b6fdd77 100644 --- a/src/media/video/video_rtp_session.cpp +++ b/src/media/video/video_rtp_session.cpp @@ -306,18 +306,18 @@ VideoRtpSession::setupConferenceVideoPipeline(Conference& conference) JAMI_DBG("[call:%s] Setup video pipeline on conference %s", callID_.c_str(), conference.getConfID().c_str()); videoMixer_ = conference.getVideoMixer(); - if (sender_) { // Swap sender from local video to conference video mixer if (videoLocal_) videoLocal_->detach(sender_.get()); - videoMixer_->attach(sender_.get()); + if (videoMixer_) + videoMixer_->attach(sender_.get()); } else JAMI_WARN("[call:%s] no sender", callID_.c_str()); if (receiveThread_) { receiveThread_->enterConference(); - receiveThread_->attach(videoMixer_.get()); + conference.attachVideo(receiveThread_.get(), callID_); } else JAMI_WARN("[call:%s] no receiver", callID_.c_str()); } @@ -366,7 +366,7 @@ void VideoRtpSession::exitConference() videoMixer_->detach(sender_.get()); if (receiveThread_) { - receiveThread_->detach(videoMixer_.get()); + conference_->detachVideo(receiveThread_.get()); receiveThread_->exitConference(); } diff --git a/src/sip/sipaccount.h b/src/sip/sipaccount.h index 0641927a9b4748932ad147210b802876c310de92..aa0e11bc469100550cf9c5d350a8e3bb0cf15cc8 100644 --- a/src/sip/sipaccount.h +++ b/src/sip/sipaccount.h @@ -360,7 +360,7 @@ class SIPAccount : public SIPAccountBase { * of the host on which the UA is running, since these are not logical * names." */ - std::string getFromUri() const; + std::string getFromUri() const override; /** * This method adds the correct scheme, hostname and append