Negotiating Simulcast in the initial offer/answer - Part1.

This change adds Simulcast negotiation to media session offers/answers.
Next step is to add negotiation logic to PeerConnection.

Bug: webrtc:10075
Change-Id: Iea3a1084c16058f0efbc974cf623ec05c3c7a74f
Reviewed-on: https://webrtc-review.googlesource.com/c/115790
Reviewed-by: Seth Hampson <shampson@webrtc.org>
Reviewed-by: Steve Anton <steveanton@webrtc.org>
Commit-Queue: Amit Hilbuch <amithi@webrtc.org>
Cr-Commit-Position: refs/heads/master@{#26115}
This commit is contained in:
Amit Hilbuch 2019-01-02 10:13:58 -08:00 committed by Commit Bot
parent bba675db3e
commit c63ddb2a3f
10 changed files with 1019 additions and 329 deletions

View File

@ -66,6 +66,8 @@ rtc_static_library("rtc_pc_base") {
"srtptransport.h",
"transportstats.cc",
"transportstats.h",
"unique_id_generator.cc",
"unique_id_generator.h",
]
deps = [
@ -90,6 +92,7 @@ rtc_static_library("rtc_pc_base") {
"../rtc_base:checks",
"../rtc_base:rtc_base",
"../rtc_base:rtc_task_queue",
"../rtc_base:stringutils",
"../rtc_base/third_party/base64",
"../rtc_base/third_party/sigslot",
"../system_wrappers:metrics",
@ -257,6 +260,7 @@ if (rtc_include_tests) {
"srtpsession_unittest.cc",
"srtptestutil.h",
"srtptransport_unittest.cc",
"unique_id_generator_unittest.cc",
]
include_dirs = [ "//third_party/libsrtp/srtp" ]

View File

@ -28,6 +28,7 @@
#include "pc/channelmanager.h"
#include "pc/rtpmediautils.h"
#include "pc/srtpfilter.h"
#include "pc/unique_id_generator.h"
#include "rtc_base/checks.h"
#include "rtc_base/helpers.h"
#include "rtc_base/logging.h"
@ -36,6 +37,7 @@
namespace {
using webrtc::RtpTransceiverDirection;
using webrtc::UniqueRandomIdGenerator;
const char kInline[] = "inline:";
@ -272,22 +274,6 @@ static bool SelectCrypto(const MediaContentDescription* offer,
return false;
}
// Generate random SSRC values that are not already present in |params_vec|.
// The generated values are added to |ssrcs|.
// |num_ssrcs| is the number of the SSRC will be generated.
static void GenerateSsrcs(const StreamParamsVec& params_vec,
int num_ssrcs,
std::vector<uint32_t>* ssrcs) {
for (int i = 0; i < num_ssrcs; i++) {
uint32_t candidate;
do {
candidate = rtc::CreateRandomNonZeroId();
} while (GetStreamBySsrc(params_vec, candidate) ||
std::count(ssrcs->begin(), ssrcs->end(), candidate) > 0);
ssrcs->push_back(candidate);
}
}
// Finds all StreamParams of all media types and attach them to stream_params.
static StreamParamsVec GetCurrentStreamParams(
const std::vector<const ContentInfo*>& active_local_contents) {
@ -401,6 +387,135 @@ class UsedRtpHeaderExtensionIds : public UsedIds<webrtc::RtpExtension> {
private:
};
static StreamParams CreateStreamParamsForNewSenderWithSsrcs(
const SenderOptions& sender,
const std::string& rtcp_cname,
const StreamParamsVec& current_streams,
bool include_rtx_streams,
bool include_flexfec_stream) {
StreamParams result;
result.id = sender.track_id;
std::vector<uint32_t> known_ssrcs;
for (const StreamParams& params : current_streams) {
for (uint32_t ssrc : params.ssrcs) {
known_ssrcs.push_back(ssrc);
}
}
UniqueRandomIdGenerator ssrc_generator(known_ssrcs);
// We need to keep |primary_ssrcs| separate from |result.ssrcs| because
// iterators are invalidated when rtx and flexfec ssrcs are added to the list.
std::vector<uint32_t> primary_ssrcs;
for (int i = 0; i < sender.num_sim_layers; ++i) {
primary_ssrcs.push_back(ssrc_generator());
}
result.ssrcs = primary_ssrcs;
if (sender.num_sim_layers > 1) {
SsrcGroup group(kSimSsrcGroupSemantics, result.ssrcs);
result.ssrc_groups.push_back(group);
}
// Generate an RTX ssrc for every ssrc in the group.
if (include_rtx_streams) {
for (uint32_t ssrc : primary_ssrcs) {
result.AddFidSsrc(ssrc, ssrc_generator());
}
}
// Generate extra ssrc for include_flexfec_stream case.
if (include_flexfec_stream) {
// TODO(brandtr): Update when we support multistream protection.
if (result.ssrcs.size() == 1) {
for (uint32_t ssrc : primary_ssrcs) {
result.AddFecFrSsrc(ssrc, ssrc_generator());
}
} else if (!result.ssrcs.empty()) {
RTC_LOG(LS_WARNING)
<< "Our FlexFEC implementation only supports protecting "
"a single media streams. This session has multiple "
"media streams however, so no FlexFEC SSRC will be generated.";
}
}
result.cname = rtcp_cname;
result.set_stream_ids(sender.stream_ids);
return result;
}
static bool ValidateSimulcastLayers(
const std::vector<RidDescription>& rids,
const SimulcastLayerList& simulcast_layers) {
std::vector<SimulcastLayer> all_layers = simulcast_layers.GetAllLayers();
return std::all_of(all_layers.begin(), all_layers.end(),
[&rids](const SimulcastLayer& layer) {
return std::find_if(rids.begin(), rids.end(),
[&layer](const RidDescription& rid) {
return rid.rid == layer.rid;
}) != rids.end();
});
}
static StreamParams CreateStreamParamsForNewSenderWithRids(
const SenderOptions& sender,
const std::string& rtcp_cname) {
RTC_DCHECK(!sender.rids.empty());
RTC_DCHECK_EQ(sender.num_sim_layers, 0)
<< "RIDs are the compliant way to indicate simulcast.";
RTC_DCHECK(ValidateSimulcastLayers(sender.rids, sender.simulcast_layers));
StreamParams result;
result.id = sender.track_id;
result.cname = rtcp_cname;
result.set_stream_ids(sender.stream_ids);
// More than one rid should be signaled.
if (sender.rids.size() > 1) {
result.set_rids(sender.rids);
}
return result;
}
// Adds SimulcastDescription if indicated by the media description options.
// MediaContentDescription should already be set up with the send rids.
static void AddSimulcastToMediaDescription(
const MediaDescriptionOptions& media_description_options,
MediaContentDescription* description) {
RTC_DCHECK(description);
// Check if we are using RIDs in this scenario.
if (std::all_of(
description->streams().begin(), description->streams().end(),
[](const StreamParams& params) { return !params.has_rids(); })) {
return;
}
RTC_DCHECK_EQ(1, description->streams().size())
<< "RIDs are only supported in Unified Plan semantics.";
RTC_DCHECK_EQ(1, media_description_options.sender_options.size());
RTC_DCHECK(description->type() == MediaType::MEDIA_TYPE_AUDIO ||
description->type() == MediaType::MEDIA_TYPE_VIDEO);
// One RID or less indicates that simulcast is not needed.
if (description->streams()[0].rids().size() <= 1) {
return;
}
RTC_DCHECK(!media_description_options.receive_rids.empty());
RTC_DCHECK(ValidateSimulcastLayers(
media_description_options.receive_rids,
media_description_options.receive_simulcast_layers));
StreamParams receive_stream;
receive_stream.set_rids(media_description_options.receive_rids);
description->set_receive_stream(receive_stream);
SimulcastDescription simulcast;
simulcast.send_layers() =
media_description_options.sender_options[0].simulcast_layers;
simulcast.receive_layers() =
media_description_options.receive_simulcast_layers;
description->set_simulcast_description(simulcast);
}
// Adds a StreamParams for each SenderOptions in |sender_options| to
// content_description.
// |current_params| - All currently known StreamParams of any media type.
@ -428,44 +543,17 @@ static bool AddStreamParams(
GetStreamByIds(*current_streams, "" /*group_id*/, sender.track_id);
if (!param) {
// This is a new sender.
std::vector<uint32_t> ssrcs;
GenerateSsrcs(*current_streams, sender.num_sim_layers, &ssrcs);
StreamParams stream_param;
stream_param.id = sender.track_id;
// Add the generated ssrc.
for (size_t i = 0; i < ssrcs.size(); ++i) {
stream_param.ssrcs.push_back(ssrcs[i]);
}
if (sender.num_sim_layers > 1) {
SsrcGroup group(kSimSsrcGroupSemantics, stream_param.ssrcs);
stream_param.ssrc_groups.push_back(group);
}
// Generate extra ssrcs for include_rtx_streams case.
if (include_rtx_streams) {
// Generate an RTX ssrc for every ssrc in the group.
std::vector<uint32_t> rtx_ssrcs;
GenerateSsrcs(*current_streams, static_cast<int>(ssrcs.size()),
&rtx_ssrcs);
for (size_t i = 0; i < ssrcs.size(); ++i) {
stream_param.AddFidSsrc(ssrcs[i], rtx_ssrcs[i]);
}
}
// Generate extra ssrc for include_flexfec_stream case.
if (include_flexfec_stream) {
// TODO(brandtr): Update when we support multistream protection.
if (ssrcs.size() == 1) {
std::vector<uint32_t> flexfec_ssrcs;
GenerateSsrcs(*current_streams, 1, &flexfec_ssrcs);
stream_param.AddFecFrSsrc(ssrcs[0], flexfec_ssrcs[0]);
} else if (!ssrcs.empty()) {
RTC_LOG(LS_WARNING)
<< "Our FlexFEC implementation only supports protecting "
"a single media streams. This session has multiple "
"media streams however, so no FlexFEC SSRC will be generated.";
}
}
stream_param.cname = rtcp_cname;
stream_param.set_stream_ids(sender.stream_ids);
StreamParams stream_param =
sender.rids.empty()
?
// Signal SSRCs and legacy simulcast (if requested).
CreateStreamParamsForNewSenderWithSsrcs(
sender, rtcp_cname, *current_streams, include_rtx_streams,
include_flexfec_stream)
:
// Signal RIDs and spec-compliant simulcast (if requested).
CreateStreamParamsForNewSenderWithRids(sender, rtcp_cname);
content_description->AddStream(stream_param);
// Store the new StreamParams in current_streams.
@ -692,7 +780,7 @@ static bool IsFlexfecCodec(const C& codec) {
// offer.
template <class C>
static bool CreateMediaContentOffer(
const std::vector<SenderOptions>& sender_options,
const MediaDescriptionOptions& media_description_options,
const MediaSessionOptions& session_options,
const std::vector<C>& codecs,
const SecurePolicy& secure_policy,
@ -709,11 +797,13 @@ static bool CreateMediaContentOffer(
}
offer->set_rtp_header_extensions(rtp_extensions);
if (!AddStreamParams(sender_options, session_options.rtcp_cname,
current_streams, offer)) {
if (!AddStreamParams(media_description_options.sender_options,
session_options.rtcp_cname, current_streams, offer)) {
return false;
}
AddSimulcastToMediaDescription(media_description_options, offer);
if (secure_policy != SEC_DISABLED) {
if (current_cryptos) {
AddMediaCryptos(*current_cryptos, offer);
@ -1117,6 +1207,8 @@ static bool CreateMediaContentAnswer(
return false; // Something went seriously wrong.
}
AddSimulcastToMediaDescription(media_description_options, answer);
answer->set_direction(NegotiateRtpTransceiverDirection(
offer->direction(), media_description_options.direction));
return true;
@ -1200,15 +1292,21 @@ void MediaDescriptionOptions::AddAudioSender(
const std::string& track_id,
const std::vector<std::string>& stream_ids) {
RTC_DCHECK(type == MEDIA_TYPE_AUDIO);
AddSenderInternal(track_id, stream_ids, 1);
AddSenderInternal(track_id, stream_ids, {}, SimulcastLayerList(), 1);
}
void MediaDescriptionOptions::AddVideoSender(
const std::string& track_id,
const std::vector<std::string>& stream_ids,
const std::vector<RidDescription>& rids,
const SimulcastLayerList& simulcast_layers,
int num_sim_layers) {
RTC_DCHECK(type == MEDIA_TYPE_VIDEO);
AddSenderInternal(track_id, stream_ids, num_sim_layers);
RTC_DCHECK(rids.empty() || num_sim_layers == 0)
<< "RIDs are the compliant way to indicate simulcast.";
RTC_DCHECK(ValidateSimulcastLayers(rids, simulcast_layers));
AddSenderInternal(track_id, stream_ids, rids, simulcast_layers,
num_sim_layers);
}
void MediaDescriptionOptions::AddRtpDataChannel(const std::string& track_id,
@ -1216,16 +1314,24 @@ void MediaDescriptionOptions::AddRtpDataChannel(const std::string& track_id,
RTC_DCHECK(type == MEDIA_TYPE_DATA);
// TODO(steveanton): Is it the case that RtpDataChannel will never have more
// than one stream?
AddSenderInternal(track_id, {stream_id}, 1);
AddSenderInternal(track_id, {stream_id}, {}, SimulcastLayerList(), 1);
}
void MediaDescriptionOptions::AddSenderInternal(
const std::string& track_id,
const std::vector<std::string>& stream_ids,
const std::vector<RidDescription>& rids,
const SimulcastLayerList& simulcast_layers,
int num_sim_layers) {
// TODO(steveanton): Support any number of stream ids.
RTC_CHECK(stream_ids.size() == 1U);
sender_options.push_back(SenderOptions{track_id, stream_ids, num_sim_layers});
SenderOptions options;
options.track_id = track_id;
options.stream_ids = stream_ids;
options.simulcast_layers = simulcast_layers;
options.rids = rids;
options.num_sim_layers = num_sim_layers;
sender_options.push_back(options);
}
bool MediaSessionOptions::HasMediaDescription(MediaType type) const {
@ -1928,9 +2034,9 @@ bool MediaSessionDescriptionFactory::AddAudioContentForOffer(
GetSupportedAudioSdesCryptoSuiteNames(session_options.crypto_options,
&crypto_suites);
if (!CreateMediaContentOffer(
media_description_options.sender_options, session_options,
filtered_codecs, sdes_policy, GetCryptos(current_content),
crypto_suites, audio_rtp_extensions, current_streams, audio.get())) {
media_description_options, session_options, filtered_codecs,
sdes_policy, GetCryptos(current_content), crypto_suites,
audio_rtp_extensions, current_streams, audio.get())) {
return false;
}
@ -1998,9 +2104,9 @@ bool MediaSessionDescriptionFactory::AddVideoContentForOffer(
}
if (!CreateMediaContentOffer(
media_description_options.sender_options, session_options,
filtered_codecs, sdes_policy, GetCryptos(current_content),
crypto_suites, video_rtp_extensions, current_streams, video.get())) {
media_description_options, session_options, filtered_codecs,
sdes_policy, GetCryptos(current_content), crypto_suites,
video_rtp_extensions, current_streams, video.get())) {
return false;
}
@ -2066,9 +2172,9 @@ bool MediaSessionDescriptionFactory::AddDataContentForOffer(
// Even SCTP uses a "codec".
if (!CreateMediaContentOffer(
media_description_options.sender_options, session_options,
data_codecs, sdes_policy, GetCryptos(current_content), crypto_suites,
RtpHeaderExtensions(), current_streams, data.get())) {
media_description_options, session_options, data_codecs, sdes_policy,
GetCryptos(current_content), crypto_suites, RtpHeaderExtensions(),
current_streams, data.get())) {
return false;
}

View File

@ -35,9 +35,14 @@ class ChannelManager;
const char kDefaultRtcpCname[] = "DefaultRtcpCname";
// Options for an RtpSender contained with an media description/"m=" section.
// Note: Spec-compliant Simulcast and legacy simulcast are mutually exclusive.
struct SenderOptions {
std::string track_id;
std::vector<std::string> stream_ids;
// Use RIDs and Simulcast Layers to indicate spec-compliant Simulcast.
std::vector<RidDescription> rids;
SimulcastLayerList simulcast_layers;
// Use |num_sim_layers| to indicate legacy simulcast.
int num_sim_layers;
};
@ -55,6 +60,8 @@ struct MediaDescriptionOptions {
const std::vector<std::string>& stream_ids);
void AddVideoSender(const std::string& track_id,
const std::vector<std::string>& stream_ids,
const std::vector<RidDescription>& rids,
const SimulcastLayerList& simulcast_layers,
int num_sim_layers);
// Internally just uses sender_options.
@ -69,11 +76,22 @@ struct MediaDescriptionOptions {
// Note: There's no equivalent "RtpReceiverOptions" because only send
// stream information goes in the local descriptions.
std::vector<SenderOptions> sender_options;
// |receive_rids| and |receive_simulcast_layers| are used with spec-compliant
// simulcast. When Simulcast is used, they should both not be empty.
// All RIDs in |receive_simulcast_layers| must appear in receive_rids as well.
// |receive_rids| could also be used outside of simulcast. It is possible to
// add restrictions on the incoming stream during negotiation outside the
// simulcast scenario. This is currently not fully supported, as meaningful
// restrictions are not handled by this library.
std::vector<RidDescription> receive_rids;
SimulcastLayerList receive_simulcast_layers;
private:
// Doesn't DCHECK on |type|.
void AddSenderInternal(const std::string& track_id,
const std::vector<std::string>& stream_ids,
const std::vector<RidDescription>& rids,
const SimulcastLayerList& simulcast_layers,
int num_sim_layers);
};

File diff suppressed because it is too large Load Diff

View File

@ -58,8 +58,9 @@
using cricket::ContentInfo;
using cricket::ContentInfos;
using cricket::MediaContentDescription;
using cricket::SessionDescription;
using cricket::MediaProtocolType;
using cricket::SessionDescription;
using cricket::SimulcastLayerList;
using cricket::TransportInfo;
using cricket::LOCAL_PORT_TYPE;
@ -170,7 +171,7 @@ bool IsValidOfferToReceiveMedia(int value) {
}
// Add options to |[audio/video]_media_description_options| from |senders|.
void AddRtpSenderOptions(
void AddPlanBRtpSenderOptions(
const std::vector<rtc::scoped_refptr<
RtpSenderProxyWithInternal<RtpSenderInternal>>>& senders,
cricket::MediaDescriptionOptions* audio_media_description_options,
@ -186,8 +187,8 @@ void AddRtpSenderOptions(
RTC_DCHECK(sender->media_type() == cricket::MEDIA_TYPE_VIDEO);
if (video_media_description_options) {
video_media_description_options->AddVideoSender(
sender->id(), sender->internal()->stream_ids(),
num_sim_layers);
sender->id(), sender->internal()->stream_ids(), {},
SimulcastLayerList(), num_sim_layers);
}
}
}
@ -4014,9 +4015,10 @@ void PeerConnection::GetOptionsForPlanBOffer(
!video_index ? nullptr
: &session_options->media_description_options[*video_index];
AddRtpSenderOptions(GetSendersInternal(), audio_media_description_options,
video_media_description_options,
offer_answer_options.num_simulcast_layers);
AddPlanBRtpSenderOptions(GetSendersInternal(),
audio_media_description_options,
video_media_description_options,
offer_answer_options.num_simulcast_layers);
}
static cricket::MediaDescriptionOptions
@ -4241,9 +4243,10 @@ void PeerConnection::GetOptionsForPlanBAnswer(
!video_index ? nullptr
: &session_options->media_description_options[*video_index];
AddRtpSenderOptions(GetSendersInternal(), audio_media_description_options,
video_media_description_options,
offer_answer_options.num_simulcast_layers);
AddPlanBRtpSenderOptions(GetSendersInternal(),
audio_media_description_options,
video_media_description_options,
offer_answer_options.num_simulcast_layers);
}
void PeerConnection::GetOptionsForUnifiedPlanAnswer(

View File

@ -20,6 +20,10 @@ SimulcastLayer::SimulcastLayer(const std::string& rid, bool is_paused)
RTC_DCHECK(!rid.empty());
}
bool SimulcastLayer::operator==(const SimulcastLayer& other) const {
return rid == other.rid && is_paused == other.is_paused;
}
void SimulcastLayerList::AddLayer(const SimulcastLayer& layer) {
list_.push_back({layer});
}

View File

@ -25,6 +25,7 @@ struct SimulcastLayer final {
SimulcastLayer(const SimulcastLayer& other) = default;
SimulcastLayer& operator=(const SimulcastLayer& other) = default;
bool operator==(const SimulcastLayer& other) const;
std::string rid;
bool is_paused;
@ -48,6 +49,12 @@ struct SimulcastLayer final {
// {SimulcastLayer("4", false), SimulcastLayer("5", false});
class SimulcastLayerList final {
public:
// Type definitions required by a container.
typedef size_t size_type;
typedef std::vector<SimulcastLayer> value_type;
typedef std::vector<std::vector<SimulcastLayer>>::const_iterator
const_iterator;
// Use to add a layer when there will be no alternatives.
void AddLayer(const SimulcastLayer& layer);
@ -57,13 +64,9 @@ class SimulcastLayerList final {
// 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();
}
const_iterator begin() const { return list_.begin(); }
std::vector<std::vector<SimulcastLayer>>::const_iterator end() const {
return list_.end();
}
const_iterator end() const { return list_.end(); }
const std::vector<SimulcastLayer>& operator[](size_t index) const;

64
pc/unique_id_generator.cc Normal file
View File

@ -0,0 +1,64 @@
/*
* 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/unique_id_generator.h"
#include <limits>
#include <vector>
#include "rtc_base/helpers.h"
#include "rtc_base/string_to_number.h"
#include "rtc_base/stringencode.h"
namespace webrtc {
namespace {
UniqueNumberGenerator<uint32_t> CreateUniqueNumberGenerator(
rtc::ArrayView<std::string> known_ids) {
std::vector<uint32_t> known_ints;
for (const std::string& str : known_ids) {
absl::optional<uint32_t> value = rtc::StringToNumber<uint32_t>(str);
if (value.has_value()) {
known_ints.push_back(value.value());
}
}
return UniqueNumberGenerator<uint32_t>(known_ints);
}
} // namespace
UniqueRandomIdGenerator::UniqueRandomIdGenerator() : known_ids_() {}
UniqueRandomIdGenerator::UniqueRandomIdGenerator(
rtc::ArrayView<uint32_t> known_ids)
: known_ids_(known_ids.begin(), known_ids.end()) {}
UniqueRandomIdGenerator::~UniqueRandomIdGenerator() = default;
uint32_t UniqueRandomIdGenerator::GenerateId() {
while (true) {
RTC_CHECK_LT(known_ids_.size(), std::numeric_limits<uint32_t>::max());
auto pair = known_ids_.insert(rtc::CreateRandomNonZeroId());
if (pair.second) {
return *pair.first;
}
}
}
UniqueStringGenerator::UniqueStringGenerator() : unique_number_generator_() {}
UniqueStringGenerator::UniqueStringGenerator(
rtc::ArrayView<std::string> known_ids)
: unique_number_generator_(CreateUniqueNumberGenerator(known_ids)) {}
UniqueStringGenerator::~UniqueStringGenerator() = default;
std::string UniqueStringGenerator::GenerateString() {
return rtc::ToString(unique_number_generator_.GenerateNumber());
}
} // namespace webrtc

122
pc/unique_id_generator.h Normal file
View File

@ -0,0 +1,122 @@
/*
* 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_UNIQUE_ID_GENERATOR_H_
#define PC_UNIQUE_ID_GENERATOR_H_
#include <limits>
#include <set>
#include <string>
#include "api/array_view.h"
namespace webrtc {
// This class will generate numbers. A common use case is for identifiers.
// The generated numbers will be unique, in the local scope of the generator.
// This means that a generator will never generate the same number twice.
// The generator can also be initialized with a sequence of known ids.
// In such a case, it will never generate an id from that list.
// Recommendedations:
// * Prefer unsigned types.
// * Prefer larger types (uint8_t will run out quickly).
template <typename TIntegral>
class UniqueNumberGenerator {
public:
typedef TIntegral value_type;
UniqueNumberGenerator();
// Creates a generator that will never return any value from the given list.
explicit UniqueNumberGenerator(rtc::ArrayView<TIntegral> known_ids);
~UniqueNumberGenerator();
// Generates a number that this generator has never produced before.
// If there are no available numbers to generate, this method will fail
// with an |RTC_CHECK|.
TIntegral GenerateNumber();
TIntegral operator()() { return GenerateNumber(); }
private:
static_assert(std::is_integral<TIntegral>::value, "Must be integral type.");
TIntegral counter_;
// This class can be further optimized by removing the known_ids_ set when
// the generator was created without a sequence of ids to ignore.
// In such a case, the implementation uses a counter which is sufficient to
// prevent repetitions of the generated values.
std::set<TIntegral> known_ids_;
};
// This class will generate unique ids. Ids are 32 bit unsigned integers.
// The generated ids will be unique, in the local scope of the generator.
// This means that a generator will never generate the same id twice.
// The generator can also be initialized with a sequence of known ids.
// In such a case, it will never generate an id from that list.
class UniqueRandomIdGenerator {
public:
typedef uint32_t value_type;
UniqueRandomIdGenerator();
// Create a generator that will never return any value from the given list.
explicit UniqueRandomIdGenerator(rtc::ArrayView<uint32_t> known_ids);
~UniqueRandomIdGenerator();
// Generates a random id that this generator has never produced before.
// This method becomes more expensive with each use, as the probability of
// collision for the randomly generated numbers increases.
uint32_t GenerateId();
uint32_t operator()() { return GenerateId(); }
private:
std::set<uint32_t> known_ids_;
};
// This class will generate strings. A common use case is for identifiers.
// The generated strings will be unique, in the local scope of the generator.
// This means that a generator will never generate the same string twice.
// The generator can also be initialized with a sequence of known ids.
// In such a case, it will never generate an id from that list.
class UniqueStringGenerator {
public:
typedef std::string value_type;
UniqueStringGenerator();
explicit UniqueStringGenerator(rtc::ArrayView<std::string> known_ids);
~UniqueStringGenerator();
std::string GenerateString();
std::string operator()() { return GenerateString(); }
private:
// This implementation will be simple and will generate "0", "1", ...
UniqueNumberGenerator<uint32_t> unique_number_generator_;
};
template <typename TIntegral>
UniqueNumberGenerator<TIntegral>::UniqueNumberGenerator() : counter_(0) {}
template <typename TIntegral>
UniqueNumberGenerator<TIntegral>::UniqueNumberGenerator(
rtc::ArrayView<TIntegral> known_ids)
: counter_(0), known_ids_(known_ids.begin(), known_ids.end()) {}
template <typename TIntegral>
UniqueNumberGenerator<TIntegral>::~UniqueNumberGenerator() {}
template <typename TIntegral>
TIntegral UniqueNumberGenerator<TIntegral>::GenerateNumber() {
while (true) {
RTC_CHECK_LT(counter_, std::numeric_limits<TIntegral>::max());
auto pair = known_ids_.insert(counter_++);
if (pair.second) {
return *pair.first;
}
}
}
} // namespace webrtc
#endif // PC_UNIQUE_ID_GENERATOR_H_

View File

@ -0,0 +1,79 @@
/*
* 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 "algorithm"
#include "string"
#include "vector"
#include "api/array_view.h"
#include "pc/unique_id_generator.h"
#include "rtc_base/gunit.h"
#include "rtc_base/helpers.h"
#include "test/gmock.h"
using ::testing::IsEmpty;
using ::testing::Test;
namespace webrtc {
template <typename Generator>
class UniqueIdGeneratorTest : public Test {};
using test_types = ::testing::Types<UniqueNumberGenerator<uint8_t>,
UniqueNumberGenerator<uint16_t>,
UniqueNumberGenerator<uint32_t>,
UniqueRandomIdGenerator,
UniqueStringGenerator>;
TYPED_TEST_CASE(UniqueIdGeneratorTest, test_types);
TYPED_TEST(UniqueIdGeneratorTest, ElementsDoNotRepeat) {
typedef TypeParam Generator;
const size_t num_elements = 255;
Generator generator;
std::vector<typename Generator::value_type> values;
for (size_t i = 0; i < num_elements; i++) {
values.push_back(generator());
}
EXPECT_EQ(num_elements, values.size());
// Use a set to check uniqueness.
std::set<typename Generator::value_type> set(values.begin(), values.end());
EXPECT_EQ(values.size(), set.size()) << "Returned values were not unique.";
}
TYPED_TEST(UniqueIdGeneratorTest, KnownElementsAreNotGenerated) {
typedef TypeParam Generator;
const size_t num_elements = 100;
rtc::InitRandom(0);
Generator generator1;
std::vector<typename Generator::value_type> known_values;
for (size_t i = 0; i < num_elements; i++) {
known_values.push_back(generator1());
}
EXPECT_EQ(num_elements, known_values.size());
rtc::InitRandom(0);
Generator generator2(known_values);
std::vector<typename Generator::value_type> values;
for (size_t i = 0; i < num_elements; i++) {
values.push_back(generator2());
}
EXPECT_THAT(values, ::testing::SizeIs(num_elements));
std::sort(values.begin(), values.end());
std::sort(known_values.begin(), known_values.end());
std::vector<typename Generator::value_type> intersection;
std::set_intersection(values.begin(), values.end(), known_values.begin(),
known_values.end(), std::back_inserter(intersection));
EXPECT_THAT(intersection, IsEmpty());
}
} // namespace webrtc