Respond to SDP request extmap-allow-mixed.
The SDP attribute extmap-allow-mixed shows that the client supports
mixing of one- and two-byte header extensions within the same stream.
This is supported on the receive side since CL "Parse two-byte header
extensions", commit 07ba2b9445525da3eabf7c188d631abf32279d98.
Bug: webrtc:7990
Change-Id: I8419da48673f513fcca21a8722614f4601a075fc
Reviewed-on: https://webrtc-review.googlesource.com/c/103420
Commit-Queue: Johannes Kron <kron@webrtc.org>
Reviewed-by: Seth Hampson <shampson@webrtc.org>
Cr-Commit-Position: refs/heads/master@{#25098}
diff --git a/pc/mediasession.cc b/pc/mediasession.cc
index 9ae46be..2e4accc 100644
--- a/pc/mediasession.cc
+++ b/pc/mediasession.cc
@@ -1069,6 +1069,9 @@
NegotiateCodecs(local_codecs, offer->codecs(), &negotiated_codecs);
answer->AddCodecs(negotiated_codecs);
answer->set_protocol(offer->protocol());
+
+ answer->set_mixed_one_two_byte_header_extensions_supported(
+ offer->mixed_one_two_byte_header_extensions_supported());
RtpHeaderExtensions negotiated_rtp_extensions;
NegotiateRtpHeaderExtensions(
local_rtp_extenstions, offer->rtp_header_extensions(),
@@ -1396,6 +1399,9 @@
// Transport info shared by the bundle group.
std::unique_ptr<TransportInfo> bundle_transport;
+ answer->set_mixed_one_two_byte_header_extensions_supported(
+ offer->mixed_one_two_byte_header_extensions_supported());
+
// Get list of all possible codecs that respects existing payload type
// mappings and uses a single payload type space.
//
diff --git a/pc/mediasession_unittest.cc b/pc/mediasession_unittest.cc
index db325ed..7162a37 100644
--- a/pc/mediasession_unittest.cc
+++ b/pc/mediasession_unittest.cc
@@ -1581,6 +1581,72 @@
EXPECT_TRUE(dc->rejected);
}
+TEST_F(MediaSessionDescriptionFactoryTest,
+ CreateAnswerSupportsMixedOneAndTwoByteHeaderExtensions) {
+ MediaSessionOptions opts;
+ std::unique_ptr<SessionDescription> offer(f1_.CreateOffer(opts, NULL));
+ // Offer without request of mixed one- and two-byte header extensions.
+ offer->set_mixed_one_two_byte_header_extensions_supported(false);
+ ASSERT_TRUE(offer.get() != NULL);
+ std::unique_ptr<SessionDescription> answer_no_support(
+ f2_.CreateAnswer(offer.get(), opts, NULL));
+ EXPECT_FALSE(
+ answer_no_support->mixed_one_two_byte_header_extensions_supported());
+
+ // Offer with request of mixed one- and two-byte header extensions.
+ offer->set_mixed_one_two_byte_header_extensions_supported(true);
+ ASSERT_TRUE(offer.get() != NULL);
+ std::unique_ptr<SessionDescription> answer_support(
+ f2_.CreateAnswer(offer.get(), opts, NULL));
+ EXPECT_TRUE(answer_support->mixed_one_two_byte_header_extensions_supported());
+}
+
+TEST_F(MediaSessionDescriptionFactoryTest,
+ CreateAnswerSupportsMixedOneAndTwoByteHeaderExtensionsOnMediaLevel) {
+ MediaSessionOptions opts;
+ AddAudioVideoSections(RtpTransceiverDirection::kSendRecv, &opts);
+ std::unique_ptr<SessionDescription> offer(f1_.CreateOffer(opts, NULL));
+ MediaContentDescription* video_offer =
+ offer->GetContentDescriptionByName("video");
+ ASSERT_TRUE(video_offer);
+ MediaContentDescription* audio_offer =
+ offer->GetContentDescriptionByName("audio");
+ ASSERT_TRUE(audio_offer);
+
+ // Explicit disable of mixed one-two byte header support in offer.
+ video_offer->set_mixed_one_two_byte_header_extensions_supported(
+ MediaContentDescription::kNo);
+ audio_offer->set_mixed_one_two_byte_header_extensions_supported(
+ MediaContentDescription::kNo);
+
+ ASSERT_TRUE(offer.get() != NULL);
+ std::unique_ptr<SessionDescription> answer_no_support(
+ f2_.CreateAnswer(offer.get(), opts, NULL));
+ MediaContentDescription* video_answer =
+ answer_no_support->GetContentDescriptionByName("video");
+ MediaContentDescription* audio_answer =
+ answer_no_support->GetContentDescriptionByName("audio");
+ EXPECT_EQ(MediaContentDescription::kNo,
+ video_answer->mixed_one_two_byte_header_extensions_supported());
+ EXPECT_EQ(MediaContentDescription::kNo,
+ audio_answer->mixed_one_two_byte_header_extensions_supported());
+
+ // Enable mixed one-two byte header support in offer.
+ video_offer->set_mixed_one_two_byte_header_extensions_supported(
+ MediaContentDescription::kMedia);
+ audio_offer->set_mixed_one_two_byte_header_extensions_supported(
+ MediaContentDescription::kMedia);
+ ASSERT_TRUE(offer.get() != NULL);
+ std::unique_ptr<SessionDescription> answer_support(
+ f2_.CreateAnswer(offer.get(), opts, NULL));
+ video_answer = answer_support->GetContentDescriptionByName("video");
+ audio_answer = answer_support->GetContentDescriptionByName("audio");
+ EXPECT_EQ(MediaContentDescription::kMedia,
+ video_answer->mixed_one_two_byte_header_extensions_supported());
+ EXPECT_EQ(MediaContentDescription::kMedia,
+ audio_answer->mixed_one_two_byte_header_extensions_supported());
+}
+
// Create an audio and video offer with:
// - one video track
// - two audio tracks
diff --git a/pc/sessiondescription.h b/pc/sessiondescription.h
index 645ebc5..4fdbf3b 100644
--- a/pc/sessiondescription.h
+++ b/pc/sessiondescription.h
@@ -183,6 +183,22 @@
return connection_address_;
}
+ // Determines if it's allowed to mix one- and two-byte rtp header extensions
+ // within the same rtp stream.
+ enum ExtmapAllowMixed { kNo, kSession, kMedia };
+ void set_mixed_one_two_byte_header_extensions_supported(
+ ExtmapAllowMixed supported) {
+ if (supported == kMedia &&
+ mixed_one_two_byte_header_extensions_supported_ == kSession) {
+ // Do not downgrade from session level to media level.
+ return;
+ }
+ mixed_one_two_byte_header_extensions_supported_ = supported;
+ }
+ ExtmapAllowMixed mixed_one_two_byte_header_extensions_supported() const {
+ return mixed_one_two_byte_header_extensions_supported_;
+ }
+
protected:
bool rtcp_mux_ = false;
bool rtcp_reduced_size_ = false;
@@ -196,6 +212,10 @@
webrtc::RtpTransceiverDirection direction_ =
webrtc::RtpTransceiverDirection::kSendRecv;
rtc::SocketAddress connection_address_;
+ // Mixed one- and two-byte header not included in offer on media level or
+ // session level, but we will respond that we support it. The plan is to add
+ // it to our offer on session level. See todo in SessionDescription.
+ ExtmapAllowMixed mixed_one_two_byte_header_extensions_supported_ = kNo;
};
// TODO(bugs.webrtc.org/8620): Remove this alias once downstream projects have
@@ -455,6 +475,23 @@
}
int msid_signaling() const { return msid_signaling_; }
+ // Determines if it's allowed to mix one- and two-byte rtp header extensions
+ // within the same rtp stream.
+ void set_mixed_one_two_byte_header_extensions_supported(bool supported) {
+ mixed_one_two_byte_header_extensions_supported_ = supported;
+ MediaContentDescription::ExtmapAllowMixed extmap_allow_mixed =
+ supported ? MediaContentDescription::kSession
+ : MediaContentDescription::kNo;
+ for (auto& content : contents_) {
+ content.media_description()
+ ->set_mixed_one_two_byte_header_extensions_supported(
+ extmap_allow_mixed);
+ }
+ }
+ bool mixed_one_two_byte_header_extensions_supported() const {
+ return mixed_one_two_byte_header_extensions_supported_;
+ }
+
private:
SessionDescription(const SessionDescription&);
@@ -465,6 +502,11 @@
// Default to what Plan B would do.
// TODO(bugs.webrtc.org/8530): Change default to kMsidSignalingMediaSection.
int msid_signaling_ = kMsidSignalingSsrcAttribute;
+ // TODO(kron): Activate mixed one- and two-byte header extension in offer at
+ // session level. It's currently not included in offer by default because
+ // clients prior to https://bugs.webrtc.org/9712 cannot parse this correctly.
+ // If it's included in offer to us we will respond that we support it.
+ bool mixed_one_two_byte_header_extensions_supported_ = false;
};
// Indicates whether a session description was sent by the local client or
diff --git a/pc/webrtcsdp.cc b/pc/webrtcsdp.cc
index 665b4d4..67d4bbb 100644
--- a/pc/webrtcsdp.cc
+++ b/pc/webrtcsdp.cc
@@ -119,6 +119,7 @@
static const char kAttributeRtcpReducedSize[] = "rtcp-rsize";
static const char kAttributeSsrc[] = "ssrc";
static const char kSsrcAttributeCname[] = "cname";
+static const char kAttributeExtmapAllowMixed[] = "extmap-allow-mixed";
static const char kAttributeExtmap[] = "extmap";
// draft-alvestrand-mmusic-msid-01
// a=msid-semantic: WMS
@@ -852,6 +853,12 @@
AddLine(group_line, &message);
}
+ // Mixed one- and two-byte header extension.
+ if (desc->mixed_one_two_byte_header_extensions_supported()) {
+ InitAttrLine(kAttributeExtmapAllowMixed, &os);
+ AddLine(os.str(), &message);
+ }
+
// MediaStream semantics
InitAttrLine(kAttributeMsidSemantics, &os);
os << kSdpDelimiterColon << " " << kMediaStreamSemantic;
@@ -1482,7 +1489,17 @@
int msid_signaling,
std::string* message) {
rtc::StringBuilder os;
- // RFC 5285
+ // RFC 8285
+ // a=extmap-allow-mixed
+ // The attribute MUST be either on session level or media level. We support
+ // responding on both levels, however, we don't respond on media level if it's
+ // set on session level.
+ if (media_desc->mixed_one_two_byte_header_extensions_supported() ==
+ MediaContentDescription::kMedia) {
+ InitAttrLine(kAttributeExtmapAllowMixed, &os);
+ AddLine(os.str(), message);
+ }
+ // RFC 8285
// a=extmap:<value>["/"<direction>] <URI> <extensionattributes>
// The definitions MUST be either all session level or all media level. This
// implementation uses all media level.
@@ -1996,7 +2013,7 @@
std::string line;
desc->set_msid_supported(false);
-
+ desc->set_mixed_one_two_byte_header_extensions_supported(false);
// RFC 4566
// v= (protocol version)
if (!GetLineWithType(message, pos, &line, kLineTypeVersion)) {
@@ -2133,6 +2150,8 @@
}
desc->set_msid_supported(
CaseInsensitiveFind(semantics, kMediaStreamSemantic));
+ } else if (HasAttribute(line, kAttributeExtmapAllowMixed)) {
+ desc->set_mixed_one_two_byte_header_extensions_supported(true);
} else if (HasAttribute(line, kAttributeExtmap)) {
RtpExtension extmap;
if (!ParseExtmap(line, &extmap, error)) {
@@ -2502,6 +2521,10 @@
}
if (IsRtp(protocol)) {
+ if (desc->mixed_one_two_byte_header_extensions_supported()) {
+ content->set_mixed_one_two_byte_header_extensions_supported(
+ MediaContentDescription::kSession);
+ }
// Set the extmap.
if (!session_extmaps.empty() &&
!content->rtp_header_extensions().empty()) {
@@ -2902,6 +2925,9 @@
media_desc->set_direction(RtpTransceiverDirection::kInactive);
} else if (HasAttribute(line, kAttributeSendRecv)) {
media_desc->set_direction(RtpTransceiverDirection::kSendRecv);
+ } else if (HasAttribute(line, kAttributeExtmapAllowMixed)) {
+ media_desc->set_mixed_one_two_byte_header_extensions_supported(
+ MediaContentDescription::kMedia);
} else if (HasAttribute(line, kAttributeExtmap)) {
RtpExtension extmap;
if (!ParseExtmap(line, &extmap, error)) {
diff --git a/pc/webrtcsdp_unittest.cc b/pc/webrtcsdp_unittest.cc
index 9c39c41..6a95685 100644
--- a/pc/webrtcsdp_unittest.cc
+++ b/pc/webrtcsdp_unittest.cc
@@ -90,6 +90,7 @@
static const char kFingerprint[] =
"a=fingerprint:sha-1 "
"4A:AD:B9:B1:3F:82:18:3B:54:02:12:DF:3E:5D:49:6B:19:E5:7C:AB\r\n";
+static const char kExtmapAllowMixed[] = "a=extmap-allow-mixed\r\n";
static const int kExtmapId = 1;
static const char kExtmapUri[] = "http://example.com/082005/ext.htm#ttime";
static const char kExtmap[] =
@@ -1283,6 +1284,10 @@
// streams
EXPECT_EQ(cd1->streams(), cd2->streams());
+ // extmap-allow-mixed
+ EXPECT_EQ(cd1->mixed_one_two_byte_header_extensions_supported(),
+ cd2->mixed_one_two_byte_header_extensions_supported());
+
// extmap
ASSERT_EQ(cd1->rtp_header_extensions().size(),
cd2->rtp_header_extensions().size());
@@ -1399,6 +1404,8 @@
// global attributes
EXPECT_EQ(desc1.msid_supported(), desc2.msid_supported());
+ EXPECT_EQ(desc1.mixed_one_two_byte_header_extensions_supported(),
+ desc2.mixed_one_two_byte_header_extensions_supported());
}
bool CompareSessionDescription(const JsepSessionDescription& desc1,
@@ -2095,6 +2102,26 @@
EXPECT_EQ(expected_sdp, message);
}
+TEST_F(WebRtcSdpTest, SerializeSessionDescriptionWithExtmapAllowMixed) {
+ jdesc_.description()->set_mixed_one_two_byte_header_extensions_supported(
+ true);
+ TestSerialize(jdesc_);
+}
+
+TEST_F(WebRtcSdpTest, SerializeMediaContentDescriptionWithExtmapAllowMixed) {
+ cricket::MediaContentDescription* video_desc =
+ jdesc_.description()->GetContentDescriptionByName(kVideoContentName);
+ ASSERT_TRUE(video_desc);
+ cricket::MediaContentDescription* audio_desc =
+ jdesc_.description()->GetContentDescriptionByName(kAudioContentName);
+ ASSERT_TRUE(audio_desc);
+ video_desc->set_mixed_one_two_byte_header_extensions_supported(
+ cricket::MediaContentDescription::kMedia);
+ audio_desc->set_mixed_one_two_byte_header_extensions_supported(
+ cricket::MediaContentDescription::kMedia);
+ TestSerialize(jdesc_);
+}
+
TEST_F(WebRtcSdpTest, SerializeSessionDescriptionWithExtmap) {
bool encrypted = false;
AddExtmap(encrypted);
@@ -2432,6 +2459,92 @@
EXPECT_TRUE(CompareSessionDescription(jdesc_, jdesc));
}
+TEST_F(WebRtcSdpTest, SessionLevelMixedHeaderExtensionsAlsoSetsMediaSetting) {
+ cricket::MediaContentDescription* video_desc =
+ jdesc_.description()->GetContentDescriptionByName(kVideoContentName);
+ ASSERT_TRUE(video_desc);
+ cricket::MediaContentDescription* audio_desc =
+ jdesc_.description()->GetContentDescriptionByName(kAudioContentName);
+ ASSERT_TRUE(audio_desc);
+
+ // Setting true on session level propagates to media level.
+ jdesc_.description()->set_mixed_one_two_byte_header_extensions_supported(
+ true);
+ EXPECT_EQ(cricket::MediaContentDescription::kSession,
+ video_desc->mixed_one_two_byte_header_extensions_supported());
+ EXPECT_EQ(cricket::MediaContentDescription::kSession,
+ audio_desc->mixed_one_two_byte_header_extensions_supported());
+
+ // Don't downgrade from session level to media level
+ video_desc->set_mixed_one_two_byte_header_extensions_supported(
+ cricket::MediaContentDescription::kMedia);
+ EXPECT_EQ(cricket::MediaContentDescription::kSession,
+ video_desc->mixed_one_two_byte_header_extensions_supported());
+
+ // Setting false on session level propagates to media level.
+ jdesc_.description()->set_mixed_one_two_byte_header_extensions_supported(
+ false);
+ EXPECT_EQ(cricket::MediaContentDescription::kNo,
+ video_desc->mixed_one_two_byte_header_extensions_supported());
+ EXPECT_EQ(cricket::MediaContentDescription::kNo,
+ audio_desc->mixed_one_two_byte_header_extensions_supported());
+
+ // Now possible to set at media level.
+ video_desc->set_mixed_one_two_byte_header_extensions_supported(
+ cricket::MediaContentDescription::kMedia);
+ EXPECT_EQ(cricket::MediaContentDescription::kMedia,
+ video_desc->mixed_one_two_byte_header_extensions_supported());
+}
+
+TEST_F(WebRtcSdpTest, DeserializeSessionDescriptionWithExtmapAllowMixed) {
+ jdesc_.description()->set_mixed_one_two_byte_header_extensions_supported(
+ true);
+ std::string sdp_with_extmap_allow_mixed = kSdpFullString;
+ InjectAfter("t=0 0\r\n", kExtmapAllowMixed, &sdp_with_extmap_allow_mixed);
+ // Deserialize
+ JsepSessionDescription jdesc_deserialized(kDummyType);
+ EXPECT_TRUE(SdpDeserialize(sdp_with_extmap_allow_mixed, &jdesc_deserialized));
+ // Verify
+ EXPECT_TRUE(CompareSessionDescription(jdesc_, jdesc_deserialized));
+}
+
+TEST_F(WebRtcSdpTest, DeserializeSessionDescriptionWithoutExtmapAllowMixed) {
+ jdesc_.description()->set_mixed_one_two_byte_header_extensions_supported(
+ false);
+ std::string sdp_without_extmap_allow_mixed = kSdpFullString;
+ // Deserialize
+ JsepSessionDescription jdesc_deserialized(kDummyType);
+ EXPECT_TRUE(
+ SdpDeserialize(sdp_without_extmap_allow_mixed, &jdesc_deserialized));
+ // Verify
+ EXPECT_TRUE(CompareSessionDescription(jdesc_, jdesc_deserialized));
+}
+
+TEST_F(WebRtcSdpTest, DeserializeMediaContentDescriptionWithExtmapAllowMixed) {
+ cricket::MediaContentDescription* video_desc =
+ jdesc_.description()->GetContentDescriptionByName(kVideoContentName);
+ ASSERT_TRUE(video_desc);
+ cricket::MediaContentDescription* audio_desc =
+ jdesc_.description()->GetContentDescriptionByName(kAudioContentName);
+ ASSERT_TRUE(audio_desc);
+ video_desc->set_mixed_one_two_byte_header_extensions_supported(
+ cricket::MediaContentDescription::kMedia);
+ audio_desc->set_mixed_one_two_byte_header_extensions_supported(
+ cricket::MediaContentDescription::kMedia);
+
+ std::string sdp_with_extmap_allow_mixed = kSdpFullString;
+ InjectAfter("a=mid:audio_content_name\r\n", kExtmapAllowMixed,
+ &sdp_with_extmap_allow_mixed);
+ InjectAfter("a=mid:video_content_name\r\n", kExtmapAllowMixed,
+ &sdp_with_extmap_allow_mixed);
+
+ // Deserialize
+ JsepSessionDescription jdesc_deserialized(kDummyType);
+ EXPECT_TRUE(SdpDeserialize(sdp_with_extmap_allow_mixed, &jdesc_deserialized));
+ // Verify
+ EXPECT_TRUE(CompareSessionDescription(jdesc_, jdesc_deserialized));
+}
+
TEST_F(WebRtcSdpTest, DeserializeCandidate) {
JsepIceCandidate jcandidate(kDummyMid, kDummyIndex);