diff --git a/configure.ac b/configure.ac index 74e0ed87c..e1e4a9623 100644 --- a/configure.ac +++ b/configure.ac @@ -2,7 +2,7 @@ dnl Jami - configure.ac dnl Process this file with autoconf to produce a configure script. AC_PREREQ([2.69]) -AC_INIT([Jami Daemon],[11.0.0],[jami@gnu.org],[jami]) +AC_INIT([Jami Daemon],[11.0.1],[jami@gnu.org],[jami]) dnl Clear the implicit flags that default to '-g -O2', otherwise they dnl take precedence over the values we set via the diff --git a/meson.build b/meson.build index a2ac3f602..4be3ce4b9 100644 --- a/meson.build +++ b/meson.build @@ -1,5 +1,5 @@ project('jami-daemon', ['c', 'cpp'], - version: '11.0.0', + version: '11.0.1', license: 'GPL3+', default_options: ['cpp_std=gnu++17', 'buildtype=debugoptimized'], meson_version:'>= 0.56' diff --git a/src/conference.cpp b/src/conference.cpp index eac0925df..81911aa21 100644 --- a/src/conference.cpp +++ b/src/conference.cpp @@ -772,6 +772,7 @@ Conference::createSinks(const ConfInfo& infos) void Conference::attachVideo(Observable>* frame, const std::string& callId) { + JAMI_DBG("[conf:%s] attaching video of call %s", id_.c_str(), callId.c_str()); std::lock_guard lk(videoToCallMtx_); videoToCall_.emplace(frame, callId); frame->attach(videoMixer_.get()); @@ -783,6 +784,7 @@ Conference::detachVideo(Observable>* frame) std::lock_guard lk(videoToCallMtx_); auto it = videoToCall_.find(frame); if (it != videoToCall_.end()) { + JAMI_DBG("[conf:%s] detaching video of call %s", id_.c_str(), it->second.c_str()); it->first->detach(videoMixer_.get()); videoToCall_.erase(it); } diff --git a/src/ice_socket.h b/src/ice_socket.h index bd6e0d218..c69797c5c 100644 --- a/src/ice_socket.h +++ b/src/ice_socket.h @@ -52,6 +52,7 @@ public: void setOnRecv(IceRecvCb cb); uint16_t getTransportOverhead(); void setDefaultRemoteAddress(const IpAddr& addr); + int getCompId() const { return compId_; }; }; /// ICE transport as a GenericSocket. diff --git a/src/ice_transport.cpp b/src/ice_transport.cpp index aa67adc74..5ce663745 100644 --- a/src/ice_transport.cpp +++ b/src/ice_transport.cpp @@ -167,7 +167,7 @@ public: std::mutex mutex; std::condition_variable cv; std::deque queue; - IceRecvCb cb; + IceRecvCb recvCb; }; // NOTE: Component IDs start from 1, while these three vectors @@ -1104,8 +1104,8 @@ IceTransport::Impl::onReceiveData(unsigned comp_id, void* pkt, pj_size_t size) auto& io = compIO_[comp_id - 1]; std::lock_guard lk(io.mutex); - if (io.cb) { - io.cb((uint8_t*) pkt, size); + if (io.recvCb) { + io.recvCb((uint8_t*) pkt, size); return; } } @@ -1654,12 +1654,12 @@ IceTransport::setOnRecv(unsigned compId, IceRecvCb cb) auto& io = pimpl_->compIO_[compId - 1]; std::lock_guard lk(io.mutex); - io.cb = std::move(cb); + io.recvCb = std::move(cb); - if (io.cb) { + if (io.recvCb) { // Flush existing queue using the callback for (const auto& packet : io.queue) - io.cb((uint8_t*) packet.data.data(), packet.data.size()); + io.recvCb((uint8_t*) packet.data.data(), packet.data.size()); io.queue.clear(); } } diff --git a/src/manager.cpp b/src/manager.cpp index c410ce2a4..d10c88fdd 100644 --- a/src/manager.cpp +++ b/src/manager.cpp @@ -1271,6 +1271,8 @@ Manager::holdConference(const std::string& accountId, const std::string& confId) bool Manager::unHoldConference(const std::string& accountId, const std::string& confId) { + JAMI_DBG("[conf:%s] un-holding conference", confId.c_str()); + if (const auto account = getAccount(accountId)) { if (auto conf = account->getConference(confId)) { // Unhold conf only if it was in hold state otherwise... diff --git a/src/media/audio/audio_receive_thread.cpp b/src/media/audio/audio_receive_thread.cpp index fb94a0ba9..76b59c32f 100644 --- a/src/media/audio/audio_receive_thread.cpp +++ b/src/media/audio/audio_receive_thread.cpp @@ -140,9 +140,15 @@ AudioReceiveThread::getInfo() const } void -AudioReceiveThread::startLoop() +AudioReceiveThread::startReceiver() { loop_.start(); } +void +AudioReceiveThread::stopReceiver() +{ + loop_.stop(); +} + }; // namespace jami diff --git a/src/media/audio/audio_receive_thread.h b/src/media/audio/audio_receive_thread.h index 4858c1e49..60f3b9e77 100644 --- a/src/media/audio/audio_receive_thread.h +++ b/src/media/audio/audio_receive_thread.h @@ -51,10 +51,13 @@ public: MediaStream getInfo() const; void addIOContext(SocketPair& socketPair); - void startLoop(); + void startReceiver(); + void stopReceiver(); void setSuccessfulSetupCb(const std::function& cb) - { onSuccessfulSetup_ = cb; } + { + onSuccessfulSetup_ = cb; + } private: NON_COPYABLE(AudioReceiveThread); @@ -64,9 +67,6 @@ private: static int interruptCb(void* ctx); static int readFunction(void* opaque, uint8_t* buf, int buf_size); - void openDecoder(); - bool decodeFrame(); - /*-----------------------------------------------------------------*/ /* These variables should be used in thread (i.e. process()) only! */ /*-----------------------------------------------------------------*/ diff --git a/src/media/audio/audio_rtp_session.cpp b/src/media/audio/audio_rtp_session.cpp index dc0fd07c0..49bca44e7 100644 --- a/src/media/audio/audio_rtp_session.cpp +++ b/src/media/audio/audio_rtp_session.cpp @@ -147,6 +147,9 @@ AudioRtpSession::restartSender() void AudioRtpSession::startReceiver() { + if (socketPair_) + socketPair_->setReadBlockingMode(true); + if (not receive_.enabled or receive_.onHold) { JAMI_WARN("Audio receiving disabled"); receiveThread_.reset(); @@ -163,7 +166,7 @@ AudioRtpSession::startReceiver() mtu_)); receiveThread_->addIOContext(*socketPair_); receiveThread_->setSuccessfulSetupCb(onSuccessfulSetup_); - receiveThread_->startLoop(); + receiveThread_->startReceiver(); } void @@ -212,6 +215,16 @@ AudioRtpSession::stop() { std::lock_guard lock(mutex_); + JAMI_DBG("[%p] Stopping receiver", this); + + if (not receiveThread_) + return; + + if (socketPair_) + socketPair_->setReadBlockingMode(false); + + receiveThread_->stopReceiver(); + if (audioInput_) audioInput_->detach(sender_.get()); @@ -227,12 +240,12 @@ AudioRtpSession::stop() } void -AudioRtpSession::setMuted(bool isMuted) +AudioRtpSession::setMuted(bool muted, Direction) { std::lock_guard lock(mutex_); - muteState_ = isMuted; + muteState_ = muted; if (audioInput_) - audioInput_->setMuted(isMuted); + audioInput_->setMuted(muted); } bool diff --git a/src/media/audio/audio_rtp_session.h b/src/media/audio/audio_rtp_session.h index d0e3d4480..fb04899c1 100644 --- a/src/media/audio/audio_rtp_session.h +++ b/src/media/audio/audio_rtp_session.h @@ -56,7 +56,7 @@ public: void start(std::unique_ptr rtp_sock, std::unique_ptr rtcp_sock) override; void restartSender() override; void stop() override; - void setMuted(bool isMuted) override; + void setMuted(bool muted, Direction dir = Direction::SEND) override; void initRecorder(std::shared_ptr& rec) override; void deinitRecorder(std::shared_ptr& rec) override; @@ -78,7 +78,7 @@ private: std::shared_ptr audioInput_; std::shared_ptr ringbuffer_; uint16_t initSeqVal_ {0}; - bool muteState_ = false; + bool muteState_ {false}; unsigned packetLoss_ {10}; DeviceParams localAudioParams_; diff --git a/src/media/media_decoder.cpp b/src/media/media_decoder.cpp index 2507d7b5f..a4a1d7107 100644 --- a/src/media/media_decoder.cpp +++ b/src/media/media_decoder.cpp @@ -66,6 +66,27 @@ MediaDemuxer::~MediaDemuxer() av_dict_free(&options_); } +const char* +MediaDemuxer::getStatusStr(Status status) +{ + switch (status) { + case Status::Success: + return "Success"; + case Status::EndOfFile: + return "End of file"; + case Status::ReadBufferOverflow: + return "Read overflow"; + case Status::ReadError: + return "Read error"; + case Status::FallBack: + return "Fallback"; + case Status::RestartRequired: + return "Restart required"; + default: + return "Undefined"; + } +} + int MediaDemuxer::openInput(const DeviceParams& params) { @@ -369,7 +390,11 @@ MediaDemuxer::decode() } else if (ret == AVERROR(EACCES)) { return Status::RestartRequired; } else if (ret < 0) { - JAMI_ERR("Couldn't read frame: %s\n", libav_utils::getError(ret).c_str()); + auto media = inputCtx_->streams[0]->codecpar->codec_type; + const auto type = media == AVMediaType::AVMEDIA_TYPE_AUDIO + ? "AUDIO" + : (media == AVMediaType::AVMEDIA_TYPE_VIDEO ? "VIDEO" : "UNSUPPORTED"); + JAMI_ERR("Couldn't read [%s] frame: %s\n", type, libav_utils::getError(ret).c_str()); return Status::ReadError; } @@ -462,9 +487,15 @@ MediaDecoder::setup(AVMediaType type) { demuxer_->findStreamInfo(); auto stream = demuxer_->selectStream(type); - if (stream < 0) + if (stream < 0) { + JAMI_ERR("No stream found for type %i", static_cast(type)); return -1; + } avStream_ = demuxer_->getStream(stream); + if (avStream_ == nullptr) { + JAMI_ERR("No stream found at index %i", stream); + return -1; + } demuxer_->setStreamCallback(stream, [this](AVPacket& packet) { return decode(packet); }); return setupStream(); } diff --git a/src/media/media_decoder.h b/src/media/media_decoder.h index 62ae4329f..7c87c57ef 100644 --- a/src/media/media_decoder.h +++ b/src/media/media_decoder.h @@ -96,6 +96,8 @@ public: RestartRequired }; + static const char* getStatusStr(Status status); + enum class CurrentState { Demuxing, Finished }; using StreamCallback = std::function; @@ -122,8 +124,10 @@ public: AVStream* getStream(unsigned stream) { - if (stream >= inputCtx_->nb_streams) - throw std::invalid_argument("Invalid stream index"); + if (stream >= inputCtx_->nb_streams) { + JAMI_ERR("Stream index is out of range: %u", stream); + return {}; + } return inputCtx_->streams[stream]; } diff --git a/src/media/media_player.cpp b/src/media/media_player.cpp index 831beb79a..2d719b39c 100644 --- a/src/media/media_player.cpp +++ b/src/media/media_player.cpp @@ -153,6 +153,9 @@ MediaPlayer::process() case MediaDemuxer::Status::ReadBufferOverflow: readBufferOverflow_ = true; break; + case MediaDemuxer::Status::RestartRequired: + default: + break; } } diff --git a/src/media/rtp_session.h b/src/media/rtp_session.h index 6d52f655d..5eedaef20 100644 --- a/src/media/rtp_session.h +++ b/src/media/rtp_session.h @@ -39,6 +39,9 @@ class MediaRecorder; class RtpSession { public: + // Media direction + enum class Direction { SEND, RECV }; + RtpSession(const std::string& callID, MediaType type) : callID_(callID) , mediaType_(type) @@ -51,7 +54,8 @@ public: void setMediaSource(const std::string& resource) { input_ = resource; } const std::string& getInput() const { return input_; } MediaType getMediaType() const { return mediaType_; }; - virtual void setMuted(bool mute) { muteState_ = mute; }; + virtual void setMuted(bool mute, Direction dir = Direction::SEND) = 0; + virtual void updateMedia(const MediaDescription& send, const MediaDescription& receive) { send_ = send; @@ -83,7 +87,6 @@ protected: MediaDescription send_; MediaDescription receive_; uint16_t mtu_; - bool muteState_ {false}; std::function onSuccessfulSetup_; diff --git a/src/media/socket_pair.cpp b/src/media/socket_pair.cpp index 266e36ef3..9ca32d367 100644 --- a/src/media/socket_pair.cpp +++ b/src/media/socket_pair.cpp @@ -189,6 +189,11 @@ SocketPair::SocketPair(std::unique_ptr rtp_sock, std::unique_ptrgetCompId(), + rtcp_sock_->getCompId()); + rtp_sock_->setOnRecv([this](uint8_t* buf, size_t len) { std::lock_guard l(dataBuffMutex_); rtpDataBuff_.emplace_back(buf, buf + len); @@ -207,6 +212,7 @@ SocketPair::~SocketPair() { interrupt(); closeSockets(); + JAMI_DBG("[%p] Instance destroyed", this); } bool @@ -214,7 +220,8 @@ SocketPair::waitForRTCP(std::chrono::seconds interval) { std::unique_lock lock(rtcpInfo_mutex_); return cvRtcpPacketReadyToRead_.wait_for(lock, interval, [this] { - return interrupted_ or not listRtcpRRHeader_.empty() or not listRtcpREMBHeader_.empty(); + return interrupted_ or not listRtcpRRHeader_.empty() or not listRtcpREMBHeader_.empty() + or not readBlockingMode_; }); } @@ -289,6 +296,7 @@ SocketPair::createSRTP(const char* out_suite, void SocketPair::interrupt() { + JAMI_WARN("[%p] Interrupting RTP sockets", this); interrupted_ = true; if (rtp_sock_) rtp_sock_->setOnRecv(nullptr); @@ -298,6 +306,15 @@ SocketPair::interrupt() cvRtcpPacketReadyToRead_.notify_all(); } +void +SocketPair::setReadBlockingMode(bool block) +{ + JAMI_DBG("[%p] Read operations in blocking mode [%s]", this, block ? "YES" : "NO"); + readBlockingMode_ = block; + cv_.notify_all(); + cvRtcpPacketReadyToRead_.notify_all(); +} + void SocketPair::stopSendOp(bool state) { @@ -316,6 +333,8 @@ SocketPair::closeSockets() void SocketPair::openSockets(const char* uri, int local_rtp_port) { + JAMI_DBG("Creating rtp socket for uri %s on port %d", uri, local_rtp_port); + char hostname[256]; char path[1024]; int dst_rtp_port; @@ -334,6 +353,7 @@ SocketPair::openSockets(const char* uri, int local_rtp_port) if ((rtpHandle_ = udp_socket_create(rtpDestAddr_.getFamily(), local_rtp_port)) == -1 or (rtcpHandle_ = udp_socket_create(rtcpDestAddr_.getFamily(), local_rtcp_port)) == -1) { closeSockets(); + JAMI_ERR("[%p] Sockets creation failed", this); throw std::runtime_error("Sockets creation failed"); } @@ -380,6 +400,10 @@ SocketPair::waitForData() return -1; } + if (not readBlockingMode_) { + return 0; + } + // work with system socket struct pollfd p[2] = {{rtpHandle_, POLLIN, 0}, {rtcpHandle_, POLLIN, 0}}; ret = poll(p, 2, NET_POLL_TIMEOUT); @@ -399,7 +423,8 @@ SocketPair::waitForData() { std::unique_lock lk(dataBuffMutex_); cv_.wait(lk, [this] { - return interrupted_ or not rtpDataBuff_.empty() or not rtcpDataBuff_.empty(); + return interrupted_ or not rtpDataBuff_.empty() or not rtcpDataBuff_.empty() + or not readBlockingMode_; }); } diff --git a/src/media/socket_pair.h b/src/media/socket_pair.h index 7c8c4ef00..037513282 100644 --- a/src/media/socket_pair.h +++ b/src/media/socket_pair.h @@ -138,6 +138,13 @@ public: void interrupt(); + // Set the read blocking mode. + // By default, the read operation will block until data is available + // on the socket. This method allows to switch to unblocking mode + // if to stop the receiver thread to exit and there is no more data + // to read (if the peer mutes/stops the media/RTP stream). + void setReadBlockingMode(bool blocking); + MediaIOHandle* createIOContext(const uint16_t mtu); void openSockets(const char* uri, int localPort); @@ -208,6 +215,9 @@ private: IpAddr rtpDestAddr_; IpAddr rtcpDestAddr_; std::atomic_bool interrupted_ {false}; + // Read operations are blocking. This will allow unblocking the + // receiver thread if the peer stops/mutes the media (RTP) + std::atomic_bool readBlockingMode_ {false}; std::atomic_bool noWrite_ {false}; std::unique_ptr srtpContext_; std::function packetLossCallback_; diff --git a/src/media/video/video_mixer.cpp b/src/media/video/video_mixer.cpp index 0e3867531..f21dd169f 100644 --- a/src/media/video/video_mixer.cpp +++ b/src/media/video/video_mixer.cpp @@ -204,7 +204,9 @@ VideoMixer::attached(Observable>* ob) auto src = std::unique_ptr(new VideoMixerSource); src->render_frame = std::make_shared(); src->source = ob; + JAMI_DBG("Add new source [%p]", src.get()); sources_.emplace_back(std::move(src)); + JAMI_DBG("Total sources: %lu", sources_.size()); updateLayout(); } @@ -221,7 +223,9 @@ VideoMixer::detached(Observable>* ob) activeSource_ = videoLocalSecondary_ ? videoLocalSecondary_.get() : videoLocal_.get(); } + JAMI_DBG("Remove source [%p]", x.get()); sources_.remove(x); + JAMI_DBG("Total sources: %lu", sources_.size()); updateLayout(); break; } diff --git a/src/media/video/video_receive_thread.cpp b/src/media/video/video_receive_thread.cpp index a95ba0cc7..c1025641c 100644 --- a/src/media/video/video_receive_thread.cpp +++ b/src/media/video/video_receive_thread.cpp @@ -57,24 +57,40 @@ VideoReceiveThread::VideoReceiveThread(const std::string& id, , loop_(std::bind(&VideoReceiveThread::setup, this), std::bind(&VideoReceiveThread::decodeFrame, this), std::bind(&VideoReceiveThread::cleanup, this)) -{} +{ + JAMI_DBG("[%p] Instance created", this); +} VideoReceiveThread::~VideoReceiveThread() { - loop_.join(); + JAMI_DBG("[%p] Instance destroyed", this); } void VideoReceiveThread::startLoop() { + JAMI_DBG("[%p] Starting receiver's loop", this); loop_.start(); } +void +VideoReceiveThread::stopLoop() +{ + if (loop_.isStopping()) + return; + JAMI_DBG("[%p] Stopping receiver's loop and waiting for the thread to exit ...", this); + loop_.stop(); + loop_.join(); + JAMI_DBG("[%p] Receiver's thread exited", this); +} + // We do this setup here instead of the constructor because we don't want the // main thread to block while this executes, so it happens in the video thread. bool VideoReceiveThread::setup() { + JAMI_DBG("[%p] Setupping video receiver", this); + videoDecoder_.reset(new MediaDecoder([this](const std::shared_ptr& frame) mutable { if (auto displayMatrix = displayMatrix_) av_frame_new_side_data_from_buf(frame->pointer(), @@ -132,6 +148,8 @@ VideoReceiveThread::setup() void VideoReceiveThread::cleanup() { + JAMI_DBG("[%p] Stopping receiver", this); + detach(sink_.get()); sink_->stop(); @@ -169,12 +187,20 @@ VideoReceiveThread::addIOContext(SocketPair& socketPair) void VideoReceiveThread::decodeFrame() { - if (!configureVideoOutput()) { + if (not loop_.isRunning()) return; + + if (not isVideoConfigured_) { + if (!configureVideoOutput()) { + JAMI_ERR("[%p] Failed to configure video output", this); + return; + } else { + JAMI_DBG("[%p] Decoder configured, starting decoding", this); + } } auto status = videoDecoder_->decode(); if (status == MediaDemuxer::Status::EndOfFile || status == MediaDemuxer::Status::ReadError) { - loop_.stop(); + JAMI_ERR("[%p] Decoding error: %s", this, MediaDemuxer::getStatusStr(status)); } if (status == MediaDemuxer::Status::FallBack) { if (keyFrameRequestCallback_) @@ -185,15 +211,18 @@ VideoReceiveThread::decodeFrame() bool VideoReceiveThread::configureVideoOutput() { - if (isVideoConfigured_) { - return true; - } - if (!loop_.isRunning()) { + assert(not isVideoConfigured_); + + JAMI_DBG("[%p] Configuring video output", this); + + if (not loop_.isRunning()) { + JAMI_WARN("[%p] Can not configure video output, the loop is not running!", this); return false; } - if (videoDecoder_->setupVideo()) { + + if (videoDecoder_->setupVideo() < 0) { JAMI_ERR("decoder IO startup failed"); - loop_.stop(); + stopLoop(); return false; } @@ -205,7 +234,7 @@ VideoReceiveThread::configureVideoOutput() if (not sink_->start()) { JAMI_ERR("RX: sink startup failed"); - loop_.stop(); + stopLoop(); return false; } @@ -220,13 +249,15 @@ VideoReceiveThread::configureVideoOutput() if (onSuccessfulSetup_) onSuccessfulSetup_(MEDIA_VIDEO, 1); - isVideoConfigured_ = true; - return true; + + return isVideoConfigured_ = true; } void VideoReceiveThread::stopSink() { + JAMI_DBG("[%p] Stopping sink", this); + if (!loop_.isRunning()) return; @@ -237,6 +268,8 @@ VideoReceiveThread::stopSink() void VideoReceiveThread::startSink() { + JAMI_DBG("[%p] Starting sink", this); + if (!loop_.isRunning()) return; diff --git a/src/media/video/video_receive_thread.h b/src/media/video/video_receive_thread.h index 3d68c717f..0f40bd1de 100644 --- a/src/media/video/video_receive_thread.h +++ b/src/media/video/video_receive_thread.h @@ -54,8 +54,10 @@ class VideoReceiveThread : public VideoGenerator public: VideoReceiveThread(const std::string& id, bool useSink, const std::string& sdp, uint16_t mtu); ~VideoReceiveThread(); - void startLoop(); + void startLoop(); + void stopLoop(); + void decodeFrame(); void addIOContext(SocketPair& socketPair); void setRequestKeyFrameCallback(std::function cb) { @@ -94,19 +96,17 @@ private: int dstWidth_ {0}; int dstHeight_ {0}; const std::string id_; + bool useSink_; std::istringstream stream_; MediaIOHandle sdpContext_; std::unique_ptr demuxContext_; std::shared_ptr sink_; bool isVideoConfigured_ {false}; - bool useSink_; uint16_t mtu_; int rotation_ {0}; std::shared_ptr displayMatrix_; - void openDecoder(); - void decodeFrame(); static int interruptCb(void* ctx); static int readFunction(void* opaque, uint8_t* buf, int buf_size); bool configureVideoOutput(); diff --git a/src/media/video/video_rtp_session.cpp b/src/media/video/video_rtp_session.cpp index cbf23209c..86286a045 100644 --- a/src/media/video/video_rtp_session.cpp +++ b/src/media/video/video_rtp_session.cpp @@ -67,7 +67,7 @@ VideoRtpSession::VideoRtpSession(const string& callID, const DeviceParams& local { setupVideoBitrateInfo(); // reset bitrate cc = std::make_unique(); - JAMI_DBG("[%p] Video RTP session created", this); + JAMI_DBG("[%p] Video RTP session created for call %s", this, callID_.c_str()); } VideoRtpSession::~VideoRtpSession() @@ -94,9 +94,18 @@ VideoRtpSession::setRequestKeyFrameCallback(std::function cb) void VideoRtpSession::startSender() { - JAMI_DBG("Start video RTP sender: input [%s] - muted [%s]", + std::lock_guard lock(mutex_); + + JAMI_DBG("[%p] Start video RTP sender: input [%s] - muted [%s]", + this, conference_ ? "Video Mixer" : input_.c_str(), - muteState_ ? "YES" : "NO"); + send_.onHold ? "YES" : "NO"); + + if (not socketPair_) { + // Ignore if the transport is not set yet + JAMI_WARN("[%p] Transport not set yet", this); + return; + } if (send_.enabled and not send_.onHold) { if (sender_) { @@ -104,7 +113,7 @@ VideoRtpSession::startSender() videoLocal_->detach(sender_.get()); if (videoMixer_) videoMixer_->detach(sender_.get()); - JAMI_WARN("Restarting video sender"); + JAMI_WARN("[%p] Restarting video sender", this); } if (not conference_) { @@ -117,7 +126,7 @@ VideoRtpSession::startSender() && newParams.wait_for(NEWPARAMS_TIMEOUT) == std::future_status::ready) { localVideoParams_ = newParams.get(); } else { - JAMI_ERR("No valid new video parameters."); + JAMI_ERR("[%p] No valid new video parameters", this); return; } } catch (const std::exception& e) { @@ -198,15 +207,45 @@ VideoRtpSession::restartSender() return; startSender(); - setupVideoPipeline(); + + if (conference_) + setupConferenceVideoPipeline(*conference_, Direction::SEND); + else + setupVideoPipeline(); +} + +void +VideoRtpSession::stopSender() +{ + // Concurrency protection must be done by caller. + + JAMI_DBG("[%p] Stop video RTP sender: input [%s] - muted [%s]", + this, + conference_ ? "Video Mixer" : input_.c_str(), + send_.onHold ? "YES" : "NO"); + + if (sender_) { + if (videoLocal_) + videoLocal_->detach(sender_.get()); + if (videoMixer_) + videoMixer_->detach(sender_.get()); + sender_.reset(); + } + + if (socketPair_) + socketPair_->stopSendOp(); } void VideoRtpSession::startReceiver() { + // Concurrency protection must be done by caller. + + JAMI_DBG("[%p] Starting receiver", this); + if (receive_.enabled and not receive_.onHold) { if (receiveThread_) - JAMI_WARN("Restarting video receiver"); + JAMI_WARN("[%p] Already has a receiver, restarting", this); receiveThread_.reset( new VideoReceiveThread(callID_, !conference_, receive_.receiving_sdp, mtu_)); @@ -217,11 +256,37 @@ VideoRtpSession::startReceiver() receiveThread_->setRequestKeyFrameCallback([this]() { cbKeyFrameRequest_(); }); receiveThread_->setRotation(rotation_.load()); } else { - JAMI_DBG("Video receiving disabled"); - if (receiveThread_) + JAMI_DBG("[%p] Video receiver disabled", this); + if (receiveThread_ and videoMixer_) { receiveThread_->detach(videoMixer_.get()); - receiveThread_.reset(); + } } + if (socketPair_) + socketPair_->setReadBlockingMode(true); +} + +void +VideoRtpSession::stopReceiver() +{ + // Concurrency protection must be done by caller. + + JAMI_DBG("[%p] Stopping receiver", this); + + if (not receiveThread_) + return; + + if (videoMixer_) { + receiveThread_->detach(videoMixer_.get()); + } + + // We need to disable the read operation, otherwise the + // receiver thread will block since the peer stopped sending + // RTP packets. + if (socketPair_) + socketPair_->setReadBlockingMode(false); + + receiveThread_->stopLoop(); + receiveThread_->stopSink(); } void @@ -262,31 +327,32 @@ VideoRtpSession::start(std::unique_ptr rtp_sock, std::unique_ptr lock(mutex_); - if (videoLocal_) - videoLocal_->detach(sender_.get()); - if (videoMixer_) { - videoMixer_->detach(sender_.get()); - if (receiveThread_) - receiveThread_->detach(videoMixer_.get()); - } - - if (socketPair_) - socketPair_->interrupt(); + stopSender(); + stopReceiver(); rtcpCheckerThread_.join(); @@ -297,12 +363,48 @@ VideoRtpSession::stop() videoBitrateInfo_.videoBitrateCurrent = SystemCodecInfo::DEFAULT_VIDEO_BITRATE; storeVideoBitrateInfo(); - receiveThread_.reset(); - sender_.reset(); + if (socketPair_) + socketPair_->interrupt(); socketPair_.reset(); videoLocal_.reset(); } +void +VideoRtpSession::setMuted(bool mute, Direction dir) +{ + std::lock_guard lock(mutex_); + + // Sender + if (dir == Direction::SEND) { + if (send_.onHold == mute) { + JAMI_DBG("[%p] Local already %s", this, mute ? "muted" : "un-muted"); + return; + } + + if ((send_.onHold = mute)) { + stopSender(); + } else { + restartSender(); + } + return; + } + + // Receiver + if (receive_.onHold == mute) { + JAMI_DBG("[%p] Remote already %s", this, mute ? "muted" : "un-muted"); + return; + } + + if ((receive_.onHold = mute)) { + stopReceiver(); + } else { + startReceiver(); + if (conference_ and not receive_.onHold) { + setupConferenceVideoPipeline(*conference_, Direction::RECV); + } + } +} + void VideoRtpSession::forceKeyFrame() { @@ -327,11 +429,9 @@ VideoRtpSession::setRotation(int rotation) void VideoRtpSession::setupVideoPipeline() { - if (conference_) - setupConferenceVideoPipeline(*conference_); - else if (sender_) { + if (sender_) { if (videoLocal_) { - JAMI_DBG("[call:%s] Setup video pipeline on local capture device", callID_.c_str()); + JAMI_DBG("[%p] Setup video pipeline on local capture device", this); videoLocal_->attach(sender_.get()); } } else { @@ -340,27 +440,36 @@ VideoRtpSession::setupVideoPipeline() } void -VideoRtpSession::setupConferenceVideoPipeline(Conference& conference) +VideoRtpSession::setupConferenceVideoPipeline(Conference& conference, Direction dir) { - 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()); - if (videoMixer_) - videoMixer_->attach(sender_.get()); - } else - JAMI_WARN("[call:%s] no sender", callID_.c_str()); - - if (receiveThread_) { - conference.detachVideo(dummyVideoReceive_.get()); - receiveThread_->stopSink(); - conference.attachVideo(receiveThread_.get(), callID_); - } else - JAMI_WARN("[call:%s] no receiver", callID_.c_str()); + if (dir == Direction::SEND) { + JAMI_DBG("[%p] Setup video sender pipeline on conference %s for call %s", + this, + conference.getConfId().c_str(), + callID_.c_str()); + videoMixer_ = conference.getVideoMixer(); + if (sender_) { + // Swap sender from local video to conference video mixer + if (videoLocal_) + videoLocal_->detach(sender_.get()); + if (videoMixer_) + videoMixer_->attach(sender_.get()); + } else { + JAMI_WARN("[%p] no sender", this); + } + } else { + JAMI_DBG("[%p] Setup video receiver pipeline on conference %s for call %s", + this, + conference.getConfId().c_str(), + callID_.c_str()); + if (receiveThread_) { + conference.detachVideo(dummyVideoReceive_.get()); + receiveThread_->stopSink(); + conference.attachVideo(receiveThread_.get(), callID_); + } else { + JAMI_WARN("[%p] no receiver", this); + } + } } void @@ -371,9 +480,7 @@ VideoRtpSession::enterConference(Conference& conference) exitConference(); conference_ = &conference; - JAMI_DBG("[call:%s] enterConference (conf: %s)", - callID_.c_str(), - conference.getConfId().c_str()); + JAMI_DBG("[%p] enterConference (conf: %s)", this, conference.getConfId().c_str()); // TODO is this correct? The video Mixer should be enabled for a detached conference even if we // are not sending values @@ -391,11 +498,11 @@ VideoRtpSession::enterConference(Conference& conference) videoMixer_->setParameters(conf_res[0], conf_res[1]); #endif if (send_.enabled or receiveThread_) { - setupConferenceVideoPipeline(conference); - // Restart encoder with conference parameter ON in order to unlink HW encoder // from HW decoder. restartSender(); + setupConferenceVideoPipeline(conference, Direction::SEND); + setupConferenceVideoPipeline(conference, Direction::RECV); } } @@ -407,9 +514,7 @@ VideoRtpSession::exitConference() if (!conference_) return; - JAMI_DBG("[call:%s] exitConference (conf: %s)", - callID_.c_str(), - conference_->getConfId().c_str()); + JAMI_DBG("[%p] exitConference (conf: %s)", this, conference_->getConfId().c_str()); if (videoMixer_) { if (sender_) @@ -504,8 +609,6 @@ VideoRtpSession::dropProcessing(RTCPInfo* rtcpi) auto oldBitrate = videoBitrateInfo_.videoBitrateCurrent; int newBitrate = oldBitrate; - // JAMI_DBG("[AutoAdapt] pond loss: %f%, last loss: %f%", pondLoss, rtcpi->packetLoss); - // Fill histoLoss and histoJitter_ with samples if (restartTimer < DELAY_AFTER_RESTART + std::chrono::seconds(1)) { return; @@ -682,8 +785,6 @@ VideoRtpSession::getPonderateLoss(float lastLoss) for (auto it = histoLoss_.begin(); it != histoLoss_.end();) { auto delay = std::chrono::duration_cast(now - it->first); - // JAMI_WARN("now - it.first: %ld", std::chrono::duration_cast(delay)); - // 1ms -> 100% // 2000ms -> 80% if (delay <= EXPIRY_TIME_RTCP) { @@ -709,8 +810,6 @@ VideoRtpSession::delayMonitor(int gradient, int deltaT) float estimation = cc->kalmanFilter(gradient); float thresh = cc->get_thresh(); - // JAMI_WARN("gradient:%d, estimation:%f, thresh:%f", gradient, estimation, thresh); - cc->update_thresh(estimation, deltaT); BandwidthUsage bwState = cc->get_bw_state(estimation, thresh); diff --git a/src/media/video/video_rtp_session.h b/src/media/video/video_rtp_session.h index 855b78917..687102b07 100644 --- a/src/media/video/video_rtp_session.h +++ b/src/media/video/video_rtp_session.h @@ -80,6 +80,7 @@ public: void start(std::unique_ptr rtp_sock, std::unique_ptr rtcp_sock) override; void restartSender() override; void stop() override; + void setMuted(bool mute, Direction dir = Direction::SEND) override; /** * Set video orientation @@ -115,10 +116,12 @@ public: } private: - void setupConferenceVideoPipeline(Conference& conference); + void setupConferenceVideoPipeline(Conference& conference, Direction dir); void setupVideoPipeline(); void startSender(); + void stopSender(); void startReceiver(); + void stopReceiver(); using clock = std::chrono::steady_clock; using time_point = clock::time_point; diff --git a/src/sip/sdp.cpp b/src/sip/sdp.cpp index 0fdeb4f02..47f18587a 100644 --- a/src/sip/sdp.cpp +++ b/src/sip/sdp.cpp @@ -950,8 +950,7 @@ Sdp::addIceAttributes(const IceTransport::Attribute&& ice_attrs) IceTransport::Attribute Sdp::getIceAttributes() const { - IceTransport::Attribute ice_attrs; - if (auto session = (activeRemoteSession_ ? activeRemoteSession_ : remoteSession_)) + if (auto session = activeRemoteSession_ ? activeRemoteSession_ : remoteSession_) return getIceAttributes(session); return {}; } @@ -975,6 +974,8 @@ Sdp::clearIce() { clearIce(localSession_); clearIce(remoteSession_); + setActiveRemoteSdpSession(nullptr); + setActiveLocalSdpSession(nullptr); } void diff --git a/src/sip/sipcall.cpp b/src/sip/sipcall.cpp index ab60254b1..043722ac4 100644 --- a/src/sip/sipcall.cpp +++ b/src/sip/sipcall.cpp @@ -87,6 +87,9 @@ static constexpr int ICE_COMP_COUNT_PER_STREAM {2}; static constexpr auto MULTISTREAM_REQUIRED_VERSION_STR = "10.0.2"sv; static const std::vector MULTISTREAM_REQUIRED_VERSION = split_string_to_unsigned(MULTISTREAM_REQUIRED_VERSION_STR, '.'); +static constexpr auto REUSE_ICE_IN_REINVITE_REQUIRED_VERSION_STR = "11.0.99"sv; +static const std::vector REUSE_ICE_IN_REINVITE_REQUIRED_VERSION + = split_string_to_unsigned(REUSE_ICE_IN_REINVITE_REQUIRED_VERSION_STR, '.'); constexpr auto DUMMY_VIDEO_STR = "dummy video session"; @@ -147,7 +150,7 @@ SIPCall::~SIPCall() setInviteSession(); // prevents callback usage } -size_t +int SIPCall::findRtpStreamIndex(const std::string& label) const { const auto iter = std::find_if(rtpStreams_.begin(), @@ -161,7 +164,7 @@ SIPCall::findRtpStreamIndex(const std::string& label) const return std::distance(rtpStreams_.begin(), iter); // No match found. - return rtpStreams_.size(); + return -1; } void @@ -451,17 +454,14 @@ SIPCall::setSipTransport(const std::shared_ptr& transport, } void -SIPCall::requestReinvite() +SIPCall::requestReinvite(const std::vector& mediaAttrList, bool needNewIce) { JAMI_DBG("[call:%s] Sending a SIP re-invite to request media change", getCallId().c_str()); if (isWaitingForIceAndMedia_) { remainingRequest_ = Request::SwitchInput; } else { - auto mediaList = getMediaAttributeList(); - assert(not mediaList.empty()); - - if (SIPSessionReinvite(mediaList) == PJ_SUCCESS) { + if (SIPSessionReinvite(mediaAttrList, needNewIce) == PJ_SUCCESS and reinvIceMedia_) { isWaitingForIceAndMedia_ = true; } } @@ -472,7 +472,7 @@ SIPCall::requestReinvite() * Local SDP session should be modified before calling this method */ int -SIPCall::SIPSessionReinvite(const std::vector& mediaAttrList) +SIPCall::SIPSessionReinvite(const std::vector& mediaAttrList, bool needNewIce) { assert(not mediaAttrList.empty()); @@ -485,11 +485,18 @@ SIPCall::SIPSessionReinvite(const std::vector& mediaAttrList) JAMI_DBG("[call:%s] Preparing and sending a re-invite (state=%s)", getCallId().c_str(), pjsip_inv_state_name(inviteSession_->state)); + JAMI_DBG("[call:%s] New ICE required for this re-invite: [%s]", + getCallId().c_str(), + needNewIce ? "Yes" : "No"); // Generate new ports to receive the new media stream // LibAV doesn't discriminate SSRCs and will be confused about Seq changes on a given port generateMediaPorts(); + sdp_->clearIce(); + sdp_->setActiveRemoteSdpSession(nullptr); + sdp_->setActiveLocalSdpSession(nullptr); + auto acc = getSIPAccount(); if (not acc) { JAMI_ERR("No account detected"); @@ -499,11 +506,13 @@ SIPCall::SIPSessionReinvite(const std::vector& mediaAttrList) if (not sdp_->createOffer(mediaAttrList)) return !PJ_SUCCESS; - if (isIceEnabled()) { + if (isIceEnabled() and needNewIce) { if (not createIceMediaTransport(true) or not initIceMediaTransport(true)) { return !PJ_SUCCESS; } addLocalIceAttributes(); + // Media transport changed, must restart the media. + mediaRestartRequired_ = true; } pjsip_tx_data* tdata; @@ -536,7 +545,7 @@ int SIPCall::SIPSessionReinvite() { auto mediaList = getMediaAttributeList(); - return SIPSessionReinvite(mediaList); + return SIPSessionReinvite(mediaList, isNewIceMediaRequired(mediaList)); } void @@ -614,7 +623,7 @@ SIPCall::requestKeyframe() } void -SIPCall::setMute(bool state) +SIPCall::sendMuteState(bool state) { std::string BODY = "" "" @@ -815,7 +824,7 @@ SIPCall::answer(const std::vector& mediaList) // Create the SDP answer sdp_->processIncomingOffer(mediaAttrList); - if (isIceEnabled()) { + if (isIceEnabled() and remoteHasValidIceAttributes()) { setupIceResponse(); } @@ -840,6 +849,8 @@ SIPCall::answer(const std::vector& mediaList) // Setup and create ICE offer if (isIceEnabled()) { sdp_->clearIce(); + sdp_->setActiveRemoteSdpSession(nullptr); + sdp_->setActiveLocalSdpSession(nullptr); auto opts = account->getIceOptions(); @@ -896,7 +907,7 @@ SIPCall::answer(const std::vector& mediaList) } void -SIPCall::answerMediaChangeRequest(const std::vector& mediaList) +SIPCall::answerMediaChangeRequest(const std::vector& remoteMediaList) { std::lock_guard lk {callMutex_}; @@ -906,19 +917,20 @@ SIPCall::answerMediaChangeRequest(const std::vector& mediaList) return; } - auto mediaAttrList = MediaAttribute::buildMediaAttributesList(mediaList, isSrtpEnabled()); + auto remoteMediaAttrList = MediaAttribute::buildMediaAttributesList(remoteMediaList, + isSrtpEnabled()); // TODO. is the right place? // Disable video if disabled in the account. if (not account->isVideoEnabled()) { - for (auto& mediaAttr : mediaAttrList) { + for (auto& mediaAttr : remoteMediaAttrList) { if (mediaAttr.type_ == MediaType::MEDIA_VIDEO) { mediaAttr.enabled_ = false; } } } - if (mediaAttrList.empty()) { + if (remoteMediaAttrList.empty()) { JAMI_WARN("[call:%s] Media list is empty. Ignoring the media change request", getCallId().c_str()); return; @@ -940,16 +952,16 @@ SIPCall::answerMediaChangeRequest(const std::vector& mediaList) JAMI_DBG("[call:%s] Answering to media change request with new media", getCallId().c_str()); idx = 0; - for (auto const& newMediaAttr : mediaAttrList) { + for (auto const& newMediaAttr : remoteMediaAttrList) { JAMI_DBG("[call:%s] Media @%u: %s", getCallId().c_str(), idx++, newMediaAttr.toString(true).c_str()); } - updateAllMediaStreams(mediaAttrList); + updateAllMediaStreams(remoteMediaAttrList); - if (not sdp_->processIncomingOffer(mediaAttrList)) { + if (not sdp_->processIncomingOffer(remoteMediaAttrList)) { JAMI_WARN("[call:%s] Could not process the new offer, ignoring", getCallId().c_str()); return; } @@ -959,8 +971,10 @@ SIPCall::answerMediaChangeRequest(const std::vector& mediaList) return; } - if (isIceEnabled()) + if (isIceEnabled() and remoteHasValidIceAttributes()) { + JAMI_WARN("[call:%s] Requesting a new ICE media", getCallId().c_str()); setupIceResponse(true); + } if (not sdp_->startNegotiation()) { JAMI_ERR("[call:%s] Could not start media negotiation for a re-invite request", @@ -1262,7 +1276,9 @@ SIPCall::hold() return false; } - isWaitingForIceAndMedia_ = true; + // TODO. Do we need to check for reinvIceMedia_ ? + isWaitingForIceAndMedia_ = (reinvIceMedia_ != nullptr); + JAMI_DBG("[call:%s] Set state to HOLD", getCallId().c_str()); return true; } @@ -1272,11 +1288,14 @@ SIPCall::offhold(OnReadyCb&& cb) { // If ICE is currently negotiating, we must wait before unhold the call if (isWaitingForIceAndMedia_) { + JAMI_DBG("[call:%s] ICE negotiation in progress. Resume request will be once ICE " + "negotiation completes", + getCallId().c_str()); offHoldCb_ = std::move(cb); remainingRequest_ = Request::HoldingOff; return false; } - + JAMI_DBG("[call:%s] Resuming the call", getCallId().c_str()); auto result = unhold(); if (cb) @@ -1302,8 +1321,8 @@ SIPCall::unhold() throw VoipLinkException("SDP issue in offhold"); } - if (success) - isWaitingForIceAndMedia_ = true; + // Only wait for ICE if we have an ICE re-invite in progress + isWaitingForIceAndMedia_ = success and (reinvIceMedia_ != nullptr); return success; } @@ -1324,7 +1343,8 @@ SIPCall::internalOffHold(const std::function& sdp_cb) for (auto& stream : rtpStreams_) { stream.mediaAttribute_->onHold_ = false; } - if (SIPSessionReinvite() != PJ_SUCCESS) { + // For now, call resume will always require new ICE negotiation. + if (SIPSessionReinvite(getMediaAttributeList(), true) != PJ_SUCCESS) { JAMI_WARN("[call:%s] resuming hold", getCallId().c_str()); if (isWaitingForIceAndMedia_) { remainingRequest_ = Request::HoldingOn; @@ -1355,7 +1375,9 @@ SIPCall::switchInput(const std::string& source) if (isWaitingForIceAndMedia_) { remainingRequest_ = Request::SwitchInput; } else { - if (SIPSessionReinvite() == PJ_SUCCESS) { + // For now, switchInput will always trigger a re-invite + // with new ICE session. + if (SIPSessionReinvite(getMediaAttributeList(), true) == PJ_SUCCESS and reinvIceMedia_) { isWaitingForIceAndMedia_ = true; } } @@ -1643,6 +1665,18 @@ SIPCall::setPeerUaVersion(std::string_view ua) (int) MULTISTREAM_REQUIRED_VERSION_STR.size(), MULTISTREAM_REQUIRED_VERSION_STR.data()); } + + // Check if peer's version supports re-invite without ICE renegotiation. + peerSupportReuseIceInReinv_ + = Account::meetMinimumRequiredVersion(peerVersion, REUSE_ICE_IN_REINVITE_REQUIRED_VERSION); + if (not peerSupportReuseIceInReinv_) { + JAMI_DBG("Peer's version [%.*s] does not support re-invite without ICE renegotiation. Min " + "required version: [%.*s]", + (int) version.size(), + version.data(), + (int) REUSE_ICE_IN_REINVITE_REQUIRED_VERSION_STR.size(), + REUSE_ICE_IN_REINVITE_REQUIRED_VERSION_STR.data()); + } } void @@ -1899,7 +1933,7 @@ SIPCall::isCaptureDeviceMuted(const MediaType& mediaType) const } void -SIPCall::updateNegotiatedMedia() +SIPCall::setupNegotiatedMedia() { JAMI_DBG("[call:%s] updating negotiated media", getCallId().c_str()); @@ -1987,26 +2021,20 @@ SIPCall::updateNegotiatedMedia() configureRtpSession(rtpStream.rtpSession_, rtpStream.mediaAttribute_, local, remote); } + // TODO. Do we really use this? if (not isSubcall() and peerHolding_ != peer_holding) { peerHolding_ = peer_holding; emitSignal(getCallId(), peerHolding_); } - - // Notify using the parent Id if it's a subcall. - auto callId = isSubcall() ? parent_->getCallId() : getCallId(); - emitSignal( - callId, - DRing::Media::MediaNegotiationStatusEvents::NEGOTIATION_SUCCESS, - MediaAttribute::mediaAttributesToMediaMaps(getMediaAttributeList())); } void SIPCall::startAllMedia() { - JAMI_DBG("[call:%s] starting the media", getCallId().c_str()); + JAMI_DBG("[call:%s] Starting all media", getCallId().c_str()); if (not sipTransport_ or not sdp_) { - JAMI_ERR("[call:%s] the call is in invalid state", getCallId().c_str()); + JAMI_ERR("[call:%s] The call is in invalid state", getCallId().c_str()); return; } @@ -2022,8 +2050,6 @@ SIPCall::startAllMedia() bool hasActiveVideo = false; #endif - int currentCompId = 1; - for (auto iter = rtpStreams_.begin(); iter != rtpStreams_.end(); iter++) { if (not iter->mediaAttribute_) { throw std::runtime_error("Missing media attribute"); @@ -2038,14 +2064,7 @@ SIPCall::startAllMedia() // because of the audio loop if (getState() != CallState::HOLD) { if (isIceRunning()) { - // Create sockets for RTP and RTCP, and start the session. - auto iceRtpSocket = newIceSocket(currentCompId++); - - std::unique_ptr iceRtcpSocket; - if (not rtcpMuxEnabled_) { - iceRtcpSocket = newIceSocket(currentCompId++); - } - iter->rtpSession_->start(std::move(iceRtpSocket), std::move(iceRtcpSocket)); + iter->rtpSession_->start(std::move(iter->rtpSocket_), std::move(iter->rtcpSocket_)); } else { iter->rtpSession_->start(nullptr, nullptr); } @@ -2090,6 +2109,8 @@ SIPCall::startAllMedia() remainingRequest_ = Request::NoRequest; } + mediaRestartRequired_ = false; + #ifdef ENABLE_PLUGIN // Create AVStreams associated with the call createCallAVStreams(); @@ -2116,7 +2137,7 @@ SIPCall::restartMediaSender() void SIPCall::stopAllMedia() { - JAMI_DBG("[call:%s] stopping all medias", getCallId().c_str()); + JAMI_DBG("[call:%s] Stopping all media", getCallId().c_str()); if (Recordable::isRecording()) { deinitRecorder(); stopRecording(); // if call stops, finish recording @@ -2154,6 +2175,36 @@ SIPCall::stopAllMedia() #endif } +void +SIPCall::updateRemoteMedia() +{ + JAMI_DBG("[call:%s] Updating remote media", getCallId().c_str()); + + auto remoteMediaList = Sdp::getMediaAttributeListFromSdp(sdp_->getActiveRemoteSdpSession()); + + if (remoteMediaList.size() != rtpStreams_.size()) { + JAMI_ERR("[call:%s] Media size mismatch!", getCallId().c_str()); + return; + } + + for (size_t idx = 0; idx < remoteMediaList.size(); idx++) { + auto& rtpStream = rtpStreams_[idx]; + auto const& remoteMedia = rtpStream.remoteMediaAttribute_ = std::make_shared( + remoteMediaList[idx]); + if (remoteMedia->type_ == MediaType::MEDIA_VIDEO) { + JAMI_DBG("[call:%s] Remote media @ %lu : %s", + getCallId().c_str(), + idx, + remoteMedia->toString().c_str()); + rtpStream.rtpSession_->setMuted(remoteMedia->muted_, RtpSession::Direction::RECV); + // Request a key-frame if we are un-muting the video + if (not remoteMedia->muted_) { + requestKeyframe(); + } + } + } +} + void SIPCall::muteMedia(const std::string& mediaType, bool mute) { @@ -2208,33 +2259,37 @@ SIPCall::updateMediaStream(const MediaAttribute& newMediaAttr, size_t streamIdx) mediaAttr->muted_ ? "muted " : "un-muted "); } else { + // Update + mediaAttr->muted_ = newMediaAttr.muted_; notify = true; + JAMI_DBG("[call:%s] %s [%s]", + getCallId().c_str(), + mediaAttr->muted_ ? "muting" : "un-muting", + mediaAttr->label_.c_str()); } - // Update - mediaAttr->muted_ = newMediaAttr.muted_; // Only update source and type if actually set. if (not newMediaAttr.sourceUri_.empty()) mediaAttr->sourceUri_ = newMediaAttr.sourceUri_; if (newMediaAttr.sourceType_ != MediaSourceType::NONE) mediaAttr->sourceType_ = newMediaAttr.sourceType_; - JAMI_DBG("[call:%s] %s [%s]", - getCallId().c_str(), - mediaAttr->muted_ ? "muting" : "un-muting", - mediaAttr->label_.c_str()); - if (notify and mediaAttr->type_ == MediaType::MEDIA_AUDIO) { + rtpStream.rtpSession_->setMediaSource(mediaAttr->sourceUri_); rtpStream.rtpSession_->setMuted(mediaAttr->muted_); - setMute(mediaAttr->muted_); + sendMuteState(mediaAttr->muted_); if (not isSubcall()) emitSignal(getCallId(), mediaAttr->muted_); return; } #ifdef ENABLE_VIDEO - if (notify and mediaAttr->type_ == MediaType::MEDIA_VIDEO and not isSubcall()) { - emitSignal(getCallId(), mediaAttr->muted_); + if (notify and mediaAttr->type_ == MediaType::MEDIA_VIDEO) { + rtpStream.rtpSession_->setMediaSource(mediaAttr->sourceUri_); + rtpStream.rtpSession_->setMuted(mediaAttr->muted_); + + if (not isSubcall()) + emitSignal(getCallId(), mediaAttr->muted_); } #endif } @@ -2257,12 +2312,12 @@ SIPCall::updateAllMediaStreams(const std::vector& mediaAttrList) for (auto const& newAttr : mediaAttrList) { auto streamIdx = findRtpStreamIndex(newAttr.label_); - if (streamIdx == rtpStreams_.size()) { + if (streamIdx < 0) { // Media does not exist, add a new one. addMediaStream(newAttr); auto& stream = rtpStreams_.back(); createRtpSession(stream); - JAMI_DBG("[call:%s] Added a new media stream [%s] @ index %lu", + JAMI_DBG("[call:%s] Added a new media stream [%s] @ index %i", getCallId().c_str(), stream.mediaAttribute_->label_.c_str(), streamIdx); @@ -2281,7 +2336,7 @@ SIPCall::isReinviteRequired(const std::vector& mediaAttrList) for (auto const& newAttr : mediaAttrList) { auto streamIdx = findRtpStreamIndex(newAttr.label_); - if (streamIdx == rtpStreams_.size()) { + if (streamIdx < 0) { // Always needs a re-invite when a new media is added. return true; } @@ -2293,9 +2348,11 @@ SIPCall::isReinviteRequired(const std::vector& mediaAttrList) #ifdef ENABLE_VIDEO if (newAttr.type_ == MediaType::MEDIA_VIDEO) { - assert(rtpStreams_[streamIdx].mediaAttribute_); - // Changes in video attributes always trigger a re-invite. - return newAttr.muted_ != rtpStreams_[streamIdx].mediaAttribute_->muted_; + // For now, only video mute triggers a re-invite. + // Might be done for audio as well if required. + if (newAttr.muted_ != rtpStreams_[streamIdx].mediaAttribute_->muted_) { + return true; + } } #endif } @@ -2303,6 +2360,37 @@ SIPCall::isReinviteRequired(const std::vector& mediaAttrList) return false; } +bool +SIPCall::isNewIceMediaRequired(const std::vector& mediaAttrList) +{ + // Always needs a new ICE media if the peer does not support + // re-invite without ICE renegotiation + if (not peerSupportReuseIceInReinv_) + return true; + + // Always needs a new ICE media when the number of media changes. + if (mediaAttrList.size() != rtpStreams_.size()) + return true; + + for (auto const& newAttr : mediaAttrList) { + auto streamIdx = findRtpStreamIndex(newAttr.label_); + if (streamIdx < 0) { + // Always needs a new ICE media when a media is added or replaced. + return true; + } + auto const& currAttr = rtpStreams_[streamIdx].mediaAttribute_; + if (newAttr.sourceType_ != currAttr->sourceType_ + or newAttr.sourceUri_ != currAttr->sourceUri_) { + // For now, media will be restarted if the source changes. + // TODO. This should not be needed if the decoder/receiver + // correctly handles dynamic media properties changes. + return true; + } + } + + return false; +} + bool SIPCall::requestMediaChange(const std::vector& mediaList) { @@ -2348,13 +2436,17 @@ SIPCall::requestMediaChange(const std::vector& mediaList) } auto needReinvite = isReinviteRequired(mediaAttrList); + auto needNewIce = isNewIceMediaRequired(mediaAttrList); updateAllMediaStreams(mediaAttrList); if (needReinvite) { JAMI_DBG("[call:%s] Media change requires a new negotiation (re-invite)", getCallId().c_str()); - requestReinvite(); + requestReinvite(mediaAttrList, needNewIce); + } else { + JAMI_WARN("[call:%s] Media change DOES NOT require a new negotiation (re-invite)", + getCallId().c_str()); } return true; @@ -2371,21 +2463,14 @@ SIPCall::getMediaAttributeList() const return mediaList; } -/// \brief Prepare media transport and launch media stream based on negotiated SDP -/// -/// This method has to be called by link (ie SipVoIpLink) when SDP is negotiated and -/// media streams structures are knows. -/// In case of ICE transport used, the medias streams are launched asynchronously when -/// the transport is negotiated. void SIPCall::onMediaNegotiationComplete() { - // Main call (no subcalls) must wait for ICE now, the rest of code needs to access - // to a negotiated transport. runOnMainThread([w = weak()] { if (auto this_ = w.lock()) { std::lock_guard lk {this_->callMutex_}; JAMI_DBG("[call:%s] Media negotiation complete", this_->getCallId().c_str()); + // If the call has already ended, we don't need to start the media. if (not this_->inviteSession_ or this_->inviteSession_->state == PJSIP_INV_STATE_DISCONNECTED @@ -2393,32 +2478,47 @@ SIPCall::onMediaNegotiationComplete() return; } - bool hasIce = this_->isIceEnabled(); - if (hasIce) { - // If ICE is not used, start medias now - auto rem_ice_attrs = this_->sdp_->getIceAttributes(); - hasIce = not rem_ice_attrs.ufrag.empty() and not rem_ice_attrs.pwd.empty(); - } - if (hasIce) { + // This method is called to report media negotiation (SDP) for initial + // invite or subsequent invites (re-invite). + // If ICE is negotiated, the media update will be handled in the + // ICE callback, otherwise, it will be handled here. + // Note that ICE can be negotiated in the first invite and not negotiated + // in the re-invite. In this case, the media transport is unchanged (reused). + if (this_->isIceEnabled() and this_->remoteHasValidIceAttributes()) { if (not this_->isSubcall()) { // Start ICE checks. Media will be started once ICE checks complete. this_->startIceMedia(); } } else { - // No ICE, start media now. - JAMI_WARN("[call:%s] ICE media disabled, using default media ports", - this_->getCallId().c_str()); // Update the negotiated media. - this_->updateNegotiatedMedia(); + if (this_->mediaRestartRequired_) { + this_->setupNegotiatedMedia(); + // No ICE, start media now. + JAMI_WARN("[call:%s] ICE media disabled, using default media ports", + this_->getCallId().c_str()); + // Start the media. + this_->stopAllMedia(); + this_->startAllMedia(); + } - // Start the media. - this_->stopAllMedia(); - this_->startAllMedia(); + this_->updateRemoteMedia(); + this_->reportMediaNegotiationStatus(); } } }); } +void +SIPCall::reportMediaNegotiationStatus() +{ + // Notify using the parent Id if it's a subcall. + auto callId = isSubcall() ? parent_->getCallId() : getCallId(); + emitSignal( + callId, + DRing::Media::MediaNegotiationStatusEvents::NEGOTIATION_SUCCESS, + MediaAttribute::mediaAttributesToMediaMaps(getMediaAttributeList())); +} + void SIPCall::startIceMedia() { @@ -2461,6 +2561,8 @@ SIPCall::startIceMedia() void SIPCall::onIceNegoSucceed() { + std::lock_guard lk {callMutex_}; + JAMI_DBG("[call:%s] ICE negotiation succeeded", getCallId().c_str()); // Check if the call is already ended, so we don't need to restart medias @@ -2473,16 +2575,28 @@ SIPCall::onIceNegoSucceed() } // Update the negotiated media. - updateNegotiatedMedia(); + setupNegotiatedMedia(); // If this callback is for a re-invite session then update // the ICE media transport. if (isIceEnabled()) switchToIceReinviteIfNeeded(); + for (unsigned int idx = 0, compId = 1; idx < rtpStreams_.size(); idx++, compId += 2) { + // Create sockets for RTP and RTCP, and start the session. + auto& rtpStream = rtpStreams_[idx]; + rtpStream.rtpSocket_ = newIceSocket(compId); + + if (not rtcpMuxEnabled_) { + rtpStream.rtcpSocket_ = newIceSocket(compId + 1); + } + } + // Start/Restart the media using the new transport stopAllMedia(); startAllMedia(); + updateRemoteMedia(); + reportMediaNegotiationStatus(); } bool @@ -2574,6 +2688,9 @@ SIPCall::onReceiveReinvite(const pjmedia_sdp_session* offer, pjsip_rx_data* rdat } sdp_->clearIce(); + sdp_->setActiveRemoteSdpSession(nullptr); + sdp_->setActiveLocalSdpSession(nullptr); + auto acc = getSIPAccount(); if (not acc) { JAMI_ERR("No account detected"); @@ -2642,7 +2759,11 @@ SIPCall::onReceiveOfferIn200OK(const pjmedia_sdp_session* offer) } Sdp::printSession(offer, "Remote session (offer in 200 OK answer)", SdpDirection::OFFER); + sdp_->clearIce(); + sdp_->setActiveRemoteSdpSession(nullptr); + sdp_->setActiveLocalSdpSession(nullptr); + sdp_->setReceivedOffer(offer); // If we send an empty offer, video will be accepted only if locally @@ -2661,7 +2782,7 @@ SIPCall::onReceiveOfferIn200OK(const pjmedia_sdp_session* offer) openPortsUPnP(); } - if (isIceEnabled()) { + if (isIceEnabled() and remoteHasValidIceAttributes()) { setupIceResponse(); } @@ -2835,7 +2956,7 @@ SIPCall::getVideoRtp() const return std::dynamic_pointer_cast(rtp); } } - return nullptr; + return {}; } bool @@ -3154,6 +3275,7 @@ SIPCall::merge(Call& call) peerUserAgent_ = subcall.peerUserAgent_; peerSupportMultiStream_ = subcall.peerSupportMultiStream_; peerAllowedMethods_ = subcall.peerAllowedMethods_; + peerSupportReuseIceInReinv_ = subcall.peerSupportReuseIceInReinv_; Call::merge(subcall); if (isIceEnabled()) @@ -3161,7 +3283,7 @@ SIPCall::merge(Call& call) } bool -SIPCall::remoteHasValidIceAttributes() +SIPCall::remoteHasValidIceAttributes() const { if (not sdp_) { throw std::runtime_error("Must have a valid SDP Session"); @@ -3169,13 +3291,12 @@ SIPCall::remoteHasValidIceAttributes() auto rem_ice_attrs = sdp_->getIceAttributes(); if (rem_ice_attrs.ufrag.empty()) { - JAMI_WARN("[call:%s] Missing ICE username fragment attribute in remote SDP", - getCallId().c_str()); + JAMI_DBG("[call:%s] No ICE username fragment attribute in remote SDP", getCallId().c_str()); return false; } if (rem_ice_attrs.pwd.empty()) { - JAMI_WARN("[call:%s] Missing ICE password attribute in remote SDP", getCallId().c_str()); + JAMI_DBG("[call:%s] No ICE password attribute in remote SDP", getCallId().c_str()); return false; } @@ -3223,13 +3344,6 @@ SIPCall::setupIceResponse(bool isReinvite) JAMI_ERR("No account detected"); } - if (not remoteHasValidIceAttributes()) { - // If ICE attributes are not present, skip the ICE initialization - // step (most likely peer does not support/enable ICE). - JAMI_WARN("[call:%s] no ICE data in remote SDP", getCallId().c_str()); - return; - } - auto opt = account->getIceOptions(); // Try to use the discovered public address. If not available, @@ -3260,6 +3374,9 @@ SIPCall::setupIceResponse(bool isReinvite) return; } + // Media transport changed, must restart the media. + mediaRestartRequired_ = true; + // WARNING: This call blocks! (need ice init done) addLocalIceAttributes(); } diff --git a/src/sip/sipcall.h b/src/sip/sipcall.h index 16e4cbc91..4ccb5427f 100644 --- a/src/sip/sipcall.h +++ b/src/sip/sipcall.h @@ -89,6 +89,9 @@ public: { std::shared_ptr rtpSession_ {}; std::shared_ptr mediaAttribute_ {}; + std::shared_ptr remoteMediaAttribute_; + std::unique_ptr rtpSocket_; + std::unique_ptr rtcpSocket_; }; /** @@ -208,6 +211,7 @@ public: pj_status_t onReceiveReinvite(const pjmedia_sdp_session* offer, pjsip_rx_data* rdata); void onReceiveOfferIn200OK(const pjmedia_sdp_session* offer); + /** * Called when the media negotiation (SDP offer/answer) has * completed. @@ -230,7 +234,7 @@ public: std::shared_ptr getSIPAccount() const; - bool remoteHasValidIceAttributes(); + bool remoteHasValidIceAttributes() const; void addLocalIceAttributes(); std::shared_ptr getIceMedia() const @@ -308,7 +312,7 @@ private: void rtpSetupSuccess(MediaType type, bool isRemote); - void setMute(bool state); + void sendMuteState(bool state); void resetTransport(std::shared_ptr&& transport); @@ -355,9 +359,11 @@ private: void setCallMediaLocal(); void startIceMedia(); void onIceNegoSucceed(); - void updateNegotiatedMedia(); + void setupNegotiatedMedia(); void startAllMedia(); void stopAllMedia(); + void reportMediaNegotiationStatus(); + void updateRemoteMedia(); /** * Transfer method used for both type of transfer @@ -375,8 +381,10 @@ private: void updateAllMediaStreams(const std::vector& mediaAttrList); // Check if a SIP re-invite must be sent to negotiate the new media bool isReinviteRequired(const std::vector& mediaAttrList); - void requestReinvite(); - int SIPSessionReinvite(const std::vector& mediaAttrList); + // Check if a new ICE media session is needed when performing a re-invite + bool isNewIceMediaRequired(const std::vector& mediaAttrList); + void requestReinvite(const std::vector& mediaAttrList, bool needNewIce); + int SIPSessionReinvite(const std::vector& mediaAttrList, bool needNewIce); int SIPSessionReinvite(); // Add a media stream to the call. void addMediaStream(const MediaAttribute& mediaAttr); @@ -390,7 +398,7 @@ private: const MediaDescription& localMedia, const MediaDescription& remoteMedia); // Find the stream index with the matching label - size_t findRtpStreamIndex(const std::string& label) const; + int findRtpStreamIndex(const std::string& label) const; std::vector getAllRemoteCandidates(IceTransport& transport) const; @@ -410,9 +418,13 @@ private: // Peer's User-Agent. std::string peerUserAgent_ {}; - // Flag to indicate the the peer's Daemon version support multi-stream. + // Flag to indicate if the peer's Daemon version supports multi-stream. bool peerSupportMultiStream_ {false}; + // Flag to indicate if the peer's Daemon version supports re-invite + // without ICE renegotiation. + bool peerSupportReuseIceInReinv_ {false}; + // Peer's allowed methods. std::vector peerAllowedMethods_; @@ -447,6 +459,7 @@ private: /** Local video port, as seen by me. */ unsigned int localVideoPort_ {0}; + bool mediaRestartRequired_ {true}; bool enableIce_ {true}; bool srtpEnabled_ {false}; bool rtcpMuxEnabled_ {false}; diff --git a/src/sip/sipvoiplink.cpp b/src/sip/sipvoiplink.cpp index 273c70105..43534721b 100644 --- a/src/sip/sipvoiplink.cpp +++ b/src/sip/sipvoiplink.cpp @@ -1146,6 +1146,7 @@ sdp_media_update_cb(pjsip_inv_session* inv, pj_status_t status) if (remoteSDP != nullptr) { Sdp::printSession(remoteSDP, "Remote active session:", sdp.getSdpDirection()); } + call->onMediaNegotiationComplete(); } diff --git a/test/unitTest/media_negotiation/auto_answer.cpp b/test/unitTest/media_negotiation/auto_answer.cpp index ef312c31b..496c60277 100644 --- a/test/unitTest/media_negotiation/auto_answer.cpp +++ b/test/unitTest/media_negotiation/auto_answer.cpp @@ -614,7 +614,7 @@ AutoAnswerMediaNegoTest::configureScenario() bobData_.userName_ = account->getAccountDetails()[ConfProperties::USERNAME]; bobData_.alias_ = account->getAccountDetails()[ConfProperties::ALIAS]; account->enableIceForMedia(true); - account->isAutoAnswerEnabled(); + CPPUNIT_ASSERT(account->isAutoAnswerEnabled()); if (isSipAccount_) { auto sipAccount = std::dynamic_pointer_cast(account); @@ -1021,8 +1021,10 @@ AutoAnswerMediaNegoTest::audio_and_video_then_change_video_source() JAMI_INFO("=== End test %s ===", __FUNCTION__); } -CPPUNIT_TEST_SUITE_NAMED_REGISTRATION(AutoAnswerMediaNegoTestSip, "AutoAnswerMediaNegoTestSip"); -CPPUNIT_TEST_SUITE_NAMED_REGISTRATION(AutoAnswerMediaNegoTestJami, "AutoAnswerMediaNegoTestJami"); +CPPUNIT_TEST_SUITE_NAMED_REGISTRATION(AutoAnswerMediaNegoTestSip, + AutoAnswerMediaNegoTestSip::name()); +CPPUNIT_TEST_SUITE_NAMED_REGISTRATION(AutoAnswerMediaNegoTestJami, + AutoAnswerMediaNegoTestJami::name()); } // namespace test } // namespace jami diff --git a/test/unitTest/media_negotiation/media_negotiation.cpp b/test/unitTest/media_negotiation/media_negotiation.cpp index 5eb79c053..e4728fc2b 100644 --- a/test/unitTest/media_negotiation/media_negotiation.cpp +++ b/test/unitTest/media_negotiation/media_negotiation.cpp @@ -27,6 +27,7 @@ #include "manager.h" #include "jamidht/connectionmanager.h" #include "jamidht/jamiaccount.h" +#include "sip/sipaccount.h" #include "../../test_runner.h" #include "jami.h" #include "jami/media_const.h" @@ -79,9 +80,23 @@ struct CallData std::string event_ {}; }; + CallData() = default; + CallData(CallData&& other) = delete; + CallData(const CallData& other) + { + accountId_ = std::move(other.accountId_); + listeningPort_ = other.listeningPort_; + userName_ = std::move(other.userName_); + alias_ = std::move(other.alias_); + callId_ = std::move(other.callId_); + signals_ = std::move(other.signals_); + }; + std::string accountId_ {}; std::string userName_ {}; std::string alias_ {}; + uint16_t listeningPort_ {0}; + std::string toUri_ {}; std::string callId_ {}; std::vector signals_; std::condition_variable cv_ {}; @@ -91,7 +106,7 @@ struct CallData /** * Basic tests for media negotiation. */ -class MediaNegotiationTest : public CppUnit::TestFixture +class MediaNegotiationTest { public: MediaNegotiationTest() @@ -104,10 +119,8 @@ public: ~MediaNegotiationTest() { DRing::fini(); } static std::string name() { return "MediaNegotiationTest"; } - void setUp(); - void tearDown(); -private: +protected: // Test cases. void audio_and_video_then_caller_mute_video(); void audio_only_then_caller_add_video(); @@ -115,14 +128,6 @@ private: void audio_and_video_answer_muted_video_then_mute_video(); void audio_and_video_then_change_video_source(); - CPPUNIT_TEST_SUITE(MediaNegotiationTest); - CPPUNIT_TEST(audio_and_video_then_caller_mute_video); - CPPUNIT_TEST(audio_only_then_caller_add_video); - CPPUNIT_TEST(audio_and_video_then_caller_mute_audio); - CPPUNIT_TEST(audio_and_video_answer_muted_video_then_mute_video); - CPPUNIT_TEST(audio_and_video_then_change_video_source); - CPPUNIT_TEST_SUITE_END(); - // Event/Signal handlers static void onCallStateChange(const std::string& accountId, const std::string& callId, @@ -132,7 +137,8 @@ private: const std::string& callId, const std::vector mediaList, CallData& callData); - // For backward compatibility test cases + // For backward compatibility test cases. + // TODO. Do we still need this? static void onIncomingCall(const std::string& accountId, const std::string& callId, CallData& callData); @@ -146,11 +152,10 @@ private: CallData& callData); // Helpers - static void configureScenario(CallData& bob, CallData& alice); - static void testWithScenario(CallData& aliceData, - CallData& bobData, - const TestScenario& scenario); - static std::string getUserAlias(const std::string& callId); + void configureScenario(); + void testWithScenario(CallData& aliceData, CallData& bobData, const TestScenario& scenario); + std::string getAccountId(const std::string& callId); + std::string getUserAlias(const std::string& callId); // Infer media direction of an offer. static uint8_t directionToBitset(MediaDirection direction, bool isLocal); static MediaDirection bitsetToDirection(uint8_t val); @@ -168,50 +173,166 @@ private: const std::string& signal, const std::string& expectedEvent = {}); -private: - CallData aliceData_; - CallData bobData_; + bool isSipAccount_ {false}; + std::map callDataMap_; + std::set testAccounts_; }; -CPPUNIT_TEST_SUITE_NAMED_REGISTRATION(MediaNegotiationTest, MediaNegotiationTest::name()); - -void -MediaNegotiationTest::setUp() +// Specialized test case for Jami accounts +class MediaNegotiationTestJami : public MediaNegotiationTest, public CppUnit::TestFixture { - auto actors = load_actors("actors/alice-bob-no-upnp.yml"); +public: + MediaNegotiationTestJami() { isSipAccount_ = false; } - aliceData_.accountId_ = actors["alice"]; - bobData_.accountId_ = actors["bob"]; + static std::string name() { return "MediaNegotiationTestJami"; } - JAMI_INFO("Initialize account..."); - auto aliceAccount = Manager::instance().getAccount(aliceData_.accountId_); - auto bobAccount = Manager::instance().getAccount(bobData_.accountId_); + void setUp() override + { + auto actors = load_actors("actors/alice-bob-no-upnp.yml"); + callDataMap_["ALICE"].accountId_ = actors["alice"]; + callDataMap_["BOB"].accountId_ = actors["bob"]; - wait_for_announcement_of({aliceAccount->getAccountID(), bobAccount->getAccountID()}); -} + JAMI_INFO("Initialize account..."); + auto aliceAccount = Manager::instance().getAccount( + callDataMap_["ALICE"].accountId_); + auto bobAccount = Manager::instance().getAccount( + callDataMap_["BOB"].accountId_); -void -MediaNegotiationTest::tearDown() + wait_for_announcement_of({aliceAccount->getAccountID(), bobAccount->getAccountID()}); + } + + void tearDown() override + { + wait_for_removal_of({callDataMap_["ALICE"].accountId_, callDataMap_["BOB"].accountId_}); + } + +private: + CPPUNIT_TEST_SUITE(MediaNegotiationTestJami); + CPPUNIT_TEST(audio_and_video_then_caller_mute_video); + CPPUNIT_TEST(audio_only_then_caller_add_video); + CPPUNIT_TEST(audio_and_video_then_caller_mute_audio); + CPPUNIT_TEST(audio_and_video_answer_muted_video_then_mute_video); + CPPUNIT_TEST(audio_and_video_then_change_video_source); + CPPUNIT_TEST_SUITE_END(); +}; + +// Specialized test case for SIP accounts +class MediaNegotiationTestSip : public MediaNegotiationTest, public CppUnit::TestFixture { - wait_for_removal_of({aliceData_.accountId_, bobData_.accountId_}); -} +public: + MediaNegotiationTestSip() { isSipAccount_ = true; } + + static std::string name() { return "MediaNegotiationTestSip"; } + + bool addTestAccount(const std::string& alias, uint16_t port) + { + CallData callData; + callData.alias_ = alias; + callData.userName_ = alias; + callData.listeningPort_ = port; + std::map details = DRing::getAccountTemplate("SIP"); + details[ConfProperties::TYPE] = "SIP"; + details[ConfProperties::USERNAME] = alias; + details[ConfProperties::DISPLAYNAME] = alias; + details[ConfProperties::ALIAS] = alias; + details[ConfProperties::LOCAL_PORT] = std::to_string(port); + details[ConfProperties::UPNP_ENABLED] = "false"; + callData.accountId_ = Manager::instance().addAccount(details); + testAccounts_.insert(callData.accountId_); + callDataMap_.emplace(alias, std::move(callData)); + return (not callDataMap_[alias].accountId_.empty()); + } + + void setUp() override + { + CPPUNIT_ASSERT(addTestAccount("ALICE", 5080)); + CPPUNIT_ASSERT(addTestAccount("BOB", 5082)); + } + + void tearDown() override + { + std::map> confHandlers; + std::mutex mtx; + std::unique_lock lk {mtx}; + std::condition_variable cv; + std::atomic_bool accountsRemoved {false}; + confHandlers.insert( + DRing::exportable_callback([&]() { + auto currAccounts = Manager::instance().getAccountList(); + for (auto iter = testAccounts_.begin(); iter != testAccounts_.end();) { + auto item = std::find(currAccounts.begin(), currAccounts.end(), *iter); + if (item == currAccounts.end()) { + JAMI_INFO("Removing account %s", (*iter).c_str()); + iter = testAccounts_.erase(iter); + } else { + iter++; + } + } + + if (testAccounts_.empty()) { + accountsRemoved = true; + JAMI_INFO("All accounts removed..."); + cv.notify_one(); + } + })); + + DRing::registerSignalHandlers(confHandlers); + + Manager::instance().removeAccount(callDataMap_["ALICE"].accountId_, true); + Manager::instance().removeAccount(callDataMap_["BOB"].accountId_, true); + CPPUNIT_ASSERT( + cv.wait_for(lk, std::chrono::seconds(30), [&] { return accountsRemoved.load(); })); + + DRing::unregisterSignalHandlers(); + } + +private: + CPPUNIT_TEST_SUITE(MediaNegotiationTestSip); + CPPUNIT_TEST(audio_and_video_then_caller_mute_video); + CPPUNIT_TEST(audio_only_then_caller_add_video); + CPPUNIT_TEST(audio_and_video_then_caller_mute_audio); + CPPUNIT_TEST(audio_and_video_answer_muted_video_then_mute_video); + CPPUNIT_TEST(audio_and_video_then_change_video_source); + CPPUNIT_TEST_SUITE_END(); +}; std::string -MediaNegotiationTest::getUserAlias(const std::string& callId) +MediaNegotiationTest::getAccountId(const std::string& callId) { auto call = Manager::instance().getCallFromCallID(callId); if (not call) { - JAMI_WARN("Call with ID [%s] does not exist anymore!", callId.c_str()); + JAMI_WARN("Call [%s] does not exist anymore!", callId.c_str()); return {}; } auto const& account = call->getAccount().lock(); - if (not account) { + + if (account) { + return account->getAccountID(); + } + + JAMI_WARN("Account owning the call [%s] does not exist anymore!", callId.c_str()); + return {}; +} + +std::string +MediaNegotiationTest::getUserAlias(const std::string& accountId) +{ + if (accountId.empty()) { + JAMI_WARN("No account ID is empty"); return {}; } - return account->getAccountDetails()[ConfProperties::ALIAS]; + auto ret = std::find_if(callDataMap_.begin(), callDataMap_.end(), [accountId](auto const& item) { + return item.second.accountId_ == accountId; + }); + + if (ret != callDataMap_.end()) + return ret->first; + + JAMI_WARN("No matching test account %s", accountId.c_str()); + return {}; } MediaDirection @@ -398,39 +519,28 @@ MediaNegotiationTest::onMediaChangeRequested(const std::string& accountId, } void -MediaNegotiationTest::onCallStateChange(const std::string&, +MediaNegotiationTest::onCallStateChange(const std::string& accountId, const std::string& callId, const std::string& state, CallData& callData) { - auto call = Manager::instance().getCallFromCallID(callId); - if (not call) { - JAMI_WARN("Call with ID [%s] does not exist anymore!", callId.c_str()); - return; - } - - auto account = call->getAccount().lock(); - if (not account) { - JAMI_WARN("Account owning the call [%s] does not exist!", callId.c_str()); - return; - } - JAMI_INFO("Signal [%s] - user [%s] - call [%s] - state [%s]", DRing::CallSignal::StateChange::name, callData.alias_.c_str(), callId.c_str(), state.c_str()); - if (account->getAccountID() != callData.accountId_) - return; + CPPUNIT_ASSERT(accountId == callData.accountId_); { std::unique_lock lock {callData.mtx_}; callData.signals_.emplace_back( CallData::Signal(DRing::CallSignal::StateChange::name, state)); } - - if (state == "CURRENT" or state == "OVER" or state == "HUNGUP") { + // NOTE. Only states that we are interested in will notify the CV. + // If this unit test is modified to process other states, they must + // be added here. + if (state == "CURRENT" or state == "OVER" or state == "HUNGUP" or state == "RINGING") { callData.cv_.notify_one(); } } @@ -545,7 +655,7 @@ MediaNegotiationTest::waitForSignal(CallData& callData, JAMI_INFO("[%s] currently has the following signals:", callData.alias_.c_str()); for (auto const& sig : callData.signals_) { - JAMI_INFO() << "Signal [" << sig.name_ + JAMI_INFO() << "\tSignal [" << sig.name_ << (sig.event_.empty() ? "" : ("::" + sig.event_)) << "]"; } } @@ -554,20 +664,39 @@ MediaNegotiationTest::waitForSignal(CallData& callData, } void -MediaNegotiationTest::configureScenario(CallData& aliceData, CallData& bobData) +MediaNegotiationTest::configureScenario() { + // Configure Alice { - CPPUNIT_ASSERT(not aliceData.accountId_.empty()); - auto const& account = Manager::instance().getAccount(aliceData.accountId_); - aliceData.userName_ = account->getAccountDetails()[ConfProperties::USERNAME]; - aliceData.alias_ = account->getAccountDetails()[ConfProperties::ALIAS]; + CPPUNIT_ASSERT(not callDataMap_["ALICE"].accountId_.empty()); + auto const& account = Manager::instance().getAccount( + callDataMap_["ALICE"].accountId_); + callDataMap_["ALICE"].userName_ = account->getAccountDetails()[ConfProperties::USERNAME]; + callDataMap_["ALICE"].alias_ = account->getAccountDetails()[ConfProperties::ALIAS]; + account->enableIceForMedia(true); + if (isSipAccount_) { + auto sipAccount = std::dynamic_pointer_cast(account); + CPPUNIT_ASSERT(sipAccount); + sipAccount->setLocalPort(callDataMap_["ALICE"].listeningPort_); + } } + // Configure Bob { - CPPUNIT_ASSERT(not bobData.accountId_.empty()); - auto const& account = Manager::instance().getAccount(bobData.accountId_); - bobData.userName_ = account->getAccountDetails()[ConfProperties::USERNAME]; - bobData.alias_ = account->getAccountDetails()[ConfProperties::ALIAS]; + CPPUNIT_ASSERT(not callDataMap_["BOB"].accountId_.empty()); + auto const& account = Manager::instance().getAccount( + callDataMap_["BOB"].accountId_); + callDataMap_["BOB"].userName_ = account->getAccountDetails()[ConfProperties::USERNAME]; + callDataMap_["BOB"].alias_ = account->getAccountDetails()[ConfProperties::ALIAS]; + account->enableIceForMedia(true); + + if (isSipAccount_) { + auto sipAccount = std::dynamic_pointer_cast(account); + CPPUNIT_ASSERT(sipAccount); + sipAccount->setLocalPort(callDataMap_["BOB"].listeningPort_); + callDataMap_["BOB"].toUri_ = fmt::format("127.0.0.1:{}", + callDataMap_["BOB"].listeningPort_); + } } std::map> signalHandlers; @@ -578,32 +707,18 @@ MediaNegotiationTest::configureScenario(CallData& aliceData, CallData& bobData) const std::string& callId, const std::string&, const std::vector mediaList) { - auto user = getUserAlias(callId); + auto user = getUserAlias(accountId); if (not user.empty()) - onIncomingCallWithMedia(accountId, - callId, - mediaList, - user == aliceData.alias_ ? aliceData : bobData); - })); - - signalHandlers.insert(DRing::exportable_callback( - [&](const std::string& accountId, const std::string& callId, const std::string&) { - auto user = getUserAlias(callId); - if (not user.empty()) { - onIncomingCall(accountId, callId, user == aliceData.alias_ ? aliceData : bobData); - } + onIncomingCallWithMedia(accountId, callId, mediaList, callDataMap_[user]); })); signalHandlers.insert(DRing::exportable_callback( [&](const std::string& accountId, const std::string& callId, const std::vector mediaList) { - auto user = getUserAlias(callId); + auto user = getUserAlias(accountId); if (not user.empty()) - onMediaChangeRequested(accountId, - callId, - mediaList, - user == aliceData.alias_ ? aliceData : bobData); + onMediaChangeRequested(accountId, callId, mediaList, callDataMap_[user]); })); signalHandlers.insert( @@ -611,30 +726,25 @@ MediaNegotiationTest::configureScenario(CallData& aliceData, CallData& bobData) const std::string& callId, const std::string& state, signed) { - auto user = getUserAlias(callId); + auto user = getUserAlias(accountId); if (not user.empty()) - onCallStateChange(accountId, - callId, - state, - user == aliceData.alias_ ? aliceData : bobData); + onCallStateChange(accountId, callId, state, callDataMap_[user]); })); signalHandlers.insert(DRing::exportable_callback( [&](const std::string& callId, bool muted) { - auto user = getUserAlias(callId); + auto user = getUserAlias(getAccountId(callId)); if (not user.empty()) - onVideoMuted(callId, muted, user == aliceData.alias_ ? aliceData : bobData); + onVideoMuted(callId, muted, callDataMap_[user]); })); signalHandlers.insert(DRing::exportable_callback( [&](const std::string& callId, const std::string& event, const std::vector>&) { - auto user = getUserAlias(callId); + auto user = getUserAlias(getAccountId(callId)); if (not user.empty()) - onMediaNegotiationStatus(callId, - event, - user == aliceData.alias_ ? aliceData : bobData); + onMediaNegotiationStatus(callId, event, callDataMap_[user]); })); DRing::registerSignalHandlers(signalHandlers); @@ -651,11 +761,17 @@ MediaNegotiationTest::testWithScenario(CallData& aliceData, auto mediaCount = scenario.offer_.size(); CPPUNIT_ASSERT_EQUAL(mediaCount, scenario.answer_.size()); - auto const& aliceCall = std::dynamic_pointer_cast( - (Manager::instance().getAccount(aliceData.accountId_)) - ->newOutgoingCall(bobData.userName_, - MediaAttribute::mediaAttributesToMediaMaps(scenario.offer_))); + aliceData.callId_ = DRing::placeCallWithMedia(aliceData.accountId_, + isSipAccount_ ? bobData.toUri_ + : callDataMap_["BOB"].userName_, + MediaAttribute::mediaAttributesToMediaMaps( + scenario.offer_)); + CPPUNIT_ASSERT(not aliceData.callId_.empty()); + + auto aliceCall = std::static_pointer_cast( + Manager::instance().getCallFromCallID(aliceData.callId_)); CPPUNIT_ASSERT(aliceCall); + aliceData.callId_ = aliceCall->getCallId(); JAMI_INFO("ALICE [%s] started a call with BOB [%s] and wait for answer", @@ -815,7 +931,7 @@ MediaNegotiationTest::audio_and_video_then_caller_mute_video() { JAMI_INFO("=== Begin test %s ===", __FUNCTION__); - configureScenario(aliceData_, bobData_); + configureScenario(); MediaAttribute defaultAudio(MediaType::MEDIA_AUDIO); defaultAudio.label_ = "audio_0"; @@ -846,7 +962,7 @@ MediaNegotiationTest::audio_and_video_then_caller_mute_video() scenario.expectMediaRenegotiation_ = true; scenario.expectMediaChangeRequest_ = false; - testWithScenario(aliceData_, bobData_, scenario); + testWithScenario(callDataMap_["ALICE"], callDataMap_["BOB"], scenario); DRing::unregisterSignalHandlers(); @@ -858,7 +974,7 @@ MediaNegotiationTest::audio_only_then_caller_add_video() { JAMI_INFO("=== Begin test %s ===", __FUNCTION__); - configureScenario(aliceData_, bobData_); + configureScenario(); MediaAttribute defaultAudio(MediaType::MEDIA_AUDIO); defaultAudio.label_ = "audio_0"; @@ -884,7 +1000,7 @@ MediaNegotiationTest::audio_only_then_caller_add_video() scenario.expectMediaRenegotiation_ = true; scenario.expectMediaChangeRequest_ = true; - testWithScenario(aliceData_, bobData_, scenario); + testWithScenario(callDataMap_["ALICE"], callDataMap_["BOB"], scenario); DRing::unregisterSignalHandlers(); @@ -896,7 +1012,7 @@ MediaNegotiationTest::audio_and_video_then_caller_mute_audio() { JAMI_INFO("=== Begin test %s ===", __FUNCTION__); - configureScenario(aliceData_, bobData_); + configureScenario(); MediaAttribute defaultAudio(MediaType::MEDIA_AUDIO); defaultAudio.label_ = "audio_0"; @@ -928,7 +1044,7 @@ MediaNegotiationTest::audio_and_video_then_caller_mute_audio() scenario.expectMediaRenegotiation_ = false; scenario.expectMediaChangeRequest_ = false; - testWithScenario(aliceData_, bobData_, scenario); + testWithScenario(callDataMap_["ALICE"], callDataMap_["BOB"], scenario); DRing::unregisterSignalHandlers(); @@ -940,7 +1056,7 @@ MediaNegotiationTest::audio_and_video_answer_muted_video_then_mute_video() { JAMI_INFO("=== Begin test %s ===", __FUNCTION__); - configureScenario(aliceData_, bobData_); + configureScenario(); MediaAttribute defaultAudio(MediaType::MEDIA_AUDIO); defaultAudio.label_ = "audio_0"; @@ -973,7 +1089,7 @@ MediaNegotiationTest::audio_and_video_answer_muted_video_then_mute_video() scenario.expectMediaChangeRequest_ = false; scenario.expectMediaRenegotiation_ = true; - testWithScenario(aliceData_, bobData_, scenario); + testWithScenario(callDataMap_["ALICE"], callDataMap_["BOB"], scenario); DRing::unregisterSignalHandlers(); @@ -985,7 +1101,7 @@ MediaNegotiationTest::audio_and_video_then_change_video_source() { JAMI_INFO("=== Begin test %s ===", __FUNCTION__); - configureScenario(aliceData_, bobData_); + configureScenario(); MediaAttribute defaultAudio(MediaType::MEDIA_AUDIO); defaultAudio.label_ = "audio_0"; @@ -1018,14 +1134,17 @@ MediaNegotiationTest::audio_and_video_then_change_video_source() scenario.expectMediaRenegotiation_ = true; scenario.expectMediaChangeRequest_ = false; - testWithScenario(aliceData_, bobData_, scenario); + testWithScenario(callDataMap_["ALICE"], callDataMap_["BOB"], scenario); DRing::unregisterSignalHandlers(); JAMI_INFO("=== End test %s ===", __FUNCTION__); } +CPPUNIT_TEST_SUITE_NAMED_REGISTRATION(MediaNegotiationTestJami, MediaNegotiationTestJami::name()); +CPPUNIT_TEST_SUITE_NAMED_REGISTRATION(MediaNegotiationTestSip, MediaNegotiationTestSip::name()); + } // namespace test } // namespace jami -JAMI_TEST_RUNNER(jami::test::MediaNegotiationTest::name()) +JAMI_TEST_RUNNER(jami::test::MediaNegotiationTestJami::name()) diff --git a/test/unitTest/sip_account/sip_basic_calls.cpp b/test/unitTest/sip_account/sip_basic_calls.cpp index 2f77daba2..9042eafa1 100644 --- a/test/unitTest/sip_account/sip_basic_calls.cpp +++ b/test/unitTest/sip_account/sip_basic_calls.cpp @@ -53,6 +53,18 @@ struct CallData std::string event_ {}; }; + CallData() = default; + CallData(CallData&& other) = delete; + CallData(const CallData& other) + { + accountId_ = std::move(other.accountId_); + listeningPort_ = other.listeningPort_; + userName_ = std::move(other.userName_); + alias_ = std::move(other.alias_); + callId_ = std::move(other.callId_); + signals_ = std::move(other.signals_); + }; + std::string accountId_ {}; uint16_t listeningPort_ {0}; std::string userName_ {}; @@ -113,63 +125,55 @@ private: CallData& callData); // Helpers + bool addTestAccount(const std::string& alias, uint16_t port); void audio_video_call(std::vector offer, std::vector answer, bool expectedToSucceed = true, bool validateMedia = true); - static void configureTest(CallData& bob, CallData& alice, CallData& carla); - static std::string getUserAlias(const std::string& callId); + void configureTest(); + std::string getAccountId(const std::string& callId); + std::string getUserAlias(const std::string& accountId); + CallData& getCallData(const std::string& account); // Wait for a signal from the callbacks. Some signals also report the event that // triggered the signal a like the StateChange signal. static bool waitForSignal(CallData& callData, const std::string& signal, const std::string& expectedEvent = {}); -private: - CallData aliceData_; - CallData bobData_; - CallData carlaData_; + std::map callDataMap_; + std::set testAccounts_; }; CPPUNIT_TEST_SUITE_NAMED_REGISTRATION(SipBasicCallTest, SipBasicCallTest::name()); +bool +SipBasicCallTest::addTestAccount(const std::string& alias, uint16_t port) +{ + CallData callData; + callData.alias_ = alias; + callData.userName_ = alias; + callData.listeningPort_ = port; + std::map details = DRing::getAccountTemplate("SIP"); + details[ConfProperties::TYPE] = "SIP"; + details[ConfProperties::USERNAME] = alias; + details[ConfProperties::DISPLAYNAME] = alias; + details[ConfProperties::ALIAS] = alias; + details[ConfProperties::LOCAL_PORT] = std::to_string(port); + details[ConfProperties::UPNP_ENABLED] = "false"; + callData.accountId_ = Manager::instance().addAccount(details); + testAccounts_.insert(callData.accountId_); + callDataMap_.emplace(alias, std::move(callData)); + return (not callDataMap_[alias].accountId_.empty()); +} + void SipBasicCallTest::setUp() { - aliceData_.listeningPort_ = 5080; - std::map details = DRing::getAccountTemplate("SIP"); - details[ConfProperties::TYPE] = "SIP"; - details[ConfProperties::USERNAME] = "ALICE"; - details[ConfProperties::DISPLAYNAME] = "ALICE"; - details[ConfProperties::ALIAS] = "ALICE"; - details[ConfProperties::LOCAL_PORT] = std::to_string(aliceData_.listeningPort_); - details[ConfProperties::UPNP_ENABLED] = "false"; - aliceData_.accountId_ = Manager::instance().addAccount(details); - - bobData_.listeningPort_ = 5082; - details = DRing::getAccountTemplate("SIP"); - details[ConfProperties::TYPE] = "SIP"; - details[ConfProperties::USERNAME] = "BOB"; - details[ConfProperties::DISPLAYNAME] = "BOB"; - details[ConfProperties::ALIAS] = "BOB"; - details[ConfProperties::LOCAL_PORT] = std::to_string(bobData_.listeningPort_); - details[ConfProperties::UPNP_ENABLED] = "false"; - bobData_.accountId_ = Manager::instance().addAccount(details); - - carlaData_.listeningPort_ = 5084; - details = DRing::getAccountTemplate("SIP"); - details[ConfProperties::TYPE] = "SIP"; - details[ConfProperties::USERNAME] = "CARLA"; - details[ConfProperties::DISPLAYNAME] = "CARLA"; - details[ConfProperties::ALIAS] = "CARLA"; - details[ConfProperties::LOCAL_PORT] = std::to_string(carlaData_.listeningPort_); - details[ConfProperties::UPNP_ENABLED] = "false"; - carlaData_.accountId_ = Manager::instance().addAccount(details); - JAMI_INFO("Initialize accounts ..."); - auto aliceAccount = Manager::instance().getAccount(aliceData_.accountId_); - auto bobAccount = Manager::instance().getAccount(bobData_.accountId_); - auto carlaAccount = Manager::instance().getAccount(carlaData_.accountId_); + + CPPUNIT_ASSERT(addTestAccount("ALICE", 5080)); + CPPUNIT_ASSERT(addTestAccount("BOB", 5082)); + CPPUNIT_ASSERT(addTestAccount("CARLA", 5084)); } void @@ -181,20 +185,32 @@ SipBasicCallTest::tearDown() std::mutex mtx; std::unique_lock lk {mtx}; std::condition_variable cv; - auto currentAccSize = Manager::instance().getAccountList().size(); std::atomic_bool accountsRemoved {false}; confHandlers.insert( DRing::exportable_callback([&]() { - if (Manager::instance().getAccountList().size() <= currentAccSize - 2) { + auto currAccounts = Manager::instance().getAccountList(); + for (auto iter = testAccounts_.begin(); iter != testAccounts_.end();) { + auto item = std::find(currAccounts.begin(), currAccounts.end(), *iter); + if (item == currAccounts.end()) { + JAMI_INFO("Removing account %s", (*iter).c_str()); + iter = testAccounts_.erase(iter); + } else { + iter++; + } + } + + if (testAccounts_.empty()) { accountsRemoved = true; + JAMI_INFO("All accounts removed..."); cv.notify_one(); } })); + DRing::registerSignalHandlers(confHandlers); - Manager::instance().removeAccount(aliceData_.accountId_, true); - Manager::instance().removeAccount(bobData_.accountId_, true); - Manager::instance().removeAccount(carlaData_.accountId_, true); + Manager::instance().removeAccount(callDataMap_["ALICE"].accountId_, true); + Manager::instance().removeAccount(callDataMap_["BOB"].accountId_, true); + Manager::instance().removeAccount(callDataMap_["CARLA"].accountId_, true); CPPUNIT_ASSERT( cv.wait_for(lk, std::chrono::seconds(30), [&] { return accountsRemoved.load(); })); @@ -203,21 +219,42 @@ SipBasicCallTest::tearDown() } std::string -SipBasicCallTest::getUserAlias(const std::string& callId) +SipBasicCallTest::getAccountId(const std::string& callId) { auto call = Manager::instance().getCallFromCallID(callId); if (not call) { - JAMI_WARN("Call with ID [%s] does not exist anymore!", callId.c_str()); + JAMI_WARN("Call [%s] does not exist anymore!", callId.c_str()); return {}; } auto const& account = call->getAccount().lock(); - if (not account) { + + if (account) { + return account->getAccountID(); + } + + JAMI_WARN("Account owning the call [%s] does not exist anymore!", callId.c_str()); + return {}; +} + +std::string +SipBasicCallTest::getUserAlias(const std::string& accountId) +{ + if (accountId.empty()) { + JAMI_WARN("No account ID is empty"); return {}; } - return account->getAccountDetails()[ConfProperties::ALIAS]; + auto ret = std::find_if(callDataMap_.begin(), callDataMap_.end(), [accountId](auto const& item) { + return item.second.accountId_ == accountId; + }); + + if (ret != callDataMap_.end()) + return ret->first; + + JAMI_WARN("No matching test account %s", accountId.c_str()); + return {}; } void @@ -248,31 +285,18 @@ SipBasicCallTest::onIncomingCallWithMedia(const std::string& accountId, } void -SipBasicCallTest::onCallStateChange(const std::string&, +SipBasicCallTest::onCallStateChange(const std::string& accountId, const std::string& callId, const std::string& state, CallData& callData) { - auto call = Manager::instance().getCallFromCallID(callId); - if (not call) { - JAMI_WARN("Call with ID [%s] does not exist anymore!", callId.c_str()); - return; - } - - auto account = call->getAccount().lock(); - if (not account) { - JAMI_WARN("Account owning the call [%s] does not exist!", callId.c_str()); - return; - } - JAMI_INFO("Signal [%s] - user [%s] - call [%s] - state [%s]", DRing::CallSignal::StateChange::name, callData.alias_.c_str(), callId.c_str(), state.c_str()); - if (account->getAccountID() != callData.accountId_) - return; + CPPUNIT_ASSERT(accountId == callData.accountId_); { std::unique_lock lock {callData.mtx_}; @@ -362,7 +386,7 @@ SipBasicCallTest::waitForSignal(CallData& callData, JAMI_INFO("[%s] currently has the following signals:", callData.alias_.c_str()); for (auto const& sig : callData.signals_) { - JAMI_INFO() << "Signal [" << sig.name_ + JAMI_INFO() << "\tSignal [" << sig.name_ << (sig.event_.empty() ? "" : ("::" + sig.event_)) << "]"; } } @@ -371,33 +395,29 @@ SipBasicCallTest::waitForSignal(CallData& callData, } void -SipBasicCallTest::configureTest(CallData& aliceData, CallData& bobData, CallData& carlaData) +SipBasicCallTest::configureTest() { { - CPPUNIT_ASSERT(not aliceData.accountId_.empty()); - auto const& account = Manager::instance().getAccount(aliceData.accountId_); - aliceData.userName_ = account->getAccountDetails()[ConfProperties::USERNAME]; - aliceData.alias_ = account->getAccountDetails()[ConfProperties::ALIAS]; - account->setLocalPort(aliceData.listeningPort_); + CPPUNIT_ASSERT(not callDataMap_["ALICE"].accountId_.empty()); + auto const& account = Manager::instance().getAccount( + callDataMap_["ALICE"].accountId_); + account->setLocalPort(callDataMap_["ALICE"].listeningPort_); account->enableIceForMedia(true); } { - CPPUNIT_ASSERT(not bobData.accountId_.empty()); - auto const& account = Manager::instance().getAccount(bobData.accountId_); - bobData.userName_ = account->getAccountDetails()[ConfProperties::USERNAME]; - bobData.alias_ = account->getAccountDetails()[ConfProperties::ALIAS]; - account->setLocalPort(bobData.listeningPort_); + CPPUNIT_ASSERT(not callDataMap_["BOB"].accountId_.empty()); + auto const& account = Manager::instance().getAccount( + callDataMap_["BOB"].accountId_); + account->setLocalPort(callDataMap_["BOB"].listeningPort_); } -#if 1 + { - CPPUNIT_ASSERT(not carlaData.accountId_.empty()); - auto const& account = Manager::instance().getAccount(carlaData.accountId_); - carlaData.userName_ = account->getAccountDetails()[ConfProperties::USERNAME]; - carlaData.alias_ = account->getAccountDetails()[ConfProperties::ALIAS]; - account->setLocalPort(carlaData.listeningPort_); + CPPUNIT_ASSERT(not callDataMap_["CARLA"].accountId_.empty()); + auto const& account = Manager::instance().getAccount( + callDataMap_["CARLA"].accountId_); + account->setLocalPort(callDataMap_["CARLA"].listeningPort_); } -#endif std::map> signalHandlers; @@ -407,14 +427,10 @@ SipBasicCallTest::configureTest(CallData& aliceData, CallData& bobData, CallData const std::string& callId, const std::string&, const std::vector mediaList) { - auto user = getUserAlias(callId); - if (not user.empty()) - onIncomingCallWithMedia(accountId, - callId, - mediaList, - user == aliceData.alias_ - ? aliceData - : (user == bobData.alias_ ? bobData : carlaData)); + auto alias = getUserAlias(accountId); + if (not alias.empty()) { + onIncomingCallWithMedia(accountId, callId, mediaList, callDataMap_[alias]); + } })); signalHandlers.insert( @@ -422,27 +438,18 @@ SipBasicCallTest::configureTest(CallData& aliceData, CallData& bobData, CallData const std::string& callId, const std::string& state, signed) { - auto user = getUserAlias(callId); - if (not user.empty()) - onCallStateChange(accountId, - callId, - state, - user == aliceData.alias_ - ? aliceData - : (user == bobData.alias_ ? bobData : carlaData)); + auto alias = getUserAlias(accountId); + if (not alias.empty()) + onCallStateChange(accountId, callId, state, callDataMap_[alias]); })); signalHandlers.insert(DRing::exportable_callback( [&](const std::string& callId, const std::string& event, const std::vector>& /* mediaList */) { - auto user = getUserAlias(callId); - if (not user.empty()) - onMediaNegotiationStatus(callId, - event, - user == aliceData.alias_ - ? aliceData - : (user == bobData.alias_ ? bobData : carlaData)); + auto alias = getUserAlias(getAccountId(callId)); + if (not alias.empty()) + onMediaNegotiationStatus(callId, event, callDataMap_[alias]); })); DRing::registerSignalHandlers(signalHandlers); @@ -454,60 +461,63 @@ SipBasicCallTest::audio_video_call(std::vector offer, bool expectedToSucceed, bool validateMedia) { - configureTest(aliceData_, bobData_, carlaData_); + configureTest(); JAMI_INFO("=== Start a call and validate ==="); - std::string bobUri = bobData_.userName_ - + "@127.0.0.1:" + std::to_string(bobData_.listeningPort_); + std::string bobUri = callDataMap_["BOB"].userName_ + + "@127.0.0.1:" + std::to_string(callDataMap_["BOB"].listeningPort_); - aliceData_.callId_ = DRing::placeCallWithMedia(aliceData_.accountId_, - bobUri, - MediaAttribute::mediaAttributesToMediaMaps( - offer)); + callDataMap_["ALICE"].callId_ + = DRing::placeCallWithMedia(callDataMap_["ALICE"].accountId_, + bobUri, + MediaAttribute::mediaAttributesToMediaMaps(offer)); - CPPUNIT_ASSERT(not aliceData_.callId_.empty()); + CPPUNIT_ASSERT(not callDataMap_["ALICE"].callId_.empty()); JAMI_INFO("ALICE [%s] started a call with BOB [%s] and wait for answer", - aliceData_.accountId_.c_str(), - bobData_.accountId_.c_str()); + callDataMap_["ALICE"].accountId_.c_str(), + callDataMap_["BOB"].accountId_.c_str()); // Give it some time to ring std::this_thread::sleep_for(std::chrono::seconds(2)); // Wait for call to be processed. - CPPUNIT_ASSERT( - waitForSignal(aliceData_, DRing::CallSignal::StateChange::name, StateEvent::RINGING)); + CPPUNIT_ASSERT(waitForSignal(callDataMap_["ALICE"], + DRing::CallSignal::StateChange::name, + StateEvent::RINGING)); // Wait for incoming call signal. - CPPUNIT_ASSERT(waitForSignal(bobData_, DRing::CallSignal::IncomingCallWithMedia::name)); + CPPUNIT_ASSERT( + waitForSignal(callDataMap_["BOB"], DRing::CallSignal::IncomingCallWithMedia::name)); // Answer the call. - DRing::acceptWithMedia(bobData_.accountId_, - bobData_.callId_, + DRing::acceptWithMedia(callDataMap_["BOB"].accountId_, + callDataMap_["BOB"].callId_, MediaAttribute::mediaAttributesToMediaMaps(answer)); if (expectedToSucceed) { // Wait for media negotiation complete signal. CPPUNIT_ASSERT( - waitForSignal(bobData_, + waitForSignal(callDataMap_["BOB"], DRing::CallSignal::MediaNegotiationStatus::name, DRing::Media::MediaNegotiationStatusEvents::NEGOTIATION_SUCCESS)); // Wait for the StateChange signal. - CPPUNIT_ASSERT( - waitForSignal(bobData_, DRing::CallSignal::StateChange::name, StateEvent::CURRENT)); + CPPUNIT_ASSERT(waitForSignal(callDataMap_["BOB"], + DRing::CallSignal::StateChange::name, + StateEvent::CURRENT)); - JAMI_INFO("BOB answered the call [%s]", bobData_.callId_.c_str()); + JAMI_INFO("BOB answered the call [%s]", callDataMap_["BOB"].callId_.c_str()); // Wait for media negotiation complete signal. CPPUNIT_ASSERT( - waitForSignal(aliceData_, + waitForSignal(callDataMap_["ALICE"], DRing::CallSignal::MediaNegotiationStatus::name, DRing::Media::MediaNegotiationStatusEvents::NEGOTIATION_SUCCESS)); // Validate Alice's media if (validateMedia) { - auto call = Manager::instance().getCallFromCallID(aliceData_.callId_); + auto call = Manager::instance().getCallFromCallID(callDataMap_["ALICE"].callId_); auto activeMediaList = call->getMediaAttributeList(); CPPUNIT_ASSERT_EQUAL(offer.size(), activeMediaList.size()); // Audio @@ -523,7 +533,7 @@ SipBasicCallTest::audio_video_call(std::vector offer, // Validate Bob's media if (validateMedia) { - auto call = Manager::instance().getCallFromCallID(bobData_.callId_); + auto call = Manager::instance().getCallFromCallID(callDataMap_["BOB"].callId_); auto activeMediaList = call->getMediaAttributeList(); CPPUNIT_ASSERT_EQUAL(answer.size(), activeMediaList.size()); // Audio @@ -542,19 +552,21 @@ SipBasicCallTest::audio_video_call(std::vector offer, // Bob hang-up. JAMI_INFO("Hang up BOB's call and wait for ALICE to hang up"); - DRing::hangUp(bobData_.accountId_, bobData_.callId_); + DRing::hangUp(callDataMap_["BOB"].accountId_, callDataMap_["BOB"].callId_); } else { // The media negotiation for the call is expected to fail, so we // should receive the signal. - CPPUNIT_ASSERT( - waitForSignal(bobData_, DRing::CallSignal::StateChange::name, StateEvent::FAILURE)); + CPPUNIT_ASSERT(waitForSignal(callDataMap_["BOB"], + DRing::CallSignal::StateChange::name, + StateEvent::FAILURE)); } // The hang-up signal will be emitted on caller's side (Alice) in both // success failure scenarios. - CPPUNIT_ASSERT( - waitForSignal(aliceData_, DRing::CallSignal::StateChange::name, StateEvent::HUNGUP)); + CPPUNIT_ASSERT(waitForSignal(callDataMap_["ALICE"], + DRing::CallSignal::StateChange::name, + StateEvent::HUNGUP)); JAMI_INFO("Call terminated on both sides"); } @@ -567,8 +579,9 @@ SipBasicCallTest::audio_only_test() JAMI_INFO("=== Begin test %s ===", __FUNCTION__); - auto const aliceAcc = Manager::instance().getAccount(aliceData_.accountId_); - auto const bobAcc = Manager::instance().getAccount(bobData_.accountId_); + auto const aliceAcc = Manager::instance().getAccount( + callDataMap_["ALICE"].accountId_); + auto const bobAcc = Manager::instance().getAccount(callDataMap_["BOB"].accountId_); std::vector offer; std::vector answer; @@ -605,8 +618,9 @@ SipBasicCallTest::audio_video_test() { JAMI_INFO("=== Begin test %s ===", __FUNCTION__); - auto const aliceAcc = Manager::instance().getAccount(aliceData_.accountId_); - auto const bobAcc = Manager::instance().getAccount(bobData_.accountId_); + auto const aliceAcc = Manager::instance().getAccount( + callDataMap_["ALICE"].accountId_); + auto const bobAcc = Manager::instance().getAccount(callDataMap_["BOB"].accountId_); std::vector offer; std::vector answer; @@ -643,7 +657,7 @@ SipBasicCallTest::peer_answer_with_all_media_disabled() { JAMI_INFO("=== Begin test %s ===", __FUNCTION__); - auto const bobAcc = Manager::instance().getAccount(bobData_.accountId_); + auto const bobAcc = Manager::instance().getAccount(callDataMap_["BOB"].accountId_); std::vector offer; std::vector answer; @@ -672,8 +686,9 @@ SipBasicCallTest::hold_resume_test() { JAMI_INFO("=== Begin test %s ===", __FUNCTION__); - auto const aliceAcc = Manager::instance().getAccount(aliceData_.accountId_); - auto const bobAcc = Manager::instance().getAccount(bobData_.accountId_); + auto const aliceAcc = Manager::instance().getAccount( + callDataMap_["ALICE"].accountId_); + auto const bobAcc = Manager::instance().getAccount(callDataMap_["BOB"].accountId_); std::vector offer; std::vector answer; @@ -683,69 +698,76 @@ SipBasicCallTest::hold_resume_test() audio.enabled_ = true; audio.label_ = "audio_0"; + video.enabled_ = true; + video.label_ = "video_0"; // Alice's media offer.emplace_back(audio); + offer.emplace_back(video); // Bob's media answer.emplace_back(audio); + answer.emplace_back(video); { - configureTest(aliceData_, bobData_, carlaData_); + configureTest(); JAMI_INFO("=== Start a call and validate ==="); - std::string bobUri = bobData_.userName_ - + "@127.0.0.1:" + std::to_string(bobData_.listeningPort_); + std::string bobUri = callDataMap_["BOB"].userName_ + + "@127.0.0.1:" + std::to_string(callDataMap_["BOB"].listeningPort_); - aliceData_.callId_ = DRing::placeCallWithMedia(aliceData_.accountId_, - bobUri, - MediaAttribute::mediaAttributesToMediaMaps( - offer)); + callDataMap_["ALICE"].callId_ + = DRing::placeCallWithMedia(callDataMap_["ALICE"].accountId_, + bobUri, + MediaAttribute::mediaAttributesToMediaMaps(offer)); - CPPUNIT_ASSERT(not aliceData_.callId_.empty()); + CPPUNIT_ASSERT(not callDataMap_["ALICE"].callId_.empty()); JAMI_INFO("ALICE [%s] started a call with BOB [%s] and wait for answer", - aliceData_.accountId_.c_str(), - bobData_.accountId_.c_str()); + callDataMap_["ALICE"].accountId_.c_str(), + callDataMap_["BOB"].accountId_.c_str()); // Give it some time to ring std::this_thread::sleep_for(std::chrono::seconds(2)); // Wait for call to be processed. - CPPUNIT_ASSERT( - waitForSignal(aliceData_, DRing::CallSignal::StateChange::name, StateEvent::RINGING)); + CPPUNIT_ASSERT(waitForSignal(callDataMap_["ALICE"], + DRing::CallSignal::StateChange::name, + StateEvent::RINGING)); // Wait for incoming call signal. - CPPUNIT_ASSERT(waitForSignal(bobData_, DRing::CallSignal::IncomingCallWithMedia::name)); + CPPUNIT_ASSERT( + waitForSignal(callDataMap_["BOB"], DRing::CallSignal::IncomingCallWithMedia::name)); // Answer the call. - DRing::acceptWithMedia(bobData_.accountId_, - bobData_.callId_, + DRing::acceptWithMedia(callDataMap_["BOB"].accountId_, + callDataMap_["BOB"].callId_, MediaAttribute::mediaAttributesToMediaMaps(answer)); // Wait for media negotiation complete signal. CPPUNIT_ASSERT( - waitForSignal(bobData_, + waitForSignal(callDataMap_["BOB"], DRing::CallSignal::MediaNegotiationStatus::name, DRing::Media::MediaNegotiationStatusEvents::NEGOTIATION_SUCCESS)); // Wait for the StateChange signal. - CPPUNIT_ASSERT( - waitForSignal(bobData_, DRing::CallSignal::StateChange::name, StateEvent::CURRENT)); + CPPUNIT_ASSERT(waitForSignal(callDataMap_["BOB"], + DRing::CallSignal::StateChange::name, + StateEvent::CURRENT)); - JAMI_INFO("BOB answered the call [%s]", bobData_.callId_.c_str()); + JAMI_INFO("BOB answered the call [%s]", callDataMap_["BOB"].callId_.c_str()); // Wait for media negotiation complete signal. CPPUNIT_ASSERT( - waitForSignal(aliceData_, + waitForSignal(callDataMap_["ALICE"], DRing::CallSignal::MediaNegotiationStatus::name, DRing::Media::MediaNegotiationStatusEvents::NEGOTIATION_SUCCESS)); // Validate Alice's side of media direction { auto call = std::static_pointer_cast( - Manager::instance().getCallFromCallID(aliceData_.callId_)); + Manager::instance().getCallFromCallID(callDataMap_["ALICE"].callId_)); auto& sdp = call->getSDP(); auto mediaStreams = sdp.getMediaSlots(); for (auto const& media : mediaStreams) { @@ -757,7 +779,7 @@ SipBasicCallTest::hold_resume_test() // Validate Bob's side of media direction { auto call = std::static_pointer_cast( - Manager::instance().getCallFromCallID(bobData_.callId_)); + Manager::instance().getCallFromCallID(callDataMap_["BOB"].callId_)); auto& sdp = call->getSDP(); auto mediaStreams = sdp.getMediaSlots(); for (auto const& media : mediaStreams) { @@ -771,19 +793,20 @@ SipBasicCallTest::hold_resume_test() // Hold/Resume the call JAMI_INFO("Hold Alice's call"); - DRing::hold(aliceData_.accountId_, aliceData_.callId_); + DRing::hold(callDataMap_["ALICE"].accountId_, callDataMap_["ALICE"].callId_); // Wait for the StateChange signal. - CPPUNIT_ASSERT( - waitForSignal(aliceData_, DRing::CallSignal::StateChange::name, StateEvent::HOLD)); + CPPUNIT_ASSERT(waitForSignal(callDataMap_["ALICE"], + DRing::CallSignal::StateChange::name, + StateEvent::HOLD)); // Wait for media negotiation complete signal. CPPUNIT_ASSERT( - waitForSignal(aliceData_, + waitForSignal(callDataMap_["ALICE"], DRing::CallSignal::MediaNegotiationStatus::name, DRing::Media::MediaNegotiationStatusEvents::NEGOTIATION_SUCCESS)); { // Validate hold state. - auto call = Manager::instance().getCallFromCallID(aliceData_.callId_); + auto call = Manager::instance().getCallFromCallID(callDataMap_["ALICE"].callId_); auto activeMediaList = call->getMediaAttributeList(); for (const auto& mediaAttr : activeMediaList) { CPPUNIT_ASSERT(mediaAttr.onHold_); @@ -793,7 +816,7 @@ SipBasicCallTest::hold_resume_test() // Validate Alice's side of media direction { auto call = std::static_pointer_cast( - Manager::instance().getCallFromCallID(aliceData_.callId_)); + Manager::instance().getCallFromCallID(callDataMap_["ALICE"].callId_)); auto& sdp = call->getSDP(); auto mediaStreams = sdp.getMediaSlots(); for (auto const& media : mediaStreams) { @@ -805,7 +828,7 @@ SipBasicCallTest::hold_resume_test() // Validate Bob's side of media direction { auto call = std::static_pointer_cast( - Manager::instance().getCallFromCallID(bobData_.callId_)); + Manager::instance().getCallFromCallID(callDataMap_["BOB"].callId_)); auto& sdp = call->getSDP(); auto mediaStreams = sdp.getMediaSlots(); for (auto const& media : mediaStreams) { @@ -817,22 +840,23 @@ SipBasicCallTest::hold_resume_test() std::this_thread::sleep_for(std::chrono::seconds(2)); JAMI_INFO("Resume Alice's call"); - DRing::unhold(aliceData_.accountId_, aliceData_.callId_); + DRing::unhold(callDataMap_["ALICE"].accountId_, callDataMap_["ALICE"].callId_); // Wait for the StateChange signal. - CPPUNIT_ASSERT( - waitForSignal(aliceData_, DRing::CallSignal::StateChange::name, StateEvent::CURRENT)); + CPPUNIT_ASSERT(waitForSignal(callDataMap_["ALICE"], + DRing::CallSignal::StateChange::name, + StateEvent::CURRENT)); // Wait for media negotiation complete signal. CPPUNIT_ASSERT( - waitForSignal(aliceData_, + waitForSignal(callDataMap_["ALICE"], DRing::CallSignal::MediaNegotiationStatus::name, DRing::Media::MediaNegotiationStatusEvents::NEGOTIATION_SUCCESS)); std::this_thread::sleep_for(std::chrono::seconds(2)); { // Validate hold state. - auto call = Manager::instance().getCallFromCallID(aliceData_.callId_); + auto call = Manager::instance().getCallFromCallID(callDataMap_["ALICE"].callId_); auto activeMediaList = call->getMediaAttributeList(); for (const auto& mediaAttr : activeMediaList) { CPPUNIT_ASSERT(not mediaAttr.onHold_); @@ -841,13 +865,14 @@ SipBasicCallTest::hold_resume_test() // Bob hang-up. JAMI_INFO("Hang up BOB's call and wait for ALICE to hang up"); - DRing::hangUp(bobData_.accountId_, bobData_.callId_); + DRing::hangUp(callDataMap_["BOB"].accountId_, callDataMap_["BOB"].callId_); // The hang-up signal will be emitted on caller's side (Alice) in both // success and failure scenarios. - CPPUNIT_ASSERT( - waitForSignal(aliceData_, DRing::CallSignal::StateChange::name, StateEvent::HUNGUP)); + CPPUNIT_ASSERT(waitForSignal(callDataMap_["ALICE"], + DRing::CallSignal::StateChange::name, + StateEvent::HUNGUP)); JAMI_INFO("Call terminated on both sides"); } @@ -890,9 +915,11 @@ SipBasicCallTest::blind_transfer_test() JAMI_INFO("=== Begin test %s ===", __FUNCTION__); - auto const aliceAcc = Manager::instance().getAccount(aliceData_.accountId_); - auto const bobAcc = Manager::instance().getAccount(bobData_.accountId_); - auto const carlaAcc = Manager::instance().getAccount(carlaData_.accountId_); + auto const aliceAcc = Manager::instance().getAccount( + callDataMap_["ALICE"].accountId_); + auto const bobAcc = Manager::instance().getAccount(callDataMap_["BOB"].accountId_); + auto const carlaAcc = Manager::instance().getAccount( + callDataMap_["CARLA"].accountId_); aliceAcc->enableIceForMedia(false); bobAcc->enableIceForMedia(false); @@ -913,52 +940,55 @@ SipBasicCallTest::blind_transfer_test() // Bob's media answer.emplace_back(audio); - configureTest(aliceData_, bobData_, carlaData_); + configureTest(); JAMI_INFO("=== Start a call and validate ==="); - std::string bobUri = bobData_.userName_ - + "@127.0.0.1:" + std::to_string(bobData_.listeningPort_); + std::string bobUri = callDataMap_["BOB"].userName_ + + "@127.0.0.1:" + std::to_string(callDataMap_["BOB"].listeningPort_); - aliceData_.callId_ = DRing::placeCallWithMedia(aliceData_.accountId_, - bobUri, - MediaAttribute::mediaAttributesToMediaMaps( - offer)); + callDataMap_["ALICE"].callId_ + = DRing::placeCallWithMedia(callDataMap_["ALICE"].accountId_, + bobUri, + MediaAttribute::mediaAttributesToMediaMaps(offer)); - CPPUNIT_ASSERT(not aliceData_.callId_.empty()); + CPPUNIT_ASSERT(not callDataMap_["ALICE"].callId_.empty()); JAMI_INFO("ALICE [%s] started a call with BOB [%s] and wait for answer", - aliceData_.accountId_.c_str(), - bobData_.accountId_.c_str()); + callDataMap_["ALICE"].accountId_.c_str(), + callDataMap_["BOB"].accountId_.c_str()); // Give it some time to ring std::this_thread::sleep_for(std::chrono::seconds(2)); // Wait for call to be processed. - CPPUNIT_ASSERT( - waitForSignal(aliceData_, DRing::CallSignal::StateChange::name, StateEvent::RINGING)); + CPPUNIT_ASSERT(waitForSignal(callDataMap_["ALICE"], + DRing::CallSignal::StateChange::name, + StateEvent::RINGING)); // Wait for incoming call signal. - CPPUNIT_ASSERT(waitForSignal(bobData_, DRing::CallSignal::IncomingCallWithMedia::name)); + CPPUNIT_ASSERT( + waitForSignal(callDataMap_["BOB"], DRing::CallSignal::IncomingCallWithMedia::name)); // Answer the call. - DRing::acceptWithMedia(bobData_.accountId_, - bobData_.callId_, + DRing::acceptWithMedia(callDataMap_["BOB"].accountId_, + callDataMap_["BOB"].callId_, MediaAttribute::mediaAttributesToMediaMaps(answer)); // Wait for media negotiation complete signal. - CPPUNIT_ASSERT(waitForSignal(bobData_, + CPPUNIT_ASSERT(waitForSignal(callDataMap_["BOB"], DRing::CallSignal::MediaNegotiationStatus::name, DRing::Media::MediaNegotiationStatusEvents::NEGOTIATION_SUCCESS)); // Wait for the StateChange signal. - CPPUNIT_ASSERT( - waitForSignal(bobData_, DRing::CallSignal::StateChange::name, StateEvent::CURRENT)); + CPPUNIT_ASSERT(waitForSignal(callDataMap_["BOB"], + DRing::CallSignal::StateChange::name, + StateEvent::CURRENT)); - JAMI_INFO("BOB answered the call [%s]", bobData_.callId_.c_str()); + JAMI_INFO("BOB answered the call [%s]", callDataMap_["BOB"].callId_.c_str()); // Wait for media negotiation complete signal. - CPPUNIT_ASSERT(waitForSignal(aliceData_, + CPPUNIT_ASSERT(waitForSignal(callDataMap_["ALICE"], DRing::CallSignal::MediaNegotiationStatus::name, DRing::Media::MediaNegotiationStatusEvents::NEGOTIATION_SUCCESS)); @@ -967,52 +997,59 @@ SipBasicCallTest::blind_transfer_test() // Transfer the call to Carla std::string carlaUri = carlaAcc->getUsername() - + "@127.0.0.1:" + std::to_string(carlaData_.listeningPort_); + + "@127.0.0.1:" + std::to_string(callDataMap_["CARLA"].listeningPort_); - DRing::transfer(aliceData_.accountId_, aliceData_.callId_, carlaUri); // TODO. Check trim + DRing::transfer(callDataMap_["ALICE"].accountId_, + callDataMap_["ALICE"].callId_, + carlaUri); // TODO. Check trim // Expect Alice's call to end. - CPPUNIT_ASSERT( - waitForSignal(aliceData_, DRing::CallSignal::StateChange::name, StateEvent::HUNGUP)); + CPPUNIT_ASSERT(waitForSignal(callDataMap_["ALICE"], + DRing::CallSignal::StateChange::name, + StateEvent::HUNGUP)); // Wait for the new call to be processed. - CPPUNIT_ASSERT( - waitForSignal(bobData_, DRing::CallSignal::StateChange::name, StateEvent::RINGING)); + CPPUNIT_ASSERT(waitForSignal(callDataMap_["BOB"], + DRing::CallSignal::StateChange::name, + StateEvent::RINGING)); // Wait for incoming call signal. - CPPUNIT_ASSERT(waitForSignal(carlaData_, DRing::CallSignal::IncomingCallWithMedia::name)); + CPPUNIT_ASSERT( + waitForSignal(callDataMap_["CARLA"], DRing::CallSignal::IncomingCallWithMedia::name)); // Let it ring std::this_thread::sleep_for(std::chrono::seconds(2)); // Carla answers the call. - DRing::acceptWithMedia(carlaData_.accountId_, - carlaData_.callId_, + DRing::acceptWithMedia(callDataMap_["CARLA"].accountId_, + callDataMap_["CARLA"].callId_, MediaAttribute::mediaAttributesToMediaMaps(answer)); // Wait for media negotiation complete signal. - CPPUNIT_ASSERT(waitForSignal(carlaData_, + CPPUNIT_ASSERT(waitForSignal(callDataMap_["CARLA"], DRing::CallSignal::MediaNegotiationStatus::name, DRing::Media::MediaNegotiationStatusEvents::NEGOTIATION_SUCCESS)); // Wait for the StateChange signal. - CPPUNIT_ASSERT( - waitForSignal(carlaData_, DRing::CallSignal::StateChange::name, StateEvent::CURRENT)); + CPPUNIT_ASSERT(waitForSignal(callDataMap_["CARLA"], + DRing::CallSignal::StateChange::name, + StateEvent::CURRENT)); - JAMI_INFO("CARLA answered the call [%s]", bobData_.callId_.c_str()); + JAMI_INFO("CARLA answered the call [%s]", callDataMap_["BOB"].callId_.c_str()); // Wait for media negotiation complete signal. - CPPUNIT_ASSERT(waitForSignal(bobData_, + CPPUNIT_ASSERT(waitForSignal(callDataMap_["BOB"], DRing::CallSignal::MediaNegotiationStatus::name, DRing::Media::MediaNegotiationStatusEvents::NEGOTIATION_SUCCESS)); // Wait for the StateChange signal. - CPPUNIT_ASSERT( - waitForSignal(bobData_, DRing::CallSignal::StateChange::name, StateEvent::CURRENT)); + CPPUNIT_ASSERT(waitForSignal(callDataMap_["BOB"], + DRing::CallSignal::StateChange::name, + StateEvent::CURRENT)); // Validate Carla's side of media direction { auto call = std::static_pointer_cast( - Manager::instance().getCallFromCallID(carlaData_.callId_)); + Manager::instance().getCallFromCallID(callDataMap_["CARLA"].callId_)); auto& sdp = call->getSDP(); auto mediaStreams = sdp.getMediaSlots(); for (auto const& media : mediaStreams) { @@ -1023,7 +1060,7 @@ SipBasicCallTest::blind_transfer_test() // NOTE: // For now, we dont validate Bob's media because currently - // test does not update BOB's call ID (bobData_.callId_ + // test does not update BOB's call ID (callDataMap_["BOB"].callId_ // still point to the first call). // It seems there is no easy way to get the ID of the new call // made by Bob to Carla. @@ -1033,11 +1070,12 @@ SipBasicCallTest::blind_transfer_test() // Bob hang-up. JAMI_INFO("Hang up CARLA's call and wait for CARLA to hang up"); - DRing::hangUp(carlaData_.accountId_, carlaData_.callId_); + DRing::hangUp(callDataMap_["CARLA"].accountId_, callDataMap_["CARLA"].callId_); // Expect end call on Carla's side. - CPPUNIT_ASSERT( - waitForSignal(carlaData_, DRing::CallSignal::StateChange::name, StateEvent::HUNGUP)); + CPPUNIT_ASSERT(waitForSignal(callDataMap_["CARLA"], + DRing::CallSignal::StateChange::name, + StateEvent::HUNGUP)); JAMI_INFO("Calls normally ended on both sides"); }