Adding SDP parsing for Simulcast.

Parsing simulcast according to:
https://tools.ietf.org/html/draft-ietf-mmusic-sdp-simulcast-13#section-5.1
Created SdpSerializer for making serialized components more testable.
Simulcast functionality is still not accessible to users.

Bug: webrtc:10055
Change-Id: Ia6e4cef756cb954521dd19e22911f8eb6498880e
Reviewed-on: https://webrtc-review.googlesource.com/c/112160
Commit-Queue: Amit Hilbuch <amithi@webrtc.org>
Reviewed-by: Seth Hampson <shampson@webrtc.org>
Reviewed-by: Steve Anton <steveanton@webrtc.org>
Cr-Commit-Position: refs/heads/master@{#25883}
This commit is contained in:
Amit Hilbuch
2018-12-03 11:35:05 -08:00
committed by Commit Bot
parent ec086d842c
commit a201204215
9 changed files with 785 additions and 0 deletions

View File

@ -56,6 +56,8 @@ rtc_static_library("rtc_pc_base") {
"rtptransportinternaladapter.h", "rtptransportinternaladapter.h",
"sessiondescription.cc", "sessiondescription.cc",
"sessiondescription.h", "sessiondescription.h",
"simulcastdescription.cc",
"simulcastdescription.h",
"srtpfilter.cc", "srtpfilter.cc",
"srtpfilter.h", "srtpfilter.h",
"srtpsession.cc", "srtpsession.cc",
@ -163,6 +165,8 @@ rtc_static_library("peerconnection") {
"rtptransceiver.h", "rtptransceiver.h",
"sctputils.cc", "sctputils.cc",
"sctputils.h", "sctputils.h",
"sdpserializer.cc",
"sdpserializer.h",
"sdputils.cc", "sdputils.cc",
"sdputils.h", "sdputils.h",
"statscollector.cc", "statscollector.cc",
@ -456,6 +460,7 @@ if (rtc_include_tests) {
"rtpsenderreceiver_unittest.cc", "rtpsenderreceiver_unittest.cc",
"rtptransceiver_unittest.cc", "rtptransceiver_unittest.cc",
"sctputils_unittest.cc", "sctputils_unittest.cc",
"sdpserializer_unittest.cc",
"statscollector_unittest.cc", "statscollector_unittest.cc",
"test/fakeaudiocapturemodule_unittest.cc", "test/fakeaudiocapturemodule_unittest.cc",
"test/testsdpstrings.h", "test/testsdpstrings.h",

217
pc/sdpserializer.cc Normal file
View File

@ -0,0 +1,217 @@
/*
* Copyright 2018 The WebRTC project authors. All Rights Reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree. An additional intellectual property rights grant can be found
* in the file PATENTS. All contributing project authors may
* be found in the AUTHORS file in the root of the source tree.
*/
#include "pc/sdpserializer.h"
#include <string>
#include <utility>
#include <vector>
#include "api/jsep.h"
#include "rtc_base/strings/string_builder.h"
using cricket::SimulcastDescription;
using cricket::SimulcastLayer;
using cricket::SimulcastLayerList;
namespace webrtc {
namespace {
// delimiters
const char kDelimiterComma[] = ",";
const char kDelimiterCommaChar = ',';
const char kDelimiterSemicolon[] = ";";
const char kDelimiterSemicolonChar = ';';
const char kDelimiterSpace[] = " ";
const char kDelimiterSpaceChar = ' ';
// https://tools.ietf.org/html/draft-ietf-mmusic-sdp-simulcast-13#section-5.1
const char kSimulcastPausedStream[] = "~";
const char kSimulcastPausedStreamChar = '~';
const char kSimulcastSendStreams[] = "send";
const char kSimulcastReceiveStreams[] = "recv";
RTCError ParseError(const std::string& message) {
return RTCError(RTCErrorType::SYNTAX_ERROR, message);
}
// These methods serialize simulcast according to the specification:
// https://tools.ietf.org/html/draft-ietf-mmusic-sdp-simulcast-13#section-5.1
rtc::StringBuilder& operator<<(rtc::StringBuilder& builder,
const SimulcastLayer& simulcast_layer) {
if (simulcast_layer.is_paused) {
builder << kSimulcastPausedStream;
}
builder << simulcast_layer.rid;
return builder;
}
rtc::StringBuilder& operator<<(
rtc::StringBuilder& builder,
const std::vector<SimulcastLayer>& layer_alternatives) {
bool first = true;
for (const SimulcastLayer& rid : layer_alternatives) {
if (!first) {
builder << kDelimiterComma;
}
builder << rid;
first = false;
}
return builder;
}
rtc::StringBuilder& operator<<(rtc::StringBuilder& builder,
const SimulcastLayerList& simulcast_layers) {
bool first = true;
for (auto alternatives : simulcast_layers) {
if (!first) {
builder << kDelimiterSemicolon;
}
builder << alternatives;
first = false;
}
return builder;
}
// These methods deserialize simulcast according to the specification:
// https://tools.ietf.org/html/draft-ietf-mmusic-sdp-simulcast-13#section-5.1
// sc-str-list = sc-alt-list *( ";" sc-alt-list )
// sc-alt-list = sc-id *( "," sc-id )
// sc-id-paused = "~"
// sc-id = [sc-id-paused] rid-id
// rid-id = 1*(alpha-numeric / "-" / "_") ; see: I-D.ietf-mmusic-rid
RTCErrorOr<SimulcastLayerList> ParseSimulcastLayerList(const std::string& str) {
std::vector<std::string> tokens;
rtc::tokenize_with_empty_tokens(str, kDelimiterSemicolonChar, &tokens);
if (tokens.empty()) {
return ParseError("Layer list cannot be empty.");
}
SimulcastLayerList result;
for (const std::string& token : tokens) {
if (token.empty()) {
return ParseError("Simulcast alternative layer list is empty.");
}
std::vector<std::string> rid_tokens;
rtc::tokenize_with_empty_tokens(token, kDelimiterCommaChar, &rid_tokens);
if (rid_tokens.empty()) {
return ParseError("Simulcast alternative layer list is malformed.");
}
std::vector<SimulcastLayer> layers;
for (const auto& rid_token : rid_tokens) {
if (rid_token.empty() || rid_token == kSimulcastPausedStream) {
return ParseError("Rid must not be empty.");
}
bool paused = rid_token[0] == kSimulcastPausedStreamChar;
std::string rid = paused ? rid_token.substr(1) : rid_token;
// TODO(amithi, bugs.webrtc.org/10073):
// Validate the rid format.
// See also: https://github.com/w3c/webrtc-pc/issues/2013
layers.push_back(SimulcastLayer(rid, paused));
}
result.AddLayerWithAlternatives(layers);
}
return std::move(result);
}
} // namespace
std::string SdpSerializer::SerializeSimulcastDescription(
const cricket::SimulcastDescription& simulcast) const {
rtc::StringBuilder sb;
std::string delimiter;
if (!simulcast.send_layers().empty()) {
sb << kSimulcastSendStreams << kDelimiterSpace << simulcast.send_layers();
delimiter = kDelimiterSpace;
}
if (!simulcast.receive_layers().empty()) {
sb << delimiter << kSimulcastReceiveStreams << kDelimiterSpace
<< simulcast.receive_layers();
}
return sb.str();
}
// https://tools.ietf.org/html/draft-ietf-mmusic-sdp-simulcast-13#section-5.1
// a:simulcast:<send> <streams> <recv> <streams>
// Formal Grammar
// sc-value = ( sc-send [SP sc-recv] ) / ( sc-recv [SP sc-send] )
// sc-send = %s"send" SP sc-str-list
// sc-recv = %s"recv" SP sc-str-list
// sc-str-list = sc-alt-list *( ";" sc-alt-list )
// sc-alt-list = sc-id *( "," sc-id )
// sc-id-paused = "~"
// sc-id = [sc-id-paused] rid-id
// rid-id = 1*(alpha-numeric / "-" / "_") ; see: I-D.ietf-mmusic-rid
RTCErrorOr<SimulcastDescription> SdpSerializer::DeserializeSimulcastDescription(
absl::string_view string) const {
std::vector<std::string> tokens;
rtc::tokenize(std::string(string), kDelimiterSpaceChar, &tokens);
if (tokens.size() != 2 && tokens.size() != 4) {
return ParseError("Must have one or two <direction, streams> pairs.");
}
bool bidirectional = tokens.size() == 4; // indicates both send and recv
// Tokens 0, 2 (if exists) should be send / recv
if ((tokens[0] != kSimulcastSendStreams &&
tokens[0] != kSimulcastReceiveStreams) ||
(bidirectional && tokens[2] != kSimulcastSendStreams &&
tokens[2] != kSimulcastReceiveStreams) ||
(bidirectional && tokens[0] == tokens[2])) {
return ParseError("Valid values: send / recv.");
}
// Tokens 1, 3 (if exists) should be alternative layer lists
RTCErrorOr<SimulcastLayerList> list1, list2;
list1 = ParseSimulcastLayerList(tokens[1]);
if (!list1.ok()) {
return list1.MoveError();
}
if (bidirectional) {
list2 = ParseSimulcastLayerList(tokens[3]);
if (!list2.ok()) {
return list2.MoveError();
}
}
// Set the layers so that list1 is for send and list2 is for recv
if (tokens[0] != kSimulcastSendStreams) {
std::swap(list1, list2);
}
// Set the layers according to which pair is send and which is recv
// At this point if the simulcast is unidirectional then
// either |list1| or |list2| will be in 'error' state indicating that
// the value should not be used.
SimulcastDescription simulcast;
if (list1.ok()) {
simulcast.send_layers() = list1.MoveValue();
}
if (list2.ok()) {
simulcast.receive_layers() = list2.MoveValue();
}
return std::move(simulcast);
}
} // namespace webrtc

49
pc/sdpserializer.h Normal file
View File

@ -0,0 +1,49 @@
/*
* Copyright 2018 The WebRTC project authors. All Rights Reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree. An additional intellectual property rights grant can be found
* in the file PATENTS. All contributing project authors may
* be found in the AUTHORS file in the root of the source tree.
*/
#ifndef PC_SDPSERIALIZER_H_
#define PC_SDPSERIALIZER_H_
#include <string>
#include "absl/strings/string_view.h"
#include "api/rtcerror.h"
#include "pc/sessiondescription.h"
namespace webrtc {
// This class should serialize components of the SDP (and not the SDP itself).
// Example:
// SimulcastDescription can be serialized and deserialized by this class.
// The serializer will know how to translate the data to spec-compliant
// format without knowing about the SDP attribute details (a=simulcast:)
// Usage:
// Consider the SDP attribute for simulcast a=simulcast:<configuration>.
// The SDP serializtion code (webrtcsdp.h) should use |SdpSerializer| to
// serialize and deserialize the <configuration> section.
// This class will allow testing the serialization of components without
// having to serialize the entire SDP while hiding implementation details
// from callers of sdp serialization (webrtcsdp.h).
class SdpSerializer {
public:
// Serialization for the Simulcast description according to
// https://tools.ietf.org/html/draft-ietf-mmusic-sdp-simulcast-13#section-5.1
std::string SerializeSimulcastDescription(
const cricket::SimulcastDescription& simulcast) const;
// Deserialization for the SimulcastDescription according to
// https://tools.ietf.org/html/draft-ietf-mmusic-sdp-simulcast-13#section-5.1
RTCErrorOr<cricket::SimulcastDescription> DeserializeSimulcastDescription(
absl::string_view string) const;
};
} // namespace webrtc
#endif // PC_SDPSERIALIZER_H_

View File

@ -0,0 +1,235 @@
/*
* Copyright 2018 The WebRTC project authors. All Rights Reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree. An additional intellectual property rights grant can be found
* in the file PATENTS. All contributing project authors may
* be found in the AUTHORS file in the root of the source tree.
*/
#include <string>
#include <vector>
#include "pc/sdpserializer.h"
#include "rtc_base/gunit.h"
using ::testing::ValuesIn;
using cricket::SimulcastDescription;
using cricket::SimulcastLayer;
using cricket::SimulcastLayerList;
namespace webrtc {
class SdpSerializerTest : public ::testing::TestWithParam<const char*> {
public:
// Runs a test for deserializing Simulcast.
// |str| - The serialized Simulcast to parse.
// |expected| - The expected output Simulcast to compare to.
void TestSimulcastDeserialization(
const std::string& str,
const SimulcastDescription& expected) const {
SdpSerializer deserializer;
auto result = deserializer.DeserializeSimulcastDescription(str);
EXPECT_TRUE(result.ok());
ExpectEqual(expected, result.value());
}
// Runs a test for serializing Simulcast.
// |simulcast| - The Simulcast to serialize.
// |expected| - The expected output string to compare to.
void TestSimulcastSerialization(const SimulcastDescription& simulcast,
const std::string& expected) const {
SdpSerializer serializer;
auto result = serializer.SerializeSimulcastDescription(simulcast);
EXPECT_EQ(expected, result);
}
// Checks that the two vectors of SimulcastLayer objects are equal.
void ExpectEqual(const std::vector<SimulcastLayer>& expected,
const std::vector<SimulcastLayer>& actual) const {
EXPECT_EQ(expected.size(), actual.size());
for (size_t i = 0; i < expected.size(); i++) {
EXPECT_EQ(expected[i].rid, actual[i].rid);
EXPECT_EQ(expected[i].is_paused, actual[i].is_paused);
}
}
// Checks that the two SimulcastLayerLists are equal.
void ExpectEqual(const SimulcastLayerList& expected,
const SimulcastLayerList& actual) const {
EXPECT_EQ(expected.size(), actual.size());
for (size_t i = 0; i < expected.size(); i++) {
ExpectEqual(expected[i], actual[i]);
}
}
// Checks that the two SimulcastDescriptions are equal.
void ExpectEqual(const SimulcastDescription& expected,
const SimulcastDescription& actual) const {
ExpectEqual(expected.send_layers(), actual.send_layers());
ExpectEqual(expected.receive_layers(), actual.receive_layers());
}
};
// Test Cases
// Test simple deserialization with no alternative streams.
TEST_F(SdpSerializerTest, DeserializeSimulcast_SimpleCaseNoAlternatives) {
std::string simulcast_str = "send 1;2 recv 3;4";
SimulcastDescription expected;
expected.send_layers().AddLayer(SimulcastLayer("1", false));
expected.send_layers().AddLayer(SimulcastLayer("2", false));
expected.receive_layers().AddLayer(SimulcastLayer("3", false));
expected.receive_layers().AddLayer(SimulcastLayer("4", false));
TestSimulcastDeserialization(simulcast_str, expected);
}
// Test simulcast deserialization with alternative streams.
TEST_F(SdpSerializerTest, DeserializeSimulcast_SimpleCaseWithAlternatives) {
std::string simulcast_str = "send 1,5;2,6 recv 3,7;4,8";
SimulcastDescription expected;
expected.send_layers().AddLayerWithAlternatives(
{SimulcastLayer("1", false), SimulcastLayer("5", false)});
expected.send_layers().AddLayerWithAlternatives(
{SimulcastLayer("2", false), SimulcastLayer("6", false)});
expected.receive_layers().AddLayerWithAlternatives(
{SimulcastLayer("3", false), SimulcastLayer("7", false)});
expected.receive_layers().AddLayerWithAlternatives(
{SimulcastLayer("4", false), SimulcastLayer("8", false)});
TestSimulcastDeserialization(simulcast_str, expected);
}
// Test simulcast deserialization when only some streams have alternatives.
TEST_F(SdpSerializerTest, DeserializeSimulcast_WithSomeAlternatives) {
std::string simulcast_str = "send 1;2,6 recv 3,7;4";
SimulcastDescription expected;
expected.send_layers().AddLayer(SimulcastLayer("1", false));
expected.send_layers().AddLayerWithAlternatives(
{SimulcastLayer("2", false), SimulcastLayer("6", false)});
expected.receive_layers().AddLayerWithAlternatives(
{SimulcastLayer("3", false), SimulcastLayer("7", false)});
expected.receive_layers().AddLayer(SimulcastLayer("4", false));
TestSimulcastDeserialization(simulcast_str, expected);
}
// Test simulcast deserialization when only send streams are specified.
TEST_F(SdpSerializerTest, DeserializeSimulcast_OnlySendStreams) {
std::string simulcast_str = "send 1;2,6;3,7;4";
SimulcastDescription expected;
expected.send_layers().AddLayer(SimulcastLayer("1", false));
expected.send_layers().AddLayerWithAlternatives(
{SimulcastLayer("2", false), SimulcastLayer("6", false)});
expected.send_layers().AddLayerWithAlternatives(
{SimulcastLayer("3", false), SimulcastLayer("7", false)});
expected.send_layers().AddLayer(SimulcastLayer("4", false));
TestSimulcastDeserialization(simulcast_str, expected);
}
// Test simulcast deserialization when only receive streams are specified.
TEST_F(SdpSerializerTest, DeserializeSimulcast_OnlyReceiveStreams) {
std::string simulcast_str = "recv 1;2,6;3,7;4";
SimulcastDescription expected;
expected.receive_layers().AddLayer(SimulcastLayer("1", false));
expected.receive_layers().AddLayerWithAlternatives(
{SimulcastLayer("2", false), SimulcastLayer("6", false)});
expected.receive_layers().AddLayerWithAlternatives(
{SimulcastLayer("3", false), SimulcastLayer("7", false)});
expected.receive_layers().AddLayer(SimulcastLayer("4", false));
TestSimulcastDeserialization(simulcast_str, expected);
}
// Test simulcast deserialization with receive streams before send streams.
TEST_F(SdpSerializerTest, DeserializeSimulcast_SendReceiveReversed) {
std::string simulcast_str = "recv 1;2,6 send 3,7;4";
SimulcastDescription expected;
expected.receive_layers().AddLayer(SimulcastLayer("1", false));
expected.receive_layers().AddLayerWithAlternatives(
{SimulcastLayer("2", false), SimulcastLayer("6", false)});
expected.send_layers().AddLayerWithAlternatives(
{SimulcastLayer("3", false), SimulcastLayer("7", false)});
expected.send_layers().AddLayer(SimulcastLayer("4", false));
TestSimulcastDeserialization(simulcast_str, expected);
}
// Test simulcast deserialization with some streams set to paused state.
TEST_F(SdpSerializerTest, DeserializeSimulcast_PausedStreams) {
std::string simulcast_str = "recv 1;~2,6 send 3,7;~4";
SimulcastDescription expected;
expected.receive_layers().AddLayer(SimulcastLayer("1", false));
expected.receive_layers().AddLayerWithAlternatives(
{SimulcastLayer("2", true), SimulcastLayer("6", false)});
expected.send_layers().AddLayerWithAlternatives(
{SimulcastLayer("3", false), SimulcastLayer("7", false)});
expected.send_layers().AddLayer(SimulcastLayer("4", true));
TestSimulcastDeserialization(simulcast_str, expected);
}
// Parameterized negative test case for deserialization with invalid inputs.
TEST_P(SdpSerializerTest, SimulcastDeserializationFailed) {
SdpSerializer deserializer;
auto result = deserializer.DeserializeSimulcastDescription(GetParam());
EXPECT_FALSE(result.ok());
}
// The malformed Simulcast inputs to use in the negative test case.
const char* kSimulcastMalformedStrings[] = {
"send ",
"recv ",
"recv 1 send",
"receive 1",
"recv 1;~2,6 recv 3,7;~4",
"send 1;~2,6 send 3,7;~4",
"send ~;~2,6",
"send 1; ;~2,6",
"send 1,;~2,6",
"recv 1 send 2 3",
"",
};
INSTANTIATE_TEST_CASE_P(SimulcastDeserializationErrors,
SdpSerializerTest,
ValuesIn(kSimulcastMalformedStrings));
// Test a simple serialization scenario.
TEST_F(SdpSerializerTest, SerializeSimulcast_SimpleCase) {
SimulcastDescription simulcast;
simulcast.send_layers().AddLayer(SimulcastLayer("1", false));
simulcast.receive_layers().AddLayer(SimulcastLayer("2", false));
TestSimulcastSerialization(simulcast, "send 1 recv 2");
}
// Test serialization with only send streams.
TEST_F(SdpSerializerTest, SerializeSimulcast_OnlySend) {
SimulcastDescription simulcast;
simulcast.send_layers().AddLayer(SimulcastLayer("1", false));
simulcast.send_layers().AddLayer(SimulcastLayer("2", false));
TestSimulcastSerialization(simulcast, "send 1;2");
}
// Test serialization with only receive streams
TEST_F(SdpSerializerTest, SerializeSimulcast_OnlyReceive) {
SimulcastDescription simulcast;
simulcast.receive_layers().AddLayer(SimulcastLayer("1", false));
simulcast.receive_layers().AddLayer(SimulcastLayer("2", false));
TestSimulcastSerialization(simulcast, "recv 1;2");
}
// Test a complex serialization with multiple streams, alternatives and states.
TEST_F(SdpSerializerTest, SerializeSimulcast_ComplexSerialization) {
SimulcastDescription simulcast;
simulcast.send_layers().AddLayerWithAlternatives(
{SimulcastLayer("2", false), SimulcastLayer("1", true)});
simulcast.send_layers().AddLayerWithAlternatives(
{SimulcastLayer("4", false), SimulcastLayer("3", false)});
simulcast.receive_layers().AddLayerWithAlternatives(
{SimulcastLayer("6", false), SimulcastLayer("7", false)});
simulcast.receive_layers().AddLayer(SimulcastLayer("8", true));
simulcast.receive_layers().AddLayerWithAlternatives(
{SimulcastLayer("9", false), SimulcastLayer("10", true),
SimulcastLayer("11", false)});
TestSimulcastSerialization(simulcast, "send 2,~1;4,3 recv 6,7;~8;9,~10,11");
}
} // namespace webrtc

View File

@ -25,6 +25,7 @@
#include "media/base/streamparams.h" #include "media/base/streamparams.h"
#include "p2p/base/transportdescription.h" #include "p2p/base/transportdescription.h"
#include "p2p/base/transportinfo.h" #include "p2p/base/transportinfo.h"
#include "pc/simulcastdescription.h"
#include "rtc_base/socketaddress.h" #include "rtc_base/socketaddress.h"
namespace cricket { namespace cricket {
@ -203,6 +204,17 @@ class MediaContentDescription {
} }
bool extmap_allow_mixed() const { return extmap_allow_mixed_enum_ != kNo; } bool extmap_allow_mixed() const { return extmap_allow_mixed_enum_ != kNo; }
// Simulcast functionality.
virtual bool HasSimulcast() const { return !simulcast_.empty(); }
virtual SimulcastDescription& simulcast_description() { return simulcast_; }
virtual const SimulcastDescription& simulcast_description() const {
return simulcast_;
}
virtual void set_simulcast_description(
const SimulcastDescription& simulcast) {
simulcast_ = simulcast;
}
protected: protected:
bool rtcp_mux_ = false; bool rtcp_mux_ = false;
bool rtcp_reduced_size_ = false; bool rtcp_reduced_size_ = false;
@ -220,6 +232,8 @@ class MediaContentDescription {
// session level, but we will respond that we support it. The plan is to add // 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. // it to our offer on session level. See todo in SessionDescription.
ExtmapAllowMixed extmap_allow_mixed_enum_ = kNo; ExtmapAllowMixed extmap_allow_mixed_enum_ = kNo;
SimulcastDescription simulcast_;
}; };
// TODO(bugs.webrtc.org/8620): Remove this alias once downstream projects have // TODO(bugs.webrtc.org/8620): Remove this alias once downstream projects have

View File

@ -0,0 +1,44 @@
/*
* Copyright 2018 The WebRTC Project Authors. All rights reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree. An additional intellectual property rights grant can be found
* in the file PATENTS. All contributing project authors may
* be found in the AUTHORS file in the root of the source tree.
*/
#include <utility>
#include "pc/simulcastdescription.h"
#include "rtc_base/checks.h"
namespace cricket {
SimulcastLayer::SimulcastLayer(const std::string& rid, bool is_paused)
: rid{rid}, is_paused{is_paused} {
// TODO(amithi, bugs.webrtc.org/10073): Validate rid format.
RTC_DCHECK(!rid.empty());
}
void SimulcastLayerList::AddLayer(const SimulcastLayer& layer) {
list_.push_back({layer});
}
void SimulcastLayerList::AddLayerWithAlternatives(
const std::vector<SimulcastLayer>& rids) {
RTC_DCHECK(!rids.empty());
list_.push_back(rids);
}
const std::vector<SimulcastLayer>& SimulcastLayerList::operator[](
size_t index) const {
RTC_DCHECK_LT(index, list_.size());
return list_[index];
}
bool SimulcastDescription::empty() const {
return send_layers_.empty() && receive_layers_.empty();
}
} // namespace cricket

104
pc/simulcastdescription.h Normal file
View File

@ -0,0 +1,104 @@
/*
* Copyright 2018 The WebRTC Project Authors. All rights reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree. An additional intellectual property rights grant can be found
* in the file PATENTS. All contributing project authors may
* be found in the AUTHORS file in the root of the source tree.
*/
#ifndef PC_SIMULCASTDESCRIPTION_H_
#define PC_SIMULCASTDESCRIPTION_H_
#include <string>
#include <vector>
namespace cricket {
// Describes a Simulcast Layer.
// Each simulcast layer has a rid as the identifier and a paused flag.
// See also: https://tools.ietf.org/html/draft-ietf-mmusic-rid-15 for
// an explanation about rids.
struct SimulcastLayer final {
SimulcastLayer(const std::string& rid, bool is_paused);
SimulcastLayer(const SimulcastLayer& other) = default;
SimulcastLayer& operator=(const SimulcastLayer& other) = default;
std::string rid;
bool is_paused;
};
// Describes a list of Simulcast layers.
// Simulcast layers are specified in order of preference.
// Each layer can have a list of alternatives (in order of preference).
// https://tools.ietf.org/html/draft-ietf-mmusic-sdp-simulcast-13#section-5.1
// Example Usage:
// To populate a list that specifies the following:
// 1. Layer 1 or Layer 2
// 2. Layer 3
// 3. Layer 4 or Layer 5
// Use the following code:
// SimulcastLayerList list;
// list.AddLayerWithAlternatives(
// {SimulcastLayer("1", false), SimulcastLayer("2", false});
// list.AddLayer("3");
// list.AddLayerWithAlternatives(
// {SimulcastLayer("4", false), SimulcastLayer("5", false});
class SimulcastLayerList final {
public:
// Use to add a layer when there will be no alternatives.
void AddLayer(const SimulcastLayer& layer);
// Use to add a list of alternatives.
// The alternatives should be specified in order of preference.
void AddLayerWithAlternatives(const std::vector<SimulcastLayer>& layers);
// Read-only access to the contents.
// Note: This object does not allow removal of layers.
std::vector<std::vector<SimulcastLayer>>::const_iterator begin() const {
return list_.begin();
}
std::vector<std::vector<SimulcastLayer>>::const_iterator end() const {
return list_.end();
}
const std::vector<SimulcastLayer>& operator[](size_t index) const;
size_t size() const { return list_.size(); }
bool empty() const { return list_.empty(); }
private:
// TODO(amithi, bugs.webrtc.org/10075):
// Validate that rids do not repeat in the list.
std::vector<std::vector<SimulcastLayer>> list_;
};
// Describes the simulcast options of a video media section.
// This will list the send and receive layers (along with their alternatives).
// Each simulcast layer has an identifier (rid) and can optionally be paused.
// The order of the layers (as well as alternates) indicates user preference
// from first to last (most preferred to least preferred).
// https://tools.ietf.org/html/draft-ietf-mmusic-sdp-simulcast-13#section-5.1
class SimulcastDescription final {
public:
const SimulcastLayerList& send_layers() const { return send_layers_; }
SimulcastLayerList& send_layers() { return send_layers_; }
const SimulcastLayerList& receive_layers() const { return receive_layers_; }
SimulcastLayerList& receive_layers() { return receive_layers_; }
bool empty() const;
private:
// TODO(amithi, bugs.webrtc.org/10075):
// Validate that rids do not repeat in send and receive layers.
SimulcastLayerList send_layers_;
SimulcastLayerList receive_layers_;
};
} // namespace cricket
#endif // PC_SIMULCASTDESCRIPTION_H_

View File

@ -38,6 +38,7 @@
#include "p2p/base/p2pconstants.h" #include "p2p/base/p2pconstants.h"
#include "p2p/base/port.h" #include "p2p/base/port.h"
#include "pc/mediasession.h" #include "pc/mediasession.h"
#include "pc/sdpserializer.h"
#include "rtc_base/arraysize.h" #include "rtc_base/arraysize.h"
#include "rtc_base/checks.h" #include "rtc_base/checks.h"
#include "rtc_base/logging.h" #include "rtc_base/logging.h"
@ -73,6 +74,7 @@ using cricket::MediaContentDescription;
using cricket::MediaType; using cricket::MediaType;
using cricket::RtpHeaderExtensions; using cricket::RtpHeaderExtensions;
using cricket::MediaProtocolType; using cricket::MediaProtocolType;
using cricket::SimulcastDescription;
using cricket::SsrcGroup; using cricket::SsrcGroup;
using cricket::StreamParams; using cricket::StreamParams;
using cricket::StreamParamsVec; using cricket::StreamParamsVec;
@ -162,6 +164,9 @@ static const char kAttributeInactive[] = "inactive";
// draft-ietf-mmusic-sctp-sdp-07 // draft-ietf-mmusic-sctp-sdp-07
// a=sctp-port // a=sctp-port
static const char kAttributeSctpPort[] = "sctp-port"; static const char kAttributeSctpPort[] = "sctp-port";
// draft-ietf-mmusic-sdp-simulcast-13
// a=simulcast
static const char kAttributeSimulcast[] = "simulcast";
// Experimental flags // Experimental flags
static const char kAttributeXGoogleFlag[] = "x-google-flag"; static const char kAttributeXGoogleFlag[] = "x-google-flag";
@ -1652,6 +1657,17 @@ void BuildRtpContentAttributes(const MediaContentDescription* media_desc,
} }
} }
} }
// Simulcast (a=simulcast)
// https://tools.ietf.org/html/draft-ietf-mmusic-sdp-simulcast-13#section-5.1
if (media_desc->as_video() && media_desc->as_video()->HasSimulcast()) {
const auto& simulcast = media_desc->as_video()->simulcast_description();
InitAttrLine(kAttributeSimulcast, &os);
SdpSerializer serializer;
os << kSdpDelimiterColon
<< serializer.SerializeSimulcastDescription(simulcast);
AddLine(os.str(), message);
}
} }
void WriteFmtpHeader(int payload_type, rtc::StringBuilder* os) { void WriteFmtpHeader(int payload_type, rtc::StringBuilder* os) {
@ -2750,6 +2766,7 @@ bool ParseContent(const std::string& message,
std::string ptime_as_string; std::string ptime_as_string;
std::vector<std::string> stream_ids; std::vector<std::string> stream_ids;
std::string track_id; std::string track_id;
SimulcastDescription simulcast;
// Loop until the next m line // Loop until the next m line
while (!IsLineType(message, kLineTypeMedia, *pos)) { while (!IsLineType(message, kLineTypeMedia, *pos)) {
@ -2954,6 +2971,34 @@ bool ParseContent(const std::string& message,
return false; return false;
} }
*msid_signaling |= cricket::kMsidSignalingMediaSection; *msid_signaling |= cricket::kMsidSignalingMediaSection;
} else if (HasAttribute(line, kAttributeSimulcast)) {
const size_t kSimulcastPrefixLength =
kLinePrefixLength + arraysize(kAttributeSimulcast);
if (line.size() <= kSimulcastPrefixLength) {
return ParseFailed(line, "Simulcast attribute is empty.", error);
}
if (!simulcast.empty()) {
return ParseFailed(line, "Multiple Simulcast attributes specified.",
error);
}
SdpSerializer deserializer;
RTCErrorOr<SimulcastDescription> error_or_simulcast =
deserializer.DeserializeSimulcastDescription(
line.substr(kSimulcastPrefixLength));
if (!error_or_simulcast.ok()) {
return ParseFailed(line,
std::string("Malformed simulcast line: ") +
error_or_simulcast.error().message(),
error);
}
simulcast = error_or_simulcast.value();
} else {
// Unrecognized attribute in RTP protocol.
RTC_LOG(LS_INFO) << "Ignored line: " << line;
continue;
} }
} else { } else {
// Only parse lines that we are interested of. // Only parse lines that we are interested of.
@ -3030,6 +3075,13 @@ bool ParseContent(const std::string& message,
candidates->push_back( candidates->push_back(
new JsepIceCandidate(mline_id, mline_index, candidate)); new JsepIceCandidate(mline_id, mline_index, candidate));
} }
if (!simulcast.empty()) {
// TODO(amithi, bugs.webrtc.org/10073):
// Verify that the rids in simulcast match rids in sdp.
media_desc->set_simulcast_description(simulcast);
}
return true; return true;
} }

View File

@ -60,6 +60,8 @@ using cricket::LOCAL_PORT_TYPE;
using cricket::RELAY_PORT_TYPE; using cricket::RELAY_PORT_TYPE;
using cricket::SessionDescription; using cricket::SessionDescription;
using cricket::MediaProtocolType; using cricket::MediaProtocolType;
using cricket::SimulcastDescription;
using cricket::SimulcastLayer;
using cricket::StreamParams; using cricket::StreamParams;
using cricket::STUN_PORT_TYPE; using cricket::STUN_PORT_TYPE;
using cricket::TransportDescription; using cricket::TransportDescription;
@ -1314,6 +1316,13 @@ class WebRtcSdpTest : public testing::Test {
} }
} }
void CompareSimulcastDescription(const SimulcastDescription& simulcast1,
const SimulcastDescription& simulcast2) {
EXPECT_EQ(simulcast1.send_layers().size(), simulcast2.send_layers().size());
EXPECT_EQ(simulcast1.receive_layers().size(),
simulcast2.receive_layers().size());
}
void CompareDataContentDescription(const DataContentDescription* dcd1, void CompareDataContentDescription(const DataContentDescription* dcd1,
const DataContentDescription* dcd2) { const DataContentDescription* dcd2) {
EXPECT_EQ(dcd1->use_sctpmap(), dcd2->use_sctpmap()); EXPECT_EQ(dcd1->use_sctpmap(), dcd2->use_sctpmap());
@ -1360,6 +1369,10 @@ class WebRtcSdpTest : public testing::Test {
const DataContentDescription* dcd2 = c2.media_description()->as_data(); const DataContentDescription* dcd2 = c2.media_description()->as_data();
CompareDataContentDescription(dcd1, dcd2); CompareDataContentDescription(dcd1, dcd2);
} }
CompareSimulcastDescription(
c1.media_description()->simulcast_description(),
c2.media_description()->simulcast_description());
} }
// group // group
@ -3918,3 +3931,55 @@ TEST_F(WebRtcSdpTest, DeserializeEmptySessionName) {
Replace("s=-\r\n", "s= \r\n", &sdp); Replace("s=-\r\n", "s= \r\n", &sdp);
EXPECT_TRUE(SdpDeserialize(sdp, &jsep_desc)); EXPECT_TRUE(SdpDeserialize(sdp, &jsep_desc));
} }
// Simulcast malformed input test for invalid format.
TEST_F(WebRtcSdpTest, DeserializeSimulcastNegative_EmptyAttribute) {
ExpectParseFailureWithNewLines("a=ssrc:3 label:video_track_id_1\r\n",
"a=simulcast:\r\n", "a=simulcast:");
}
// Tests that duplicate simulcast entries in the SDP triggers a parse failure.
TEST_F(WebRtcSdpTest, DeserializeSimulcastNegative_DuplicateAttribute) {
ExpectParseFailureWithNewLines("a=ssrc:3 label:video_track_id_1\r\n",
"a=simulcast:send 1\r\na=simulcast:recv 2\r\n",
"a=simulcast:");
}
// Validates that deserialization uses the a=simulcast: attribute
TEST_F(WebRtcSdpTest, TestDeserializeSimulcastAttribute) {
std::string sdp = kSdpFullString;
sdp += "a=simulcast:send 1,2;3 recv 4;5;6\r\n";
JsepSessionDescription output(kDummyType);
SdpParseError error;
EXPECT_TRUE(webrtc::SdpDeserialize(sdp, &output, &error));
const cricket::ContentInfos& contents = output.description()->contents();
const cricket::MediaContentDescription* media =
contents.back().media_description();
EXPECT_TRUE(media->HasSimulcast());
EXPECT_EQ(2ul, media->simulcast_description().send_layers().size());
EXPECT_EQ(3ul, media->simulcast_description().receive_layers().size());
}
// Simulcast serialization integration test.
// This test will serialize and deserialize the description and compare.
// More detailed tests for parsing simulcast can be found in
// unit tests for SdpSerializer.
TEST_F(WebRtcSdpTest, SerializeSimulcast_ComplexSerialization) {
MakeUnifiedPlanDescription();
auto description = jdesc_.description();
auto media = description->GetContentDescriptionByName(kVideoContentName3);
SimulcastDescription& simulcast = media->simulcast_description();
simulcast.send_layers().AddLayerWithAlternatives(
{SimulcastLayer("2", false), SimulcastLayer("1", true)});
simulcast.send_layers().AddLayerWithAlternatives(
{SimulcastLayer("4", false), SimulcastLayer("3", false)});
simulcast.receive_layers().AddLayerWithAlternatives(
{SimulcastLayer("6", false), SimulcastLayer("7", false)});
simulcast.receive_layers().AddLayer(SimulcastLayer("8", true));
simulcast.receive_layers().AddLayerWithAlternatives(
{SimulcastLayer("9", false), SimulcastLayer("10", true),
SimulcastLayer("11", false)});
TestSerialize(jdesc_);
}