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:
@ -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
217
pc/sdpserializer.cc
Normal 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
49
pc/sdpserializer.h
Normal 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_
|
235
pc/sdpserializer_unittest.cc
Normal file
235
pc/sdpserializer_unittest.cc
Normal 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
|
@ -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
|
||||||
|
44
pc/simulcastdescription.cc
Normal file
44
pc/simulcastdescription.cc
Normal 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
104
pc/simulcastdescription.h
Normal 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_
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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_);
|
||||||
|
}
|
||||||
|
Reference in New Issue
Block a user