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
This commit is contained in:
Mohamed Chibani
2021-11-01 11:51:45 -04:00
committed by Sébastien Blin
parent dd700c3177
commit a887b2dee1
7 changed files with 327 additions and 191 deletions

View File

@ -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<MediaAttribute> expected,
std::vector<MediaAttribute> actual);
static bool validateMediaDirection(std::vector<MediaDescription> descrList,
std::vector<MediaAttribute> listInOffer,
std::vector<MediaAttribute> 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<MediaAttribute> expected,
std::vector<MediaAttribute> 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<MediaDescription> descrList,
std::vector<MediaAttribute> localMediaList,
std::vector<MediaAttribute> 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<int>(negotiated),
static_cast<int>(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<SIPCall>(
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<SIPCall>(
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();