From a887b2dee16c4e3b43d31fb5824e6d85b64de84c Mon Sep 17 00:00:00 2001 From: Mohamed Chibani Date: Mon, 1 Nov 2021 11:51:45 -0400 Subject: [PATCH] SDP - set media direction according to mute state Currently, the media attribute in the SDP is always set to 'sendrecv' regardless of the mute state of the media. In this patch, media direction will be set according to mute state of the media. Note that this only applies if the mute/unmute requires media renegotiation (SIP re-invite with new SDP session). Currently, this only the case for video media. For audio, mute/unmute is done locally without SIP re-invite. References: RFC-3264 RFC-4317 (non-normative) Gitlab: #645 Change-Id: I604331255bd25dfe732e192039a673a0980105fa --- src/call.h | 2 + src/manager.cpp | 5 +- src/sip/sdp.cpp | 27 +- src/sip/sdp.h | 2 +- src/sip/sipcall.cpp | 61 +-- src/sip/sipvoiplink.cpp | 26 +- .../media_negotiation/media_negotiation.cpp | 395 ++++++++++++------ 7 files changed, 327 insertions(+), 191 deletions(-) diff --git a/src/call.h b/src/call.h index f1a346454..fc50a246c 100644 --- a/src/call.h +++ b/src/call.h @@ -222,6 +222,8 @@ public: * The media attributes set by the caller of this method will * determine the response sent to the peer and the configuration * of the local media. + * If the media list is empty, the current media set when the call + * was created will be used. */ virtual void answer(const std::vector& mediaList) = 0; diff --git a/src/manager.cpp b/src/manager.cpp index 01c54c175..a74d2af2e 100644 --- a/src/manager.cpp +++ b/src/manager.cpp @@ -1057,10 +1057,7 @@ Manager::answerCall(Call& call, const std::vector& mediaList) pimpl_->removeWaitingCall(call.getCallId()); try { - if (mediaList.empty()) - call.answer(); - else - call.answer(mediaList); + call.answer(mediaList); } catch (const std::runtime_error& e) { JAMI_ERR("%s", e.what()); return false; diff --git a/src/sip/sdp.cpp b/src/sip/sdp.cpp index 01eddf2bf..959e35335 100644 --- a/src/sip/sdp.cpp +++ b/src/sip/sdp.cpp @@ -168,23 +168,20 @@ Sdp::mediaDirection(MediaType type, bool onHold) // Direction inference based on RFC-3264 and RFC-6337 char const* -Sdp::mediaDirection(const MediaAttribute& localAttr, const MediaAttribute& remoteAttr) +Sdp::mediaDirection(const MediaAttribute& mediaAttr) { - if (not localAttr.enabled_ or not remoteAttr.enabled_) + if (not mediaAttr.enabled_) return "inactive"; - if (localAttr.muted_) { - if (remoteAttr.muted_) + if (mediaAttr.muted_) { + if (mediaAttr.onHold_) return "inactive"; - else - return "recvonly"; + return "recvonly"; } - // Local is enabled and not muted. Check remote. - if (remoteAttr.muted_) + if (mediaAttr.onHold_) return "sendonly"; - // Both enabled and not muted. return "sendrecv"; } @@ -340,7 +337,7 @@ Sdp::addMediaDescription(const MediaAttribute& mediaAttr) addRTCPAttribute(med, localVideoRtcpPort_); } - char const* direction = mediaDirection(type, mediaAttr.onHold_); + char const* direction = mediaDirection(mediaAttr); med->attr[med->attr_count++] = pjmedia_sdp_attr_create(memPool_.get(), direction, NULL); @@ -588,12 +585,10 @@ Sdp::processIncomingOffer(const std::vector& mediaList) printSession(remoteSession_, "Remote session:", SdpDirection::OFFER); - if (not localSession_) { - createLocalSession(SdpDirection::ANSWER); - if (validateSession() != PJ_SUCCESS) { - JAMI_ERR("Failed to create local session"); - return false; - } + createLocalSession(SdpDirection::ANSWER); + if (validateSession() != PJ_SUCCESS) { + JAMI_ERR("Failed to create local session"); + return false; } localSession_->media_count = 0; diff --git a/src/sip/sdp.h b/src/sip/sdp.h index 9a56ae423..0c6ba7d23 100644 --- a/src/sip/sdp.h +++ b/src/sip/sdp.h @@ -283,7 +283,7 @@ private: // Determine media direction char const* mediaDirection(MediaType type, bool onHold); - char const* mediaDirection(const MediaAttribute& localAttr, const MediaAttribute& peerAttr); + char const* mediaDirection(const MediaAttribute& mediaAttr); // Get media direction static MediaDirection getMediaDirection(pjmedia_sdp_media* media); diff --git a/src/sip/sipcall.cpp b/src/sip/sipcall.cpp index e998b1d9b..4621da5bb 100644 --- a/src/sip/sipcall.cpp +++ b/src/sip/sipcall.cpp @@ -768,44 +768,59 @@ SIPCall::answer(const std::vector& mediaList) return; } - auto mediaAttrList = MediaAttribute::buildMediaAttributesList(mediaList, isSrtpEnabled()); - - if (mediaAttrList.empty()) { - JAMI_DBG("[call:%s] Media list must not be empty!", getCallId().c_str()); + if (not inviteSession_) { + JAMI_ERR("[call:%s] No invite session for this call", getCallId().c_str()); return; } - if (not inviteSession_) - JAMI_DBG("[call:%s] No invite session for this call", getCallId().c_str()); + if (not sdp_) { + JAMI_ERR("[call:%s] No SDP session for this call", getCallId().c_str()); + return; + } - JAMI_DBG("[call:%s] Answering incoming call with %lu media:", - getCallId().c_str(), - mediaAttrList.size()); + auto newMediaAttrList = MediaAttribute::buildMediaAttributesList(mediaList, isSrtpEnabled()); - if (mediaAttrList.size() != rtpStreams_.size()) { + if (newMediaAttrList.empty() and rtpStreams_.empty()) { + JAMI_ERR("[call:%s] Media list must not be empty!", getCallId().c_str()); + return; + } + + // If the media list is empty, use the current media (this could happen + // with auto-answer for instance), otherwise update the current media. + if (newMediaAttrList.empty()) { + JAMI_DBG("[call:%s] Media list is empty, using current media", getCallId().c_str()); + } else if (newMediaAttrList.size() != rtpStreams_.size()) { + // Media count is not expected to change JAMI_ERR("[call:%s] Media list size %lu in answer does not match. Expected %lu", getCallId().c_str(), - mediaAttrList.size(), + newMediaAttrList.size(), rtpStreams_.size()); return; } + auto const& mediaAttrList = newMediaAttrList.empty() ? getMediaAttributeList() + : newMediaAttrList; + + JAMI_DBG("[call:%s] Answering incoming call with following media:", getCallId().c_str()); for (size_t idx = 0; idx < mediaAttrList.size(); idx++) { auto const& mediaAttr = mediaAttrList.at(idx); - JAMI_DBG("[call:%s] Media @%lu: %s", + JAMI_DBG("[call:%s] Media @%lu - %s", getCallId().c_str(), idx, mediaAttr.toString(true).c_str()); } - // Apply the media attributes provided by the user. + // Apply the media attributes. for (size_t idx = 0; idx < mediaAttrList.size(); idx++) { updateMediaStream(mediaAttrList[idx], idx); } - if (not inviteSession_) - throw VoipLinkException("[call:" + getCallId() - + "] answer: no invite session for this call"); + // Create the SDP answer + sdp_->processIncomingOffer(mediaAttrList); + + if (isIceEnabled()) { + setupIceResponse(); + } if (not inviteSession_->neg) { // We are answering to an INVITE that did not include a media offer (SDP). @@ -855,15 +870,9 @@ SIPCall::answer(const std::vector& mediaList) if (!inviteSession_->last_answer) throw std::runtime_error("Should only be called for initial answer"); - // Answer with an SDP offer if the initial invite was empty, - // otherwise, set the local_sdp session to null to use the - // current SDP session. + // Set the SIP final answer (200 OK). pjsip_tx_data* tdata; - if (pjsip_inv_answer(inviteSession_.get(), - PJSIP_SC_OK, - NULL, - not inviteSession_->neg ? sdp_->getLocalSdpSession() : NULL, - &tdata) + if (pjsip_inv_answer(inviteSession_.get(), PJSIP_SC_OK, NULL, sdp_->getLocalSdpSession(), &tdata) != PJ_SUCCESS) throw std::runtime_error("Could not init invite request answer (200 OK)"); @@ -912,8 +921,8 @@ SIPCall::answerMediaChangeRequest(const std::vector& mediaList) } if (mediaAttrList.empty()) { - JAMI_DBG("[call:%s] Media list size is empty. Ignoring the media change request", - getCallId().c_str()); + JAMI_WARN("[call:%s] Media list is empty. Ignoring the media change request", + getCallId().c_str()); return; } diff --git a/src/sip/sipvoiplink.cpp b/src/sip/sipvoiplink.cpp index eea0c2468..021be9e06 100644 --- a/src/sip/sipvoiplink.cpp +++ b/src/sip/sipvoiplink.cpp @@ -458,11 +458,6 @@ transaction_request_cb(pjsip_rx_data* rdata) // the accept from the client. if (r_sdp != nullptr) { call->getSDP().setReceivedOffer(r_sdp); - call->getSDP().processIncomingOffer(localMediaList); - } - - if (r_sdp and call->isIceEnabled()) { - call->setupIceResponse(); } pjsip_dialog* dialog = nullptr; @@ -488,11 +483,10 @@ transaction_request_cb(pjsip_rx_data* rdata) } pjsip_inv_session* inv = nullptr; - pjsip_inv_create_uas(dialog, - rdata, - call->getSDP().getLocalSdpSession(), - PJSIP_INV_SUPPORT_ICE, - &inv); + // Create UAS for the invite. + // The SDP is not set here, it will be done when the call is + // accepted and the media attributes of the answer are known. + pjsip_inv_create_uas(dialog, rdata, NULL, PJSIP_INV_SUPPORT_ICE, &inv); if (!inv) { JAMI_ERR("Call invite is not initialized"); pjsip_dlg_dec_lock(dialog); @@ -981,9 +975,13 @@ on_rx_offer2(pjsip_inv_session* inv, struct pjsip_inv_on_rx_offer_cb_param* para if (not call) return; - const auto msg = param->rdata->msg_info.msg; - if (msg->type != PJSIP_RESPONSE_MSG) { - JAMI_ERR("[call:%s] Ignore offer in '200 OK' answer", call->getCallId().c_str()); + // This callback is called whenever a new media offer is found in a + // SIP message, typically in a re-invite and in a '200 OK' (as a + // response to an empty invite). + // Here we only handle the second case. The first case is handled + // in reinvite_received_cb. + if (inv->cause != PJSIP_SC_OK) { + // Silently ignore if it's not a '200 OK' return; } @@ -1064,7 +1062,7 @@ sdp_create_offer_cb(pjsip_inv_session* inv, pjmedia_sdp_session** p_offer) throw VoipLinkException("Unexpected empty media attribute list"); } - JAMI_DBG("Creating and SDP offer using the following media:"); + JAMI_DBG("Creating a SDP offer using the following media:"); for (auto const& media : mediaList) { JAMI_DBG("[call %s] Media %s", call->getCallId().c_str(), media.toString(true).c_str()); } diff --git a/test/unitTest/media_negotiation/media_negotiation.cpp b/test/unitTest/media_negotiation/media_negotiation.cpp index cef99d187..8d3e8fbef 100644 --- a/test/unitTest/media_negotiation/media_negotiation.cpp +++ b/test/unitTest/media_negotiation/media_negotiation.cpp @@ -109,15 +109,17 @@ public: private: // Test cases. - void audio_and_video_then_mute_video(); - void audio_only_then_add_video(); - void audio_and_video_then_mute_audio(); + void audio_and_video_then_caller_mute_video(); + void audio_only_then_caller_add_video(); + void audio_and_video_then_caller_mute_audio(); + 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_mute_video); - CPPUNIT_TEST(audio_only_then_add_video); - CPPUNIT_TEST(audio_and_video_then_mute_audio); + 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(); @@ -147,13 +149,19 @@ private: static void configureScenario(CallData& bob, CallData& alice); void testWithScenario(CallData& aliceData, CallData& bobData, const TestScenario& scenario); static std::string getUserAlias(const std::string& callId); - // Infer media direction from the mute state. - // Note that when processing caller side, local is the caller and - // remote is the callee, and when processing the callee, the local is - // callee and remote is the caller. - static MediaDirection inferDirection(bool localMute, bool remoteMute); + // Infer media direction of an offer. + static uint8_t directionToBitset(MediaDirection direction, bool isLocal); + static MediaDirection bitsetToDirection(uint8_t val); + static MediaDirection inferInitialDirection(const MediaAttribute& offer); + // Infer media direction of an answer. + static MediaDirection inferNegotiatedDirection(MediaDirection local, MediaDirection answer); // Wait for a signal from the callbacks. Some signals also report the event that // triggered the signal a like the StateChange signal. + static bool validateMuteState(std::vector expected, + std::vector actual); + static bool validateMediaDirection(std::vector descrList, + std::vector listInOffer, + std::vector listInAnswer); static bool waitForSignal(CallData& callData, const std::string& signal, const std::string& expectedEvent = {}); @@ -205,26 +213,92 @@ MediaNegotiationTest::getUserAlias(const std::string& callId) } MediaDirection -MediaNegotiationTest::inferDirection([[maybe_unused]] bool localMute, - [[maybe_unused]] bool remoteMute) +MediaNegotiationTest::inferInitialDirection(const MediaAttribute& mediaAttr) { -#if 1 - return MediaDirection::SENDRECV; -#else - // NOTE. The media direction inferred here should be the correct one - // according to the spec (RFC-3264 and RFC-6337). However, the current - // implementation always set 'sendrecv' regardless of the mute state. - if (not localMute and not remoteMute) - return MediaDirection::SENDRECV; + if (not mediaAttr.enabled_) + return MediaDirection::INACTIVE; - if (localMute and not remoteMute) + if (mediaAttr.muted_) { + if (mediaAttr.onHold_) + return MediaDirection::INACTIVE; return MediaDirection::RECVONLY; + } - if (not localMute and remoteMute) + if (mediaAttr.onHold_) return MediaDirection::SENDONLY; + return MediaDirection::SENDRECV; +} + +uint8_t +MediaNegotiationTest::directionToBitset(MediaDirection direction, bool isLocal) +{ + if (direction == MediaDirection::SENDRECV) + return 3; + if (direction == MediaDirection::RECVONLY) + return isLocal ? 2 : 1; + if (direction == MediaDirection::SENDONLY) + return isLocal ? 1 : 2; + return 0; +} + +MediaDirection +MediaNegotiationTest::bitsetToDirection(uint8_t val) +{ + if (val == 3) + return MediaDirection::SENDRECV; + if (val == 2) + return MediaDirection::RECVONLY; + if (val == 1) + return MediaDirection::SENDONLY; return MediaDirection::INACTIVE; -#endif +} + +MediaDirection +MediaNegotiationTest::inferNegotiatedDirection(MediaDirection local, MediaDirection remote) +{ + uint8_t val = directionToBitset(local, true) & directionToBitset(remote, false); + return bitsetToDirection(val); +} + +bool +MediaNegotiationTest::validateMuteState(std::vector expected, + std::vector actual) +{ + CPPUNIT_ASSERT_EQUAL(expected.size(), actual.size()); + + return std::equal(expected.begin(), + expected.end(), + actual.begin(), + actual.end(), + [](auto const& expAttr, auto const& actAttr) { + return expAttr.muted_ == actAttr.muted_; + }); +} + +bool +MediaNegotiationTest::validateMediaDirection(std::vector descrList, + std::vector localMediaList, + std::vector remoteMediaList) +{ + CPPUNIT_ASSERT_EQUAL(descrList.size(), localMediaList.size()); + CPPUNIT_ASSERT_EQUAL(descrList.size(), remoteMediaList.size()); + + for (size_t idx = 0; idx < descrList.size(); idx++) { + auto local = inferInitialDirection(localMediaList[idx]); + auto remote = inferInitialDirection(remoteMediaList[idx]); + auto negotiated = inferNegotiatedDirection(local, remote); + + if (descrList[idx].direction_ != negotiated) { + JAMI_WARN("Media [%lu] direction mismatch: expected %i - found %i", + idx, + static_cast(negotiated), + static_cast(descrList[idx].direction_)); + return false; + } + } + + return true; } void @@ -618,22 +692,38 @@ MediaNegotiationTest::testWithScenario(CallData& aliceData, // Validate Alice's media { - auto mediaAttr = aliceCall->getMediaAttributeList(); - CPPUNIT_ASSERT_EQUAL(mediaCount, mediaAttr.size()); - for (size_t idx = 0; idx < mediaCount; idx++) { - CPPUNIT_ASSERT_EQUAL(scenario.offer_[idx].muted_, mediaAttr[idx].muted_); - } + auto mediaList = aliceCall->getMediaAttributeList(); + CPPUNIT_ASSERT_EQUAL(mediaCount, mediaList.size()); + + // Validate mute state + CPPUNIT_ASSERT(validateMuteState(scenario.offer_, mediaList)); + + auto& sdp = aliceCall->getSDP(); + + // Validate local media direction + auto descrList = sdp.getActiveMediaDescription(false); + CPPUNIT_ASSERT_EQUAL(mediaCount, descrList.size()); + // For Alice, local is the offer and remote is the answer. + CPPUNIT_ASSERT(validateMediaDirection(descrList, scenario.offer_, scenario.answer_)); } // Validate Bob's media { auto const& bobCall = std::dynamic_pointer_cast( Manager::instance().getCallFromCallID(bobData.callId_)); - auto mediaAttr = bobCall->getMediaAttributeList(); - CPPUNIT_ASSERT_EQUAL(mediaCount, mediaAttr.size()); - for (size_t idx = 0; idx < mediaCount; idx++) { - CPPUNIT_ASSERT_EQUAL(scenario.answer_[idx].muted_, mediaAttr[idx].muted_); - } + auto mediaList = bobCall->getMediaAttributeList(); + CPPUNIT_ASSERT_EQUAL(mediaCount, mediaList.size()); + + // Validate mute state + CPPUNIT_ASSERT(validateMuteState(scenario.answer_, mediaList)); + + auto& sdp = bobCall->getSDP(); + + // Validate local media direction + auto descrList = sdp.getActiveMediaDescription(false); + CPPUNIT_ASSERT_EQUAL(mediaCount, descrList.size()); + // For Bob, local is the answer and remote is the offer. + CPPUNIT_ASSERT(validateMediaDirection(descrList, scenario.answer_, scenario.offer_)); } std::this_thread::sleep_for(std::chrono::seconds(3)); @@ -669,30 +759,38 @@ MediaNegotiationTest::testWithScenario(CallData& aliceData, // Validate Alice's media { - auto mediaAttr = aliceCall->getMediaAttributeList(); - CPPUNIT_ASSERT_EQUAL(mediaCount, mediaAttr.size()); - for (size_t idx = 0; idx < mediaCount; idx++) { - CPPUNIT_ASSERT_EQUAL(scenario.offerUpdate_[idx].muted_, mediaAttr[idx].muted_); + auto mediaList = aliceCall->getMediaAttributeList(); + CPPUNIT_ASSERT_EQUAL(mediaCount, mediaList.size()); - // Check isCaptureDeviceMuted API - CPPUNIT_ASSERT_EQUAL(mediaAttr[idx].muted_, - aliceCall->isCaptureDeviceMuted(mediaAttr[idx].type_)); - } + // Validate mute state + CPPUNIT_ASSERT(validateMuteState(scenario.offerUpdate_, mediaList)); + + auto& sdp = aliceCall->getSDP(); + + // Validate local media direction + auto descrList = sdp.getActiveMediaDescription(false); + CPPUNIT_ASSERT_EQUAL(mediaCount, descrList.size()); + CPPUNIT_ASSERT( + validateMediaDirection(descrList, scenario.offerUpdate_, scenario.answerUpdate_)); + // Validate remote media direction + descrList = sdp.getActiveMediaDescription(true); + CPPUNIT_ASSERT_EQUAL(mediaCount, descrList.size()); + CPPUNIT_ASSERT( + validateMediaDirection(descrList, scenario.answerUpdate_, scenario.offerUpdate_)); } // Validate Bob's media { auto const& bobCall = std::dynamic_pointer_cast( Manager::instance().getCallFromCallID(bobData.callId_)); - auto mediaAttr = bobCall->getMediaAttributeList(); - CPPUNIT_ASSERT_EQUAL(mediaCount, mediaAttr.size()); - for (size_t idx = 0; idx < mediaCount; idx++) { - CPPUNIT_ASSERT_EQUAL(scenario.answerUpdate_[idx].muted_, mediaAttr[idx].muted_); + auto mediaList = bobCall->getMediaAttributeList(); + CPPUNIT_ASSERT_EQUAL(mediaCount, mediaList.size()); - // Check isCaptureDeviceMuted API - CPPUNIT_ASSERT_EQUAL(mediaAttr[idx].muted_, - bobCall->isCaptureDeviceMuted(mediaAttr[idx].type_)); - } + // Validate mute state + CPPUNIT_ASSERT(validateMuteState(scenario.answerUpdate_, mediaList)); + + // NOTE: + // It should be enough to validate media direction on Alice's side } } @@ -711,7 +809,7 @@ MediaNegotiationTest::testWithScenario(CallData& aliceData, } void -MediaNegotiationTest::audio_and_video_then_mute_video() +MediaNegotiationTest::audio_and_video_then_caller_mute_video() { JAMI_INFO("=== Begin test %s ===", __FUNCTION__); @@ -725,30 +823,28 @@ MediaNegotiationTest::audio_and_video_then_mute_video() defaultVideo.label_ = "video_0"; defaultVideo.enabled_ = true; - { - MediaAttribute audio(defaultAudio); - MediaAttribute video(defaultVideo); + 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); + 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 - scenario.offerUpdate_.emplace_back(audio); - video.muted_ = true; - scenario.offerUpdate_.emplace_back(video); + // Updated offer/answer + scenario.offerUpdate_.emplace_back(audio); + video.muted_ = true; + scenario.offerUpdate_.emplace_back(video); - scenario.answerUpdate_.emplace_back(audio); - video.muted_ = false; - scenario.answerUpdate_.emplace_back(video); - scenario.expectMediaRenegotiation_ = true; - scenario.expectMediaChangeRequest_ = false; + scenario.answerUpdate_.emplace_back(audio); + video.muted_ = false; + scenario.answerUpdate_.emplace_back(video); + scenario.expectMediaRenegotiation_ = true; + scenario.expectMediaChangeRequest_ = false; - testWithScenario(aliceData_, bobData_, scenario); - } + testWithScenario(aliceData_, bobData_, scenario); DRing::unregisterSignalHandlers(); @@ -756,7 +852,7 @@ MediaNegotiationTest::audio_and_video_then_mute_video() } void -MediaNegotiationTest::audio_only_then_add_video() +MediaNegotiationTest::audio_only_then_caller_add_video() { JAMI_INFO("=== Begin test %s ===", __FUNCTION__); @@ -770,25 +866,23 @@ MediaNegotiationTest::audio_only_then_add_video() defaultVideo.label_ = "video_0"; defaultVideo.enabled_ = true; - { - MediaAttribute audio(defaultAudio); - MediaAttribute video(defaultVideo); + MediaAttribute audio(defaultAudio); + MediaAttribute video(defaultVideo); - TestScenario scenario; - // First offer/answer - scenario.offer_.emplace_back(audio); - scenario.answer_.emplace_back(audio); + TestScenario scenario; + // First offer/answer + scenario.offer_.emplace_back(audio); + scenario.answer_.emplace_back(audio); - // Updated offer/answer - scenario.offerUpdate_.emplace_back(audio); - scenario.offerUpdate_.emplace_back(video); - scenario.answerUpdate_.emplace_back(audio); - scenario.answerUpdate_.emplace_back(video); - scenario.expectMediaRenegotiation_ = true; - scenario.expectMediaChangeRequest_ = true; + // Updated offer/answer + scenario.offerUpdate_.emplace_back(audio); + scenario.offerUpdate_.emplace_back(video); + scenario.answerUpdate_.emplace_back(audio); + scenario.answerUpdate_.emplace_back(video); + scenario.expectMediaRenegotiation_ = true; + scenario.expectMediaChangeRequest_ = true; - testWithScenario(aliceData_, bobData_, scenario); - } + testWithScenario(aliceData_, bobData_, scenario); DRing::unregisterSignalHandlers(); @@ -796,7 +890,7 @@ MediaNegotiationTest::audio_only_then_add_video() } void -MediaNegotiationTest::audio_and_video_then_mute_audio() +MediaNegotiationTest::audio_and_video_then_caller_mute_audio() { JAMI_INFO("=== Begin test %s ===", __FUNCTION__); @@ -810,31 +904,74 @@ MediaNegotiationTest::audio_and_video_then_mute_audio() defaultVideo.label_ = "video_0"; defaultVideo.enabled_ = true; - { - MediaAttribute audio(defaultAudio); - MediaAttribute video(defaultVideo); + 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); + 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 - audio.muted_ = true; - scenario.offerUpdate_.emplace_back(audio); - scenario.offerUpdate_.emplace_back(video); + // Updated offer/answer + audio.muted_ = true; + scenario.offerUpdate_.emplace_back(audio); + scenario.offerUpdate_.emplace_back(video); - audio.muted_ = false; - scenario.answerUpdate_.emplace_back(audio); - scenario.answerUpdate_.emplace_back(video); + audio.muted_ = false; + scenario.answerUpdate_.emplace_back(audio); + scenario.answerUpdate_.emplace_back(video); - scenario.expectMediaRenegotiation_ = false; - scenario.expectMediaChangeRequest_ = false; + scenario.expectMediaRenegotiation_ = false; + scenario.expectMediaChangeRequest_ = false; - testWithScenario(aliceData_, bobData_, scenario); - } + testWithScenario(aliceData_, bobData_, scenario); + + DRing::unregisterSignalHandlers(); + + JAMI_INFO("=== End test %s ===", __FUNCTION__); +} + +void +MediaNegotiationTest::audio_and_video_answer_muted_video_then_mute_video() +{ + JAMI_INFO("=== Begin test %s ===", __FUNCTION__); + + configureScenario(aliceData_, bobData_); + + MediaAttribute defaultAudio(MediaType::MEDIA_AUDIO); + defaultAudio.label_ = "audio_0"; + defaultAudio.enabled_ = true; + + MediaAttribute defaultVideo(MediaType::MEDIA_VIDEO); + defaultVideo.label_ = "video_0"; + defaultVideo.enabled_ = true; + + MediaAttribute audio(defaultAudio); + MediaAttribute video(defaultVideo); + + TestScenario scenario; + // First offer/answer + scenario.offer_.emplace_back(audio); + scenario.offer_.emplace_back(video); + video.muted_ = true; + 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); + + video.muted_ = true; + scenario.answerUpdate_.emplace_back(audio); + scenario.answerUpdate_.emplace_back(video); + + scenario.expectMediaChangeRequest_ = false; + scenario.expectMediaRenegotiation_ = true; + + testWithScenario(aliceData_, bobData_, scenario); DRing::unregisterSignalHandlers(); @@ -856,32 +993,30 @@ MediaNegotiationTest::audio_and_video_then_change_video_source() defaultVideo.label_ = "video_0"; defaultVideo.enabled_ = true; - { - MediaAttribute audio(defaultAudio); - MediaAttribute video(defaultVideo); + 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); + 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 - scenario.offerUpdate_.emplace_back(audio); - // Just change the media source to validate that a new - // media negotiation (re-invite) will be triggered. - video.sourceUri_ = "Fake source"; - scenario.offerUpdate_.emplace_back(video); + // Updated offer/answer + scenario.offerUpdate_.emplace_back(audio); + // Just change the media source to validate that a new + // media negotiation (re-invite) will be triggered. + video.sourceUri_ = "Fake source"; + scenario.offerUpdate_.emplace_back(video); - scenario.answerUpdate_.emplace_back(audio); - scenario.answerUpdate_.emplace_back(video); + scenario.answerUpdate_.emplace_back(audio); + scenario.answerUpdate_.emplace_back(video); - scenario.expectMediaRenegotiation_ = true; - scenario.expectMediaChangeRequest_ = false; + scenario.expectMediaRenegotiation_ = true; + scenario.expectMediaChangeRequest_ = false; - testWithScenario(aliceData_, bobData_, scenario); - } + testWithScenario(aliceData_, bobData_, scenario); DRing::unregisterSignalHandlers();