diff --git a/src/sip/sipcall.cpp b/src/sip/sipcall.cpp index e60e216d061ec8eb8a701f601d2eef009005002d..fb8cad6483c01b932df8a01595c8ea3db3dc9297 100644 --- a/src/sip/sipcall.cpp +++ b/src/sip/sipcall.cpp @@ -821,7 +821,7 @@ SIPCall::answer(const std::vector<DRing::MediaMap>& mediaList) // Apply the media attributes provided by the user. for (size_t idx = 0; idx < mediaAttrList.size(); idx++) { - rtpStreams_[idx].mediaAttribute_ = std::make_shared<MediaAttribute>(mediaAttrList[idx]); + updateMediaStream(mediaAttrList[idx], idx); } if (not inviteSession_) @@ -2098,6 +2098,8 @@ SIPCall::updateMediaStream(const MediaAttribute& newMediaAttr, size_t streamIdx) auto const& mediaAttr = rtpStream.mediaAttribute_; assert(mediaAttr); + bool notify = false; + if (newMediaAttr.muted_ == mediaAttr->muted_) { // Nothing to do. Already in the desired state. JAMI_DBG("[call:%s] [%s] already %s", @@ -2105,30 +2107,34 @@ SIPCall::updateMediaStream(const MediaAttribute& newMediaAttr, size_t streamIdx) mediaAttr->label_.c_str(), mediaAttr->muted_ ? "muted " : "un-muted "); - return; + } else { + notify = true; } // Update mediaAttr->muted_ = newMediaAttr.muted_; - mediaAttr->sourceUri_ = newMediaAttr.sourceUri_; + // 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 (mediaAttr->type_ == MediaType::MEDIA_AUDIO) { + if (notify and mediaAttr->type_ == MediaType::MEDIA_AUDIO) { rtpStream.rtpSession_->setMuted(mediaAttr->muted_); + setMute(mediaAttr->muted_); if (not isSubcall()) emitSignal<DRing::CallSignal::AudioMuted>(getCallId(), mediaAttr->muted_); - setMute(mediaAttr->muted_); return; } #ifdef ENABLE_VIDEO - if (mediaAttr->type_ == MediaType::MEDIA_VIDEO) { - if (not isSubcall()) - emitSignal<DRing::CallSignal::VideoMuted>(getCallId(), mediaAttr->muted_); + if (notify and mediaAttr->type_ == MediaType::MEDIA_VIDEO and not isSubcall()) { + emitSignal<DRing::CallSignal::VideoMuted>(getCallId(), mediaAttr->muted_); } #endif } diff --git a/src/sip/sipcall.h b/src/sip/sipcall.h index d2d13b996e17379c620c25b598b9343c3ed0d9d8..c5b0954d7c3c5058e2f63035087a356ce2aca659 100644 --- a/src/sip/sipcall.h +++ b/src/sip/sipcall.h @@ -346,6 +346,7 @@ private: bool hold(); bool unhold(); + // Update the attributes of a media stream void updateMediaStream(const MediaAttribute& newMediaAttr, size_t streamIdx); void updateAllMediaStreams(const std::vector<MediaAttribute>& mediaAttrList); diff --git a/test/unitTest/media_control/media_control.cpp b/test/unitTest/media_control/media_control.cpp deleted file mode 100644 index 1a4124c740f9afb1efbffbea8736dc5503e064d6..0000000000000000000000000000000000000000 --- a/test/unitTest/media_control/media_control.cpp +++ /dev/null @@ -1,376 +0,0 @@ -/* - * Copyright (C) 2021 Savoir-faire Linux Inc. - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <https://www.gnu.org/licenses/>. - */ - -#include "manager.h" -#include "jamidht/connectionmanager.h" -#include "jamidht/jamiaccount.h" -#include "../../test_runner.h" -#include "jami.h" -#include "call_const.h" -#include "account_const.h" -#include "sip/sipcall.h" -#include "sip/sdp.h" - -#include <cppunit/TestAssert.h> -#include <cppunit/TestFixture.h> -#include <cppunit/extensions/HelperMacros.h> - -#include <condition_variable> -#include <string> - -#include "common.h" - -using namespace DRing::Account; -using namespace DRing::Call; - -namespace jami { -namespace test { - -struct CallData -{ - std::string userName_ {}; - std::string alias_ {}; - std::shared_ptr<JamiAccount> account_; - std::shared_ptr<SIPCall> sipCall_; - std::vector<MediaAttribute> mediaAttrList_ {}; - std::string signal_ {}; - std::string event_ {}; - std::condition_variable cv_ {}; -}; - -struct TestScenario -{ - TestScenario(const std::vector<MediaAttribute>& offer, - const std::vector<MediaAttribute>& answer, - const std::vector<MediaAttribute>& offerUpdate, - const std::vector<MediaAttribute>& answerUpdate) - : offer_(std::move(offer)) - , answer_(std::move(answer)) - , offerUpdate_(std::move(offerUpdate)) - , answerUpdate_(std::move(answerUpdate)) - {} - - TestScenario() {}; - - std::vector<MediaAttribute> offer_; - std::vector<MediaAttribute> answer_; - std::vector<MediaAttribute> offerUpdate_; - std::vector<MediaAttribute> answerUpdate_; -}; - -/** - * Basic tests for media negotiation. - */ -class MediaControlTest : public CppUnit::TestFixture -{ -public: - MediaControlTest() - { - // Init daemon - DRing::init(DRing::InitFlag(DRing::DRING_FLAG_DEBUG | DRing::DRING_FLAG_CONSOLE_LOG)); - if (not Manager::instance().initialized) - CPPUNIT_ASSERT(DRing::start("jami-sample.yml")); - } - ~MediaControlTest() { DRing::fini(); } - - static std::string name() { return "MediaControlTest"; } - void setUp(); - void tearDown(); - -private: - // Test cases. - void testCallWithMediaList(); - - CPPUNIT_TEST_SUITE(MediaControlTest); - CPPUNIT_TEST(testCallWithMediaList); - CPPUNIT_TEST_SUITE_END(); - - // Helpers - void testWithScenario(CallData& aliceData, CallData& bobData, const TestScenario& scenario); - // Wait for a signal from the callbacks. Some signals also report the event that - // triggered the signal a like the StateChange signal. - bool waitForSignal(CallData& callData, - const std::string& signal, - const std::string& expectedEvent = {}); - - // Event/Signal handlers - void onCallStateChange(const std::string& callId, const std::string& state, CallData& callData); - void onIncomingCall(const std::string& accountId, const std::string& callId, CallData& callData); - void onVideoMuted(const std::string& callId, bool muted, CallData& callData); - -private: - std::string aliceAccountId_; - std::string bobAccountId_; - std::mutex mtx_; -}; - -CPPUNIT_TEST_SUITE_NAMED_REGISTRATION(MediaControlTest, MediaControlTest::name()); - -void -MediaControlTest::setUp() -{ - auto actors = load_actors_and_wait_for_announcement("actors/alice-bob-no-upnpp.yml"); - aliceAccountId_ = actors["alice"]; - bobAccountId_ = actors["bob"]; -} - -void -MediaControlTest::tearDown() -{ - wait_for_removal_of({aliceAccountId_, bobAccountId_}); -} - -void -MediaControlTest::onIncomingCall(const std::string& accountId, - const std::string& callId, - CallData& callData) -{ - CPPUNIT_ASSERT_EQUAL(callData.account_->getAccountID(), accountId); - - JAMI_DBG("%s [%s] received an incoming call [%s]", - callData.alias_.c_str(), - accountId.c_str(), - callId.c_str()); - auto call = Manager::instance().getCallFromCallID(callId); - - Manager::instance().answerCall(callId); - - callData.sipCall_ = std::dynamic_pointer_cast<SIPCall>(call); - callData.event_ = StateEvent::INCOMING; -} - -void -MediaControlTest::onCallStateChange(const std::string& callId, - const std::string& state, - CallData& callData) -{ - auto call = Manager::instance().getCallFromCallID(callId); - - // Interested only in main (parent) call. - if (not call or call->isSubcall()) - return; - - auto account = call->getAccount().lock(); - assert(account); - - JAMI_DBG("Signal [Call State Change] - user [%s] - call [%s] - state [%s]", - account->getAccountDetails()[ConfProperties::ALIAS].c_str(), - callId.c_str(), - state.c_str()); - - if (account->getAccountID() != callData.account_->getAccountID()) - return; - - callData.event_ = state; - callData.signal_ = DRing::CallSignal::StateChange::name; - - if (state == "CURRENT" or state == "OVER" or state == "HUNGUP") { - callData.cv_.notify_one(); - } -} - -void -MediaControlTest::onVideoMuted(const std::string& callId, bool muted, CallData& callData) -{ - auto call = Manager::instance().getCallFromCallID(callId); - if (not call or call->isSubcall()) - return; - auto account = call->getAccount().lock(); - assert(account); - - JAMI_INFO("Signal [Video Muted] - user [%s] - call [%s] - state [%s]", - account->getAccountDetails()[ConfProperties::ALIAS].c_str(), - call->getCallId().c_str(), - muted ? "Mute" : "Un-mute"); - - if (account->getAccountID() != callData.account_->getAccountID()) - return; - - callData.mediaAttrList_ = call->getMediaAttributeList(); - - callData.signal_ = DRing::CallSignal::VideoMuted::name; - callData.cv_.notify_one(); -} - -bool -MediaControlTest::waitForSignal(CallData& callData, - const std::string& expectedSignal, - const std::string& expectedEvent) -{ - const std::chrono::seconds TIME_OUT {20}; - std::unique_lock<std::mutex> lock {mtx_}; - - auto res = callData.cv_.wait_for(lock, TIME_OUT, [&] { - bool pred = callData.signal_ == expectedSignal; - if (not expectedEvent.empty()) { - pred = pred and callData.event_ == expectedEvent; - } - return pred; - }); - - if (not res) { - std::string msg(expectedSignal); - if (not expectedEvent.empty()) { - msg.append("::"); - msg.append(expectedEvent); - } - - JAMI_ERR("Waiting for Signal [%s] timed-out!", expectedSignal.c_str()); - } - - return res; -} -void -MediaControlTest::testWithScenario(CallData& aliceData, - CallData& bobData, - const TestScenario& scenario) -{ - JAMI_INFO("ALICE [%s] calls Bob [%s] and wait for BOB to answer", - aliceData.account_->getAccountID().c_str(), - bobData.account_->getAccountID().c_str()); - - aliceData.sipCall_ = std::dynamic_pointer_cast<SIPCall>( - aliceData.account_->newOutgoingCall(bobData.userName_, scenario.offer_)); - assert(aliceData.sipCall_); - - // Wait for the StateChange signal. - CPPUNIT_ASSERT_EQUAL(true, - waitForSignal(aliceData, - DRing::CallSignal::StateChange::name, - StateEvent::CURRENT)); - - JAMI_INFO("BOB answered the call [%s]", bobData.sipCall_->getCallId().c_str()); - - { - // TODO. Must check against scenario data. - - // Validate Alice's SDP - auto aliceLocalMedia = aliceData.sipCall_->getSDP().getActiveMediaDescription(false); - CPPUNIT_ASSERT_EQUAL(scenario.offer_.size(), aliceLocalMedia.size()); - CPPUNIT_ASSERT_EQUAL(MediaDirection::SENDRECV, aliceLocalMedia[1].direction_); - - // Validate Bob's SDP - auto bobLocalMedia = bobData.sipCall_->getSDP().getActiveMediaDescription(false); - CPPUNIT_ASSERT_EQUAL(scenario.answer_.size(), bobLocalMedia.size()); - - CPPUNIT_ASSERT(bobLocalMedia[0].enabled); - CPPUNIT_ASSERT_EQUAL(MediaType::MEDIA_AUDIO, bobLocalMedia[0].type); - CPPUNIT_ASSERT(not bobLocalMedia[0].onHold); - CPPUNIT_ASSERT(bobLocalMedia[0].addr); - CPPUNIT_ASSERT_EQUAL(MediaDirection::SENDRECV, bobLocalMedia[1].direction_); - } - - std::this_thread::sleep_for(std::chrono::seconds(2)); - - // Update the media - auto mediaListUpdate = MediaAttribute::mediaAttributesToMediaMaps(scenario.offerUpdate_); - Manager::instance().requestMediaChange(aliceData.sipCall_->getCallId(), mediaListUpdate); - - // Wait for the VideoMute signal. - JAMI_INFO("Waiting for the video muted signal"); - - CPPUNIT_ASSERT_EQUAL(true, waitForSignal(aliceData, DRing::CallSignal::VideoMuted::name)); - - { - // Validate Alice's SDP - auto aliceLocalMedia = aliceData.sipCall_->getSDP().getActiveMediaDescription(false); - CPPUNIT_ASSERT_EQUAL(scenario.offerUpdate_.size(), aliceLocalMedia.size()); - CPPUNIT_ASSERT_EQUAL(MediaDirection::SENDRECV, aliceLocalMedia[0].direction_); - - // Validate Bob's SDP - auto bobLocalMedia = bobData.sipCall_->getSDP().getActiveMediaDescription(false); - CPPUNIT_ASSERT_EQUAL(scenario.answerUpdate_.size(), bobLocalMedia.size()); - CPPUNIT_ASSERT_EQUAL(MediaDirection::SENDRECV, bobLocalMedia[0].direction_); - } - - std::this_thread::sleep_for(std::chrono::seconds(1)); - - // Bob hang-up. - JAMI_INFO("Hang up BOB's call and wait for ALICE to hang up"); - Manager::instance().hangupCall(bobData.sipCall_->getCallId()); - - CPPUNIT_ASSERT_EQUAL(true, - waitForSignal(aliceData, - DRing::CallSignal::StateChange::name, - StateEvent::HUNGUP)); - - JAMI_INFO("Call terminated on both sides"); -} - -void -MediaControlTest::testCallWithMediaList() -{ - std::map<std::string, std::shared_ptr<DRing::CallbackWrapperBase>> signalHandlers; - - CallData aliceData; - aliceData.account_ = Manager::instance().getAccount<JamiAccount>(aliceAccountId_); - aliceData.userName_ = aliceData.account_->getAccountDetails()[ConfProperties::USERNAME]; - aliceData.alias_ = aliceData.account_->getAccountDetails()[ConfProperties::ALIAS]; - - CallData bobData; - bobData.account_ = Manager::instance().getAccount<JamiAccount>(bobAccountId_); - bobData.userName_ = bobData.account_->getAccountDetails()[ConfProperties::USERNAME]; - bobData.alias_ = bobData.account_->getAccountDetails()[ConfProperties::ALIAS]; - - // Insert needed signal handlers. - signalHandlers.insert(DRing::exportable_callback<DRing::CallSignal::IncomingCall>( - [&](const std::string& accountId, const std::string& callId, const std::string&) { - onIncomingCall(accountId, callId, bobData); - })); - - signalHandlers.insert(DRing::exportable_callback<DRing::CallSignal::StateChange>( - [&](const std::string& callId, const std::string& state, signed) { - onCallStateChange(callId, state, aliceData); - })); - - signalHandlers.insert(DRing::exportable_callback<DRing::CallSignal::VideoMuted>( - [&](const std::string& callId, bool muted) { onVideoMuted(callId, muted, aliceData); })); - - DRing::registerSignalHandlers(signalHandlers); - - MediaAttribute defaultAudio(MediaType::MEDIA_AUDIO); - defaultAudio.label_ = "main audio"; - - MediaAttribute defaultVideo(MediaType::MEDIA_VIDEO); - defaultVideo.label_ = "main video"; - - { - MediaAttribute audio(defaultAudio); - MediaAttribute video(defaultVideo); - - TestScenario scenario; - // First offer/answer - scenario.offer_.emplace_back(audio); - scenario.offer_.emplace_back(video); - scenario.answer_.emplace_back(audio); - scenario.answer_.emplace_back(video); - - // Updated offer/answer - video.muted_ = true; - scenario.offerUpdate_.emplace_back(audio); - scenario.offerUpdate_.emplace_back(video); - scenario.answerUpdate_.emplace_back(audio); - scenario.answerUpdate_.emplace_back(video); - - testWithScenario(aliceData, bobData, scenario); - } -} - -} // namespace test -} // namespace jami - -RING_TEST_RUNNER(jami::test::MediaControlTest::name()) diff --git a/test/unitTest/media_negotiation/media_negotiation.cpp b/test/unitTest/media_negotiation/media_negotiation.cpp index e90c4b67334fa4ec4cc142444a4e9aa080b756e1..4dce34147199fb3fa72adf26ddb197c4f0f4aa96 100644 --- a/test/unitTest/media_negotiation/media_negotiation.cpp +++ b/test/unitTest/media_negotiation/media_negotiation.cpp @@ -676,7 +676,8 @@ MediaNegotiationTest::testWithScenario(CallData& aliceData, CPPUNIT_ASSERT_EQUAL(scenario.offerUpdate_[idx].muted_, mediaAttr[idx].muted_); // Check isCaptureDeviceMuted API - CPPUNIT_ASSERT_EQUAL(mediaAttr[idx].muted_, aliceCall->isCaptureDeviceMuted(mediaAttr[idx].type_)); + CPPUNIT_ASSERT_EQUAL(mediaAttr[idx].muted_, + aliceCall->isCaptureDeviceMuted(mediaAttr[idx].type_)); } } @@ -690,7 +691,8 @@ MediaNegotiationTest::testWithScenario(CallData& aliceData, CPPUNIT_ASSERT_EQUAL(scenario.answerUpdate_[idx].muted_, mediaAttr[idx].muted_); // Check isCaptureDeviceMuted API - CPPUNIT_ASSERT_EQUAL(mediaAttr[idx].muted_, bobCall->isCaptureDeviceMuted(mediaAttr[idx].type_)); + CPPUNIT_ASSERT_EQUAL(mediaAttr[idx].muted_, + bobCall->isCaptureDeviceMuted(mediaAttr[idx].type_)); } } } @@ -717,11 +719,11 @@ MediaNegotiationTest::audio_and_video_then_mute_video() configureScenario(aliceData_, bobData_); MediaAttribute defaultAudio(MediaType::MEDIA_AUDIO); - defaultAudio.label_ = "main audio"; + defaultAudio.label_ = "audio_0"; defaultAudio.enabled_ = true; MediaAttribute defaultVideo(MediaType::MEDIA_VIDEO); - defaultVideo.label_ = "main video"; + defaultVideo.label_ = "video_0"; defaultVideo.enabled_ = true; { @@ -762,11 +764,11 @@ MediaNegotiationTest::audio_only_then_add_video() configureScenario(aliceData_, bobData_); MediaAttribute defaultAudio(MediaType::MEDIA_AUDIO); - defaultAudio.label_ = "main audio"; + defaultAudio.label_ = "audio_0"; defaultAudio.enabled_ = true; MediaAttribute defaultVideo(MediaType::MEDIA_VIDEO); - defaultVideo.label_ = "main video"; + defaultVideo.label_ = "video_0"; defaultVideo.enabled_ = true; { @@ -802,11 +804,11 @@ MediaNegotiationTest::audio_and_video_then_mute_audio() configureScenario(aliceData_, bobData_); MediaAttribute defaultAudio(MediaType::MEDIA_AUDIO); - defaultAudio.label_ = "main audio"; + defaultAudio.label_ = "audio_0"; defaultAudio.enabled_ = true; MediaAttribute defaultVideo(MediaType::MEDIA_VIDEO); - defaultVideo.label_ = "main video"; + defaultVideo.label_ = "video_0"; defaultVideo.enabled_ = true; { @@ -851,11 +853,11 @@ MediaNegotiationTest::audio_only_then_add_video_but_peer_disabled_multistream() configureScenario(aliceData_, bobData_); MediaAttribute defaultAudio(MediaType::MEDIA_AUDIO); - defaultAudio.label_ = "main audio"; + defaultAudio.label_ = "audio_0"; defaultAudio.enabled_ = true; MediaAttribute defaultVideo(MediaType::MEDIA_VIDEO); - defaultVideo.label_ = "main video"; + defaultVideo.label_ = "video_0"; defaultVideo.enabled_ = true; {