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}
218 lines
6.7 KiB
C++
218 lines
6.7 KiB
C++
/*
|
|
* 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
|