Commit 4fdbd2c4 authored by Florent Castelli's avatar Florent Castelli Committed by Commit Bot

Remove WebRTCRtpParameters layer, use webrtc::RtpParameters instead

This is part of the onion soup efforts.

Bug: 787254
Change-Id: If099833448584243d4b03586f100faf1939c4887
Reviewed-on: https://chromium-review.googlesource.com/1057309
Commit-Queue: Florent Castelli <orphis@chromium.org>
Reviewed-by: default avatarKentaro Hara <haraken@chromium.org>
Reviewed-by: default avatarHenrik Boström <hbos@chromium.org>
Cr-Commit-Position: refs/heads/master@{#558708}
parent 00afc304
...@@ -406,8 +406,6 @@ target(link_target_type, "renderer") { ...@@ -406,8 +406,6 @@ target(link_target_type, "renderer") {
"media/webrtc/rtc_peer_connection_handler.h", "media/webrtc/rtc_peer_connection_handler.h",
"media/webrtc/rtc_rtp_contributing_source.cc", "media/webrtc/rtc_rtp_contributing_source.cc",
"media/webrtc/rtc_rtp_contributing_source.h", "media/webrtc/rtc_rtp_contributing_source.h",
"media/webrtc/rtc_rtp_parameters.cc",
"media/webrtc/rtc_rtp_parameters.h",
"media/webrtc/rtc_rtp_receiver.cc", "media/webrtc/rtc_rtp_receiver.cc",
"media/webrtc/rtc_rtp_receiver.h", "media/webrtc/rtc_rtp_receiver.h",
"media/webrtc/rtc_rtp_sender.cc", "media/webrtc/rtc_rtp_sender.cc",
......
// Copyright (c) 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "content/renderer/media/webrtc/rtc_rtp_parameters.h"
#include <utility>
#include "base/numerics/safe_conversions.h"
namespace {
// Relative weights for each priority as defined in RTCWEB-DATA
// https://tools.ietf.org/html/draft-ietf-rtcweb-data-channel
const double kPriorityWeightVeryLow = 0.5;
const double kPriorityWeightLow = 1;
const double kPriorityWeightMedium = 2;
const double kPriorityWeightHigh = 4;
template <typename T, typename F>
base::Optional<T> ToBaseOptional(const rtc::Optional<F>& from) {
if (from)
return from.value();
return base::nullopt;
}
template <typename T, typename F>
rtc::Optional<T> ToRtcOptional(const base::Optional<F>& from) {
// TODO(orphis): Remove saturated_cast. https://crbug.com/webrtc/9143
if (from)
return base::saturated_cast<T>(from.value());
return rtc::nullopt;
}
blink::WebRTCPriorityType PriorityFromDouble(double priority) {
// Find the middle point between 2 priority weights to match them to a
// WebRTC priority
const double very_low_upper_bound =
(kPriorityWeightVeryLow + kPriorityWeightLow) / 2;
const double low_upper_bound =
(kPriorityWeightLow + kPriorityWeightMedium) / 2;
const double medium_upper_bound =
(kPriorityWeightMedium + kPriorityWeightHigh) / 2;
if (priority < webrtc::kDefaultBitratePriority * very_low_upper_bound) {
return blink::WebRTCPriorityType::VeryLow;
}
if (priority < webrtc::kDefaultBitratePriority * low_upper_bound) {
return blink::WebRTCPriorityType::Low;
}
if (priority < webrtc::kDefaultBitratePriority * medium_upper_bound) {
return blink::WebRTCPriorityType::Medium;
}
return blink::WebRTCPriorityType::High;
}
double PriorityToDouble(blink::WebRTCPriorityType priority) {
double result = 1;
switch (priority) {
case blink::WebRTCPriorityType::VeryLow:
result = webrtc::kDefaultBitratePriority * kPriorityWeightVeryLow;
break;
case blink::WebRTCPriorityType::Low:
result = webrtc::kDefaultBitratePriority * kPriorityWeightLow;
break;
case blink::WebRTCPriorityType::Medium:
result = webrtc::kDefaultBitratePriority * kPriorityWeightMedium;
break;
case blink::WebRTCPriorityType::High:
result = webrtc::kDefaultBitratePriority * kPriorityWeightHigh;
break;
default:
NOTREACHED();
}
return result;
}
base::Optional<blink::WebRTCDtxStatus> FromRTCDtxStatus(
rtc::Optional<webrtc::DtxStatus> status) {
if (!status)
return base::nullopt;
blink::WebRTCDtxStatus result;
switch (status.value()) {
case webrtc::DtxStatus::DISABLED:
result = blink::WebRTCDtxStatus::Disabled;
break;
case webrtc::DtxStatus::ENABLED:
result = blink::WebRTCDtxStatus::Enabled;
break;
default:
NOTREACHED();
}
return result;
}
rtc::Optional<webrtc::DtxStatus> ToRTCDtxStatus(
base::Optional<blink::WebRTCDtxStatus> status) {
if (!status)
return rtc::nullopt;
webrtc::DtxStatus result;
switch (status.value()) {
case blink::WebRTCDtxStatus::Disabled:
result = webrtc::DtxStatus::DISABLED;
break;
case blink::WebRTCDtxStatus::Enabled:
result = webrtc::DtxStatus::ENABLED;
break;
default:
NOTREACHED();
}
return result;
}
base::Optional<blink::WebRTCDegradationPreference> FromRTCDegradationPreference(
rtc::Optional<webrtc::DegradationPreference> degradation_preference) {
if (!degradation_preference)
return base::nullopt;
blink::WebRTCDegradationPreference result;
switch (degradation_preference.value()) {
case webrtc::DegradationPreference::MAINTAIN_FRAMERATE:
result = blink::WebRTCDegradationPreference::MaintainFramerate;
break;
case webrtc::DegradationPreference::MAINTAIN_RESOLUTION:
result = blink::WebRTCDegradationPreference::MaintainResolution;
break;
case webrtc::DegradationPreference::BALANCED:
result = blink::WebRTCDegradationPreference::Balanced;
break;
default:
NOTREACHED();
}
return result;
}
} // namespace
namespace content {
webrtc::DegradationPreference ToDegradationPreference(
blink::WebRTCDegradationPreference degradation_preference) {
webrtc::DegradationPreference result =
webrtc::DegradationPreference::BALANCED;
switch (degradation_preference) {
case blink::WebRTCDegradationPreference::MaintainFramerate:
result = webrtc::DegradationPreference::MAINTAIN_FRAMERATE;
break;
case blink::WebRTCDegradationPreference::MaintainResolution:
result = webrtc::DegradationPreference::MAINTAIN_RESOLUTION;
break;
case blink::WebRTCDegradationPreference::Balanced:
result = webrtc::DegradationPreference::BALANCED;
break;
default:
NOTREACHED();
}
return result;
}
blink::WebRTCRtpParameters GetWebRTCRtpParameters(
const webrtc::RtpParameters& parameters) {
blink::WebVector<blink::WebRTCRtpEncodingParameters> encodings;
encodings.reserve(parameters.encodings.size());
for (const auto& encoding_parameter : parameters.encodings) {
encodings.emplace_back(GetWebRTCRtpEncodingParameters(encoding_parameter));
}
blink::WebVector<blink::WebRTCRtpHeaderExtensionParameters> header_extensions;
header_extensions.reserve(parameters.header_extensions.size());
for (const auto& extension_parameter : parameters.header_extensions) {
header_extensions.emplace_back(
GetWebRTCRtpHeaderExtensionParameters(extension_parameter));
}
blink::WebVector<blink::WebRTCRtpCodecParameters> codec_parameters;
codec_parameters.reserve(parameters.codecs.size());
for (const auto& codec_parameter : parameters.codecs) {
codec_parameters.emplace_back(GetWebRTCRtpCodecParameters(codec_parameter));
}
return blink::WebRTCRtpParameters(
blink::WebString::FromASCII(parameters.transaction_id),
blink::WebRTCRtcpParameters(), std::move(encodings), header_extensions,
codec_parameters,
FromRTCDegradationPreference(parameters.degradation_preference));
}
blink::WebRTCRtpEncodingParameters GetWebRTCRtpEncodingParameters(
const webrtc::RtpEncodingParameters& encoding_parameters) {
return blink::WebRTCRtpEncodingParameters(
ToBaseOptional<uint8_t>(encoding_parameters.codec_payload_type),
FromRTCDtxStatus(encoding_parameters.dtx), encoding_parameters.active,
PriorityFromDouble(encoding_parameters.bitrate_priority),
ToBaseOptional<uint32_t>(encoding_parameters.ptime),
ToBaseOptional<uint32_t>(encoding_parameters.max_bitrate_bps),
ToBaseOptional<uint32_t>(encoding_parameters.max_framerate),
encoding_parameters.scale_framerate_down_by,
blink::WebString::FromASCII(encoding_parameters.rid));
}
webrtc::RtpEncodingParameters FromWebRTCRtpEncodingParameters(
const blink::WebRTCRtpEncodingParameters& web_encoding_parameter) {
webrtc::RtpEncodingParameters encoding_parameter;
encoding_parameter.codec_payload_type =
ToRtcOptional<int>(web_encoding_parameter.CodecPayloadType());
encoding_parameter.dtx = ToRTCDtxStatus(web_encoding_parameter.Dtx());
encoding_parameter.active = web_encoding_parameter.Active();
encoding_parameter.bitrate_priority =
PriorityToDouble(web_encoding_parameter.Priority());
encoding_parameter.ptime = ToRtcOptional<int>(web_encoding_parameter.Ptime());
encoding_parameter.max_bitrate_bps =
ToRtcOptional<int>(web_encoding_parameter.MaxBitrate());
encoding_parameter.max_framerate =
ToRtcOptional<int>(web_encoding_parameter.MaxFramerate());
if (web_encoding_parameter.ScaleResolutionDownBy())
encoding_parameter.scale_resolution_down_by =
web_encoding_parameter.ScaleResolutionDownBy().value();
if (web_encoding_parameter.Rid())
encoding_parameter.rid = web_encoding_parameter.Rid().value().Ascii();
return encoding_parameter;
}
blink::WebRTCRtpHeaderExtensionParameters GetWebRTCRtpHeaderExtensionParameters(
const webrtc::RtpHeaderExtensionParameters& header_extension_parameters) {
return blink::WebRTCRtpHeaderExtensionParameters(
blink::WebString::FromASCII(header_extension_parameters.uri),
header_extension_parameters.id, header_extension_parameters.encrypt);
}
// TODO(orphis): Copy the RTCP information
// https://crbug.com/webrtc/7580
blink::WebRTCRtcpParameters GetWebRTCRtcpParameters() {
return blink::WebRTCRtcpParameters();
}
blink::WebRTCRtpCodecParameters GetWebRTCRtpCodecParameters(
const webrtc::RtpCodecParameters& codec_parameters) {
return blink::WebRTCRtpCodecParameters(
codec_parameters.payload_type,
blink::WebString::FromASCII(codec_parameters.mime_type()),
ToBaseOptional<uint32_t>(codec_parameters.clock_rate),
ToBaseOptional<uint16_t>(codec_parameters.num_channels),
// TODO(orphis): Convert the parameters field to sdpFmtpLine
// https://crbug.com/webrtc/7580
blink::WebString());
}
} // namespace content
// Copyright (c) 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef CONTENT_RENDERER_MEDIA_WEBRTC_RTC_RTP_PARAMETERS_H_
#define CONTENT_RENDERER_MEDIA_WEBRTC_RTC_RTP_PARAMETERS_H_
#include "base/memory/ref_counted.h"
#include "content/common/content_export.h"
#include "third_party/blink/public/platform/web_rtc_rtp_parameters.h"
#include "third_party/webrtc/api/rtpparameters.h"
namespace content {
CONTENT_EXPORT blink::WebRTCRtpEncodingParameters
GetWebRTCRtpEncodingParameters(
const webrtc::RtpEncodingParameters& encoding_parameters);
CONTENT_EXPORT webrtc::RtpEncodingParameters FromWebRTCRtpEncodingParameters(
const blink::WebRTCRtpEncodingParameters& encoding_parameters);
CONTENT_EXPORT blink::WebRTCRtpHeaderExtensionParameters
GetWebRTCRtpHeaderExtensionParameters(
const webrtc::RtpHeaderExtensionParameters& header_extension_parameters);
CONTENT_EXPORT blink::WebRTCRtcpParameters GetWebRTCRtcpParameters();
CONTENT_EXPORT blink::WebRTCRtpCodecParameters GetWebRTCRtpCodecParameters(
const webrtc::RtpCodecParameters& codec_parameters);
CONTENT_EXPORT blink::WebRTCRtpParameters GetWebRTCRtpParameters(
const webrtc::RtpParameters& parameters);
CONTENT_EXPORT webrtc::DegradationPreference ToDegradationPreference(
blink::WebRTCDegradationPreference degradation_preference);
} // namespace content
#endif // CONTENT_RENDERER_MEDIA_WEBRTC_RTC_RTP_PARAMETERS_H_
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "content/renderer/media/webrtc/rtc_rtp_parameters.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace content {
TEST(RTCRtpParametersTest, Read) {
webrtc::RtpParameters webrtc_parameters;
webrtc_parameters.transaction_id = "transaction_id";
webrtc_parameters.degradation_preference =
webrtc::DegradationPreference::BALANCED;
webrtc_parameters.encodings.emplace_back();
webrtc::RtpEncodingParameters& webrtc_encoding =
webrtc_parameters.encodings.front();
webrtc_encoding.codec_payload_type = 42;
webrtc_encoding.dtx = webrtc::DtxStatus::ENABLED;
webrtc_encoding.active = true;
webrtc_encoding.bitrate_priority = webrtc::kDefaultBitratePriority;
webrtc_encoding.ptime = 1337;
webrtc_encoding.max_bitrate_bps = 1337000;
webrtc_encoding.max_framerate = 60;
webrtc_encoding.scale_resolution_down_by = 1.;
webrtc_encoding.rid = "rid";
webrtc_parameters.header_extensions.emplace_back();
webrtc::RtpHeaderExtensionParameters& webrtc_header_extension =
webrtc_parameters.header_extensions.front();
webrtc_header_extension.uri = "uri";
webrtc_header_extension.id = 33;
webrtc_header_extension.encrypt = true;
webrtc_parameters.codecs.emplace_back();
webrtc::RtpCodecParameters& webrtc_codec_parameter =
webrtc_parameters.codecs.front();
webrtc_codec_parameter.payload_type = 42;
webrtc_codec_parameter.clock_rate = 1234;
webrtc_codec_parameter.num_channels = 2;
blink::WebRTCRtpParameters parameters =
GetWebRTCRtpParameters(webrtc_parameters);
EXPECT_EQ(parameters.TransactionId(), "transaction_id");
EXPECT_EQ(parameters.DegradationPreference(),
blink::WebRTCDegradationPreference::Balanced);
ASSERT_EQ(parameters.Encodings().size(), 1u);
for (const auto& encoding : parameters.Encodings()) {
EXPECT_EQ(encoding.CodecPayloadType(), 42);
EXPECT_EQ(encoding.Dtx(), blink::WebRTCDtxStatus::Enabled);
EXPECT_EQ(encoding.Active(), true);
EXPECT_EQ(encoding.Priority(), blink::WebRTCPriorityType::Low);
EXPECT_EQ(encoding.Ptime(), 1337u);
EXPECT_EQ(encoding.MaxBitrate(), 1337000u);
EXPECT_EQ(encoding.MaxFramerate(), 60u);
EXPECT_EQ(encoding.ScaleResolutionDownBy(), 1.);
EXPECT_EQ(encoding.Rid(), "rid");
}
ASSERT_EQ(parameters.HeaderExtensions().size(), 1u);
for (const auto& header_extension : parameters.HeaderExtensions()) {
EXPECT_EQ(header_extension.URI(), "uri");
EXPECT_EQ(header_extension.Id(), 33);
EXPECT_EQ(header_extension.Encrypted(), true);
}
ASSERT_EQ(parameters.Codecs().size(), 1u);
for (const auto& codec : parameters.Codecs()) {
EXPECT_EQ(codec.PayloadType(), 42);
EXPECT_EQ(codec.ClockRate(), 1234u);
EXPECT_EQ(codec.Channels(), 2u);
}
}
TEST(RTCRtpParametersTest, WriteEncodingParameters) {
blink::WebRTCRtpEncodingParameters web_encoding_parameters(
42, blink::WebRTCDtxStatus::Enabled, true,
blink::WebRTCPriorityType::High, 1337, 50000, 60, 0.5, "rid");
webrtc::RtpEncodingParameters encoding_parameters =
content::FromWebRTCRtpEncodingParameters(web_encoding_parameters);
EXPECT_EQ(encoding_parameters.codec_payload_type, 42);
EXPECT_EQ(encoding_parameters.dtx, webrtc::DtxStatus::ENABLED);
EXPECT_EQ(encoding_parameters.active, true);
EXPECT_EQ(encoding_parameters.bitrate_priority, 4.0);
EXPECT_EQ(encoding_parameters.ptime, 1337);
EXPECT_EQ(encoding_parameters.max_bitrate_bps, 50000);
EXPECT_EQ(encoding_parameters.max_framerate, 60);
EXPECT_EQ(encoding_parameters.scale_resolution_down_by, 0.5);
EXPECT_EQ(encoding_parameters.rid, "rid");
}
TEST(RTCRtpParametersTest, CheckDtxStatusEnum) {
webrtc::RtpEncodingParameters webrtc_encoding_parameters;
{
webrtc_encoding_parameters.dtx = webrtc::DtxStatus::DISABLED;
blink::WebRTCRtpEncodingParameters encoding_parameters =
GetWebRTCRtpEncodingParameters(webrtc_encoding_parameters);
EXPECT_EQ(encoding_parameters.Dtx(), blink::WebRTCDtxStatus::Disabled);
}
{
webrtc_encoding_parameters.dtx = webrtc::DtxStatus::ENABLED;
blink::WebRTCRtpEncodingParameters encoding_parameters =
GetWebRTCRtpEncodingParameters(webrtc_encoding_parameters);
EXPECT_EQ(encoding_parameters.Dtx(), blink::WebRTCDtxStatus::Enabled);
}
}
TEST(RTCRtpParametersTest, CheckDegradationPreferenceEnum) {
webrtc::RtpParameters webrtc_parameters;
{
webrtc_parameters.degradation_preference =
webrtc::DegradationPreference::MAINTAIN_FRAMERATE;
blink::WebRTCRtpParameters parameters =
GetWebRTCRtpParameters(webrtc_parameters);
EXPECT_EQ(parameters.DegradationPreference(),
blink::WebRTCDegradationPreference::MaintainFramerate);
}
{
webrtc_parameters.degradation_preference =
webrtc::DegradationPreference::MAINTAIN_RESOLUTION;
blink::WebRTCRtpParameters parameters =
GetWebRTCRtpParameters(webrtc_parameters);
EXPECT_EQ(parameters.DegradationPreference(),
blink::WebRTCDegradationPreference::MaintainResolution);
}
{
webrtc_parameters.degradation_preference =
webrtc::DegradationPreference::BALANCED;
blink::WebRTCRtpParameters parameters =
GetWebRTCRtpParameters(webrtc_parameters);
EXPECT_EQ(parameters.DegradationPreference(),
blink::WebRTCDegradationPreference::Balanced);
}
}
TEST(RTCRtpParametersTest, CheckPriorityEnum) {
webrtc::RtpEncodingParameters webrtc_encoding_parameters;
{
webrtc_encoding_parameters.bitrate_priority =
webrtc::kDefaultBitratePriority / 2;
blink::WebRTCRtpEncodingParameters encoding_parameters =
GetWebRTCRtpEncodingParameters(webrtc_encoding_parameters);
EXPECT_EQ(encoding_parameters.Priority(),
blink::WebRTCPriorityType::VeryLow);
}
{
webrtc_encoding_parameters.bitrate_priority =
webrtc::kDefaultBitratePriority;
blink::WebRTCRtpEncodingParameters encoding_parameters =
GetWebRTCRtpEncodingParameters(webrtc_encoding_parameters);
EXPECT_EQ(encoding_parameters.Priority(), blink::WebRTCPriorityType::Low);
}
{
webrtc_encoding_parameters.bitrate_priority =
webrtc::kDefaultBitratePriority * 2;
blink::WebRTCRtpEncodingParameters encoding_parameters =
GetWebRTCRtpEncodingParameters(webrtc_encoding_parameters);
EXPECT_EQ(encoding_parameters.Priority(),
blink::WebRTCPriorityType::Medium);
}
{
webrtc_encoding_parameters.bitrate_priority =
webrtc::kDefaultBitratePriority * 4;
blink::WebRTCRtpEncodingParameters encoding_parameters =
GetWebRTCRtpEncodingParameters(webrtc_encoding_parameters);
EXPECT_EQ(encoding_parameters.Priority(), blink::WebRTCPriorityType::High);
}
}
} // namespace content
...@@ -4,11 +4,11 @@ ...@@ -4,11 +4,11 @@
#include "content/renderer/media/webrtc/rtc_rtp_sender.h" #include "content/renderer/media/webrtc/rtc_rtp_sender.h"
#include <memory>
#include <utility> #include <utility>
#include "base/logging.h" #include "base/logging.h"
#include "content/renderer/media/webrtc/rtc_dtmf_sender_handler.h" #include "content/renderer/media/webrtc/rtc_dtmf_sender_handler.h"
#include "content/renderer/media/webrtc/rtc_rtp_parameters.h"
#include "content/renderer/media/webrtc/rtc_stats.h" #include "content/renderer/media/webrtc/rtc_stats.h"
namespace content { namespace content {
...@@ -138,29 +138,26 @@ class RTCRtpSender::RTCRtpSenderInternal ...@@ -138,29 +138,26 @@ class RTCRtpSender::RTCRtpSenderInternal
return std::make_unique<RtcDtmfSenderHandler>(dtmf_sender); return std::make_unique<RtcDtmfSenderHandler>(dtmf_sender);
} }
std::unique_ptr<blink::WebRTCRtpParameters> GetParameters() { std::unique_ptr<webrtc::RtpParameters> GetParameters() {
parameters_ = webrtc_sender_->GetParameters(); parameters_ = webrtc_sender_->GetParameters();
return std::make_unique<blink::WebRTCRtpParameters>( return std::make_unique<webrtc::RtpParameters>(parameters_);
GetWebRTCRtpParameters(parameters_));
} }
void SetParameters( void SetParameters(blink::WebVector<webrtc::RtpEncodingParameters> encodings,
blink::WebVector<blink::WebRTCRtpEncodingParameters> encodings, webrtc::DegradationPreference degradation_preference,
blink::WebRTCDegradationPreference degradation_preference, base::OnceCallback<void(webrtc::RTCError)> callback) {
base::OnceCallback<void(webrtc::RTCError)> callback) {
DCHECK(main_thread_->BelongsToCurrentThread()); DCHECK(main_thread_->BelongsToCurrentThread());
webrtc::RtpParameters new_parameters = parameters_; webrtc::RtpParameters new_parameters = parameters_;
new_parameters.degradation_preference = new_parameters.degradation_preference = degradation_preference;
ToDegradationPreference(degradation_preference);
for (std::size_t i = 0; i < new_parameters.encodings.size(); ++i) { for (std::size_t i = 0; i < new_parameters.encodings.size(); ++i) {
// Encodings have other parameters in the native layer that aren't exposed // Encodings have other parameters in the native layer that aren't exposed
// to the blink layer. So instead of copying the new struct over the old // to the blink layer. So instead of copying the new struct over the old
// one, we copy the members one by one over the old struct, effectively // one, we copy the members one by one over the old struct, effectively
// patching the changes done by the user. // patching the changes done by the user.
auto encoding = FromWebRTCRtpEncodingParameters(encodings[i]); const auto& encoding = encodings[i];
new_parameters.encodings[i].codec_payload_type = new_parameters.encodings[i].codec_payload_type =
encoding.codec_payload_type; encoding.codec_payload_type;
new_parameters.encodings[i].dtx = encoding.dtx; new_parameters.encodings[i].dtx = encoding.dtx;
...@@ -366,14 +363,13 @@ std::unique_ptr<blink::WebRTCDTMFSenderHandler> RTCRtpSender::GetDtmfSender() ...@@ -366,14 +363,13 @@ std::unique_ptr<blink::WebRTCDTMFSenderHandler> RTCRtpSender::GetDtmfSender()
return internal_->GetDtmfSender(); return internal_->GetDtmfSender();
} }
std::unique_ptr<blink::WebRTCRtpParameters> RTCRtpSender::GetParameters() std::unique_ptr<webrtc::RtpParameters> RTCRtpSender::GetParameters() const {
const {
return internal_->GetParameters(); return internal_->GetParameters();
} }
void RTCRtpSender::SetParameters( void RTCRtpSender::SetParameters(
blink::WebVector<blink::WebRTCRtpEncodingParameters> encodings, blink::WebVector<webrtc::RtpEncodingParameters> encodings,
blink::WebRTCDegradationPreference degradation_preference, webrtc::DegradationPreference degradation_preference,
blink::WebRTCVoidRequest request) { blink::WebRTCVoidRequest request) {
internal_->SetParameters( internal_->SetParameters(
std::move(encodings), degradation_preference, std::move(encodings), degradation_preference,
......
...@@ -63,9 +63,9 @@ class CONTENT_EXPORT RTCRtpSender : public blink::WebRTCRtpSender { ...@@ -63,9 +63,9 @@ class CONTENT_EXPORT RTCRtpSender : public blink::WebRTCRtpSender {
blink::WebRTCVoidRequest request) override; blink::WebRTCVoidRequest request) override;
std::unique_ptr<blink::WebRTCDTMFSenderHandler> GetDtmfSender() std::unique_ptr<blink::WebRTCDTMFSenderHandler> GetDtmfSender()
const override; const override;
std::unique_ptr<blink::WebRTCRtpParameters> GetParameters() const override; std::unique_ptr<webrtc::RtpParameters> GetParameters() const override;
void SetParameters(blink::WebVector<blink::WebRTCRtpEncodingParameters>, void SetParameters(blink::WebVector<webrtc::RtpEncodingParameters>,
blink::WebRTCDegradationPreference, webrtc::DegradationPreference,
blink::WebRTCVoidRequest) override; blink::WebRTCVoidRequest) override;
void GetStats(std::unique_ptr<blink::WebRTCStatsReportCallback>) override; void GetStats(std::unique_ptr<blink::WebRTCStatsReportCallback>) override;
......
...@@ -1659,7 +1659,6 @@ test("content_unittests") { ...@@ -1659,7 +1659,6 @@ test("content_unittests") {
"../renderer/media/webrtc/peer_connection_tracker_unittest.cc", "../renderer/media/webrtc/peer_connection_tracker_unittest.cc",
"../renderer/media/webrtc/rtc_data_channel_handler_unittest.cc", "../renderer/media/webrtc/rtc_data_channel_handler_unittest.cc",
"../renderer/media/webrtc/rtc_peer_connection_handler_unittest.cc", "../renderer/media/webrtc/rtc_peer_connection_handler_unittest.cc",
"../renderer/media/webrtc/rtc_rtp_parameters_unittest.cc",
"../renderer/media/webrtc/rtc_rtp_receiver_unittest.cc", "../renderer/media/webrtc/rtc_rtp_receiver_unittest.cc",
"../renderer/media/webrtc/rtc_rtp_sender_unittest.cc", "../renderer/media/webrtc/rtc_rtp_sender_unittest.cc",
"../renderer/media/webrtc/rtc_stats_unittest.cc", "../renderer/media/webrtc/rtc_stats_unittest.cc",
......
// Copyright 2016 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef THIRD_PARTY_BLINK_PUBLIC_PLATFORM_WEB_RTC_RTP_PARAMETERS_H_
#define THIRD_PARTY_BLINK_PUBLIC_PLATFORM_WEB_RTC_RTP_PARAMETERS_H_
#include <memory>
#include <string>
#include <vector>
#include "base/optional.h"
#include "third_party/blink/public/platform/web_common.h"
#include "third_party/blink/public/platform/web_string.h"
#include "third_party/blink/public/platform/web_vector.h"
namespace blink {
enum class WebRTCPriorityType {
VeryLow,
Low,
Medium,
High,
};
enum class WebRTCDegradationPreference {
MaintainFramerate,
MaintainResolution,
Balanced,
};
enum class WebRTCDtxStatus {
Disabled,
Enabled,
};
class BLINK_PLATFORM_EXPORT WebRTCRtpEncodingParameters {
public:
WebRTCRtpEncodingParameters();
WebRTCRtpEncodingParameters(
const base::Optional<uint8_t>& codec_payload_type,
const base::Optional<WebRTCDtxStatus>& dtx,
bool active,
WebRTCPriorityType priority,
const base::Optional<uint32_t>& ptime,
const base::Optional<uint32_t>& max_bitrate,
const base::Optional<uint32_t>& max_framerate,
const base::Optional<double>& scale_resolution_down_by,
const base::Optional<WebString>& rid)
: codec_payload_type_(codec_payload_type),
dtx_(dtx),
active_(active),
priority_(priority),
ptime_(ptime),
max_bitrate_(max_bitrate),
max_framerate_(max_framerate),
scale_resolution_down_by_(scale_resolution_down_by),
rid_(rid) {}
const base::Optional<uint8_t>& CodecPayloadType() const {
return codec_payload_type_;
}
const base::Optional<WebRTCDtxStatus>& Dtx() const { return dtx_; }
bool Active() const { return active_; }
WebRTCPriorityType Priority() const { return priority_; }
const base::Optional<uint32_t>& Ptime() const { return ptime_; }
const base::Optional<uint32_t>& MaxBitrate() const { return max_bitrate_; }
const base::Optional<uint32_t>& MaxFramerate() const {
return max_framerate_;
}
const base::Optional<double>& ScaleResolutionDownBy() const {
return scale_resolution_down_by_;
}
const base::Optional<WebString> Rid() const { return rid_; }
private:
base::Optional<uint8_t> codec_payload_type_;
base::Optional<WebRTCDtxStatus> dtx_;
bool active_;
WebRTCPriorityType priority_;
base::Optional<uint32_t> ptime_;
base::Optional<uint32_t> max_bitrate_;
base::Optional<uint32_t> max_framerate_;
base::Optional<double> scale_resolution_down_by_;
base::Optional<WebString> rid_;
};
class BLINK_PLATFORM_EXPORT WebRTCRtpHeaderExtensionParameters {
public:
WebRTCRtpHeaderExtensionParameters();
WebRTCRtpHeaderExtensionParameters(const base::Optional<WebString>& uri,
const base::Optional<uint16_t>& id,
const base::Optional<bool>& encrypted)
: uri_(uri), id_(id), encrypted_(encrypted) {}
const base::Optional<WebString>& URI() const { return uri_; }
const base::Optional<uint16_t>& Id() const { return id_; }
const base::Optional<bool>& Encrypted() const { return encrypted_; }
private:
base::Optional<WebString> uri_;
base::Optional<uint16_t> id_;
base::Optional<bool> encrypted_;
};
class BLINK_PLATFORM_EXPORT WebRTCRtcpParameters {
public:
const base::Optional<WebString>& Cname() const;
const base::Optional<bool>& ReducedSize() const;
private:
base::Optional<WebString> cname_;
base::Optional<bool> reduced_size_;
};
class BLINK_PLATFORM_EXPORT WebRTCRtpCodecParameters {
public:
WebRTCRtpCodecParameters() = default;
WebRTCRtpCodecParameters(const base::Optional<uint8_t>& payload_type,
const base::Optional<WebString>& mime_type,
const base::Optional<uint32_t>& clock_rate,
const base::Optional<uint16_t>& channels,
const base::Optional<WebString>& sdp_fmtp_line)
: payload_type_(payload_type),
mime_type_(mime_type),
clock_rate_(clock_rate),
channels_(channels),
sdp_fmtp_line_(sdp_fmtp_line) {}
const base::Optional<uint8_t>& PayloadType() const { return payload_type_; }
const base::Optional<WebString>& MimeType() const { return mime_type_; }
const base::Optional<uint32_t>& ClockRate() const { return clock_rate_; }
const base::Optional<uint16_t>& Channels() const { return channels_; }
const base::Optional<WebString>& SdpFmtpLine() const {
return sdp_fmtp_line_;
}
private:
base::Optional<uint8_t> payload_type_;
base::Optional<WebString> mime_type_;
base::Optional<uint32_t> clock_rate_;
base::Optional<uint16_t> channels_;
base::Optional<WebString> sdp_fmtp_line_;
};
class BLINK_PLATFORM_EXPORT WebRTCRtpParameters {
public:
WebRTCRtpParameters(
const base::Optional<WebString>& transaction_id,
const WebRTCRtcpParameters& rtcp,
const WebVector<WebRTCRtpEncodingParameters>& encodings,
const WebVector<WebRTCRtpHeaderExtensionParameters>& header_extensions,
const WebVector<WebRTCRtpCodecParameters>& codecs,
const base::Optional<WebRTCDegradationPreference>& degradation_preference)
: transaction_id_(transaction_id),
rtcp_(rtcp),
encodings_(encodings),
header_extensions_(header_extensions),
codecs_(codecs),
degradation_preference_(degradation_preference) {}
const base::Optional<WebString>& TransactionId() const {
return transaction_id_;
}
const WebVector<WebRTCRtpEncodingParameters>& Encodings() const {
return encodings_;
}
const WebVector<WebRTCRtpHeaderExtensionParameters>& HeaderExtensions()
const {
return header_extensions_;
}
const WebRTCRtcpParameters& Rtcp() const { return rtcp_; }
const WebVector<WebRTCRtpCodecParameters>& Codecs() const { return codecs_; }
const base::Optional<WebRTCDegradationPreference>& DegradationPreference()
const {
return degradation_preference_;
}
private:
base::Optional<WebString> transaction_id_;
WebRTCRtcpParameters rtcp_;
WebVector<WebRTCRtpEncodingParameters> encodings_;
WebVector<WebRTCRtpHeaderExtensionParameters> header_extensions_;
WebVector<WebRTCRtpCodecParameters> codecs_;
base::Optional<WebRTCDegradationPreference> degradation_preference_;
};
} // namespace blink
#endif // THIRD_PARTY_BLINK_PUBLIC_PLATFORM_WEB_RTC_RTP_PARAMETERS_H_
...@@ -6,10 +6,10 @@ ...@@ -6,10 +6,10 @@
#define THIRD_PARTY_BLINK_PUBLIC_PLATFORM_WEB_RTC_RTP_SENDER_H_ #define THIRD_PARTY_BLINK_PUBLIC_PLATFORM_WEB_RTC_RTP_SENDER_H_
#include "third_party/blink/public/platform/web_common.h" #include "third_party/blink/public/platform/web_common.h"
#include "third_party/blink/public/platform/web_rtc_rtp_parameters.h"
#include "third_party/blink/public/platform/web_rtc_stats.h" #include "third_party/blink/public/platform/web_rtc_stats.h"
#include "third_party/blink/public/platform/web_rtc_void_request.h" #include "third_party/blink/public/platform/web_rtc_void_request.h"
#include "third_party/blink/public/platform/web_string.h" #include "third_party/blink/public/platform/web_string.h"
#include "third_party/webrtc/api/rtpparameters.h"
namespace blink { namespace blink {
...@@ -34,9 +34,9 @@ class BLINK_PLATFORM_EXPORT WebRTCRtpSender { ...@@ -34,9 +34,9 @@ class BLINK_PLATFORM_EXPORT WebRTCRtpSender {
// https://crbug.com/790007 // https://crbug.com/790007
virtual void ReplaceTrack(WebMediaStreamTrack, WebRTCVoidRequest) = 0; virtual void ReplaceTrack(WebMediaStreamTrack, WebRTCVoidRequest) = 0;
virtual std::unique_ptr<WebRTCDTMFSenderHandler> GetDtmfSender() const = 0; virtual std::unique_ptr<WebRTCDTMFSenderHandler> GetDtmfSender() const = 0;
virtual std::unique_ptr<WebRTCRtpParameters> GetParameters() const = 0; virtual std::unique_ptr<webrtc::RtpParameters> GetParameters() const = 0;
virtual void SetParameters(WebVector<WebRTCRtpEncodingParameters>, virtual void SetParameters(blink::WebVector<webrtc::RtpEncodingParameters>,
WebRTCDegradationPreference, webrtc::DegradationPreference,
WebRTCVoidRequest) = 0; WebRTCVoidRequest) = 0;
virtual void GetStats(std::unique_ptr<blink::WebRTCStatsReportCallback>) = 0; virtual void GetStats(std::unique_ptr<blink::WebRTCStatsReportCallback>) = 0;
}; };
......
...@@ -10,7 +10,6 @@ ...@@ -10,7 +10,6 @@
#include "third_party/blink/renderer/modules/peerconnection/rtc_dtmf_sender.h" #include "third_party/blink/renderer/modules/peerconnection/rtc_dtmf_sender.h"
#include "third_party/blink/renderer/modules/peerconnection/rtc_error_util.h" #include "third_party/blink/renderer/modules/peerconnection/rtc_error_util.h"
#include "third_party/blink/renderer/modules/peerconnection/rtc_peer_connection.h" #include "third_party/blink/renderer/modules/peerconnection/rtc_peer_connection.h"
#include "third_party/blink/renderer/modules/peerconnection/rtc_rtp_parameters.h"
#include "third_party/blink/renderer/modules/peerconnection/rtc_void_request_script_promise_resolver_impl.h" #include "third_party/blink/renderer/modules/peerconnection/rtc_void_request_script_promise_resolver_impl.h"
#include "third_party/blink/renderer/modules/peerconnection/web_rtc_stats_report_callback_resolver.h" #include "third_party/blink/renderer/modules/peerconnection/web_rtc_stats_report_callback_resolver.h"
#include "third_party/blink/renderer/platform/peerconnection/rtc_void_request.h" #include "third_party/blink/renderer/platform/peerconnection/rtc_void_request.h"
...@@ -165,10 +164,56 @@ bool HasInvalidModification(const RTCRtpParameters& parameters, ...@@ -165,10 +164,56 @@ bool HasInvalidModification(const RTCRtpParameters& parameters,
return false; return false;
} }
std::tuple<WebVector<WebRTCRtpEncodingParameters>, // Relative weights for each priority as defined in RTCWEB-DATA
base::Optional<WebRTCDegradationPreference>> // https://tools.ietf.org/html/draft-ietf-rtcweb-data-channel
ToWebRTCRtpParameters(const RTCRtpParameters& parameters) { const double kPriorityWeightVeryLow = 0.5;
WebVector<WebRTCRtpEncodingParameters> encodings; const double kPriorityWeightLow = 1;
const double kPriorityWeightMedium = 2;
const double kPriorityWeightHigh = 4;
std::string PriorityFromDouble(double priority) {
// Find the middle point between 2 priority weights to match them to a
// WebRTC priority
const double very_low_upper_bound =
(kPriorityWeightVeryLow + kPriorityWeightLow) / 2;
const double low_upper_bound =
(kPriorityWeightLow + kPriorityWeightMedium) / 2;
const double medium_upper_bound =
(kPriorityWeightMedium + kPriorityWeightHigh) / 2;
if (priority < webrtc::kDefaultBitratePriority * very_low_upper_bound) {
return "very-low";
}
if (priority < webrtc::kDefaultBitratePriority * low_upper_bound) {
return "low";
}
if (priority < webrtc::kDefaultBitratePriority * medium_upper_bound) {
return "medium";
}
return "high";
}
double PriorityToDouble(const WTF::String& priority) {
double result = 1;
if (priority == "very-low") {
result = webrtc::kDefaultBitratePriority * kPriorityWeightVeryLow;
} else if (priority == "low") {
result = webrtc::kDefaultBitratePriority * kPriorityWeightLow;
} else if (priority == "medium") {
result = webrtc::kDefaultBitratePriority * kPriorityWeightMedium;
} else if (priority == "high") {
result = webrtc::kDefaultBitratePriority * kPriorityWeightHigh;
} else {
NOTREACHED();
}
return result;
}
std::tuple<std::vector<webrtc::RtpEncodingParameters>,
webrtc::DegradationPreference>
ToRtpParameters(const RTCRtpParameters& parameters) {
std::vector<webrtc::RtpEncodingParameters> encodings;
if (parameters.hasEncodings()) { if (parameters.hasEncodings()) {
encodings.reserve(parameters.encodings().size()); encodings.reserve(parameters.encodings().size());
...@@ -176,39 +221,16 @@ ToWebRTCRtpParameters(const RTCRtpParameters& parameters) { ...@@ -176,39 +221,16 @@ ToWebRTCRtpParameters(const RTCRtpParameters& parameters) {
// TODO(orphis): Forward missing fields from the WebRTC library: // TODO(orphis): Forward missing fields from the WebRTC library:
// codecPayloadType, dtx, ptime, maxFramerate, scaleResolutionDownBy, // codecPayloadType, dtx, ptime, maxFramerate, scaleResolutionDownBy,
// rid // rid
const base::Optional<uint8_t> codec_payload_type; encodings.push_back({});
const base::Optional<WebRTCDtxStatus> dtx; encodings.back().active = encoding.active();
bool active = encoding.active(); encodings.back().bitrate_priority = PriorityToDouble(encoding.priority());
if (encoding.hasMaxBitrate())
const auto& js_priority = encoding.priority(); encodings.back().max_bitrate_bps = clampTo<int>(encoding.maxBitrate());
WebRTCPriorityType priority;
if (js_priority == "very-low") {
priority = WebRTCPriorityType::VeryLow;
} else if (js_priority == "low") {
priority = WebRTCPriorityType::Low;
} else if (js_priority == "medium") {
priority = WebRTCPriorityType::Medium;
} else if (js_priority == "high") {
priority = WebRTCPriorityType::High;
} else {
NOTREACHED();
}
const base::Optional<uint32_t> ptime;
const base::Optional<uint32_t> max_bitrate =
encoding.hasMaxBitrate() ? encoding.maxBitrate()
: base::Optional<uint32_t>();
const base::Optional<uint32_t> max_framerate;
const base::Optional<double> scale_resolution_down_by;
const base::Optional<WebString> rid;
encodings.emplace_back(codec_payload_type, dtx, active, priority, ptime,
max_bitrate, max_framerate,
scale_resolution_down_by, rid);
} }
} }
base::Optional<WebRTCDegradationPreference> degradation_preference;
webrtc::DegradationPreference degradation_preference =
webrtc::DegradationPreference::BALANCED;
return std::make_tuple(encodings, degradation_preference); return std::make_tuple(encodings, degradation_preference);
} }
...@@ -253,56 +275,37 @@ ScriptPromise RTCRtpSender::replaceTrack(ScriptState* script_state, ...@@ -253,56 +275,37 @@ ScriptPromise RTCRtpSender::replaceTrack(ScriptState* script_state,
void RTCRtpSender::getParameters(RTCRtpParameters& parameters) { void RTCRtpSender::getParameters(RTCRtpParameters& parameters) {
// TODO(orphis): Forward missing fields from the WebRTC library: // TODO(orphis): Forward missing fields from the WebRTC library:
// transactionId, rtcp, headerExtensions, degradationPreference // transactionId, rtcp, headerExtensions, degradationPreference
std::unique_ptr<WebRTCRtpParameters> web_parameters = std::unique_ptr<webrtc::RtpParameters> webrtc_parameters =
sender_->GetParameters(); sender_->GetParameters();
HeapVector<RTCRtpEncodingParameters> encodings; HeapVector<RTCRtpEncodingParameters> encodings;
encodings.ReserveCapacity(web_parameters->Encodings().size()); encodings.ReserveCapacity(webrtc_parameters->encodings.size());
for (const auto& web_encoding : web_parameters->Encodings()) { for (const auto& web_encoding : webrtc_parameters->encodings) {
// TODO(orphis): Forward missing fields from the WebRTC library: // TODO(orphis): Forward missing fields from the WebRTC library:
// codecPayloadType, dtx, ptime, maxFramerate, scaleResolutionDownBy, rid // codecPayloadType, dtx, ptime, maxFramerate, scaleResolutionDownBy, rid
encodings.emplace_back(); encodings.emplace_back();
RTCRtpEncodingParameters& encoding = encodings.back(); RTCRtpEncodingParameters& encoding = encodings.back();
encoding.setActive(web_encoding.Active()); encoding.setActive(web_encoding.active);
if (web_encoding.MaxBitrate()) if (web_encoding.max_bitrate_bps)
encoding.setMaxBitrate(web_encoding.MaxBitrate().value()); encoding.setMaxBitrate(web_encoding.max_bitrate_bps.value());
encoding.setPriority(WTF::String::FromUTF8(
const char* priority = ""; PriorityFromDouble(web_encoding.bitrate_priority).c_str()));
switch (web_encoding.Priority()) {
case WebRTCPriorityType::VeryLow:
priority = "very-low";
break;
case WebRTCPriorityType::Low:
priority = "low";
break;
case WebRTCPriorityType::Medium:
priority = "medium";
break;
case WebRTCPriorityType::High:
priority = "high";
break;
default:
NOTREACHED();
}
encoding.setPriority(priority);
} }
parameters.setEncodings(encodings); parameters.setEncodings(encodings);
HeapVector<RTCRtpCodecParameters> codecs; HeapVector<RTCRtpCodecParameters> codecs;
codecs.ReserveCapacity(web_parameters->Codecs().size()); codecs.ReserveCapacity(webrtc_parameters->codecs.size());
for (const auto& web_codec : web_parameters->Codecs()) { for (const auto& web_codec : webrtc_parameters->codecs) {
// TODO(orphis): Forward missing field from the WebRTC library: // TODO(orphis): Forward missing field from the WebRTC library:
// sdpFmtpLine // sdpFmtpLine
codecs.emplace_back(); codecs.emplace_back();
RTCRtpCodecParameters& codec = codecs.back(); RTCRtpCodecParameters& codec = codecs.back();
if (web_codec.PayloadType()) codec.setPayloadType(web_codec.payload_type);
codec.setPayloadType(web_codec.PayloadType().value()); codec.setMimeType(WTF::String::FromUTF8(web_codec.mime_type().c_str()));
if (web_codec.MimeType()) if (web_codec.clock_rate)
codec.setMimeType(web_codec.MimeType().value()); codec.setClockRate(web_codec.clock_rate.value());
if (web_codec.ClockRate()) if (web_codec.num_channels)
codec.setClockRate(web_codec.ClockRate().value()); codec.setChannels(web_codec.num_channels.value());
if (web_codec.Channels())
codec.setChannels(web_codec.Channels().value());
} }
parameters.setCodecs(codecs); parameters.setCodecs(codecs);
...@@ -336,18 +339,12 @@ ScriptPromise RTCRtpSender::setParameters(ScriptState* script_state, ...@@ -336,18 +339,12 @@ ScriptPromise RTCRtpSender::setParameters(ScriptState* script_state,
// field and the degradationPreference field. We just forward those to the // field and the degradationPreference field. We just forward those to the
// native layer without having to transform all the other read-only // native layer without having to transform all the other read-only
// parameters. // parameters.
WebVector<WebRTCRtpEncodingParameters> encodings; std::vector<webrtc::RtpEncodingParameters> encodings;
base::Optional<WebRTCDegradationPreference> degradation_preference; webrtc::DegradationPreference degradation_preference;
std::tie(encodings, degradation_preference) = std::tie(encodings, degradation_preference) = ToRtpParameters(parameters);
ToWebRTCRtpParameters(parameters);
if (!degradation_preference) {
degradation_preference = blink::WebRTCDegradationPreference::Balanced;
}
auto* request = new SetParametersRequest(resolver, this); auto* request = new SetParametersRequest(resolver, this);
sender_->SetParameters(std::move(encodings), degradation_preference.value(), sender_->SetParameters(std::move(encodings), degradation_preference, request);
request);
return promise; return promise;
} }
......
...@@ -30,11 +30,11 @@ class DummyWebRTCRtpSender : public WebRTCRtpSender { ...@@ -30,11 +30,11 @@ class DummyWebRTCRtpSender : public WebRTCRtpSender {
std::unique_ptr<WebRTCDTMFSenderHandler> GetDtmfSender() const override { std::unique_ptr<WebRTCDTMFSenderHandler> GetDtmfSender() const override {
return nullptr; return nullptr;
} }
std::unique_ptr<WebRTCRtpParameters> GetParameters() const override { std::unique_ptr<webrtc::RtpParameters> GetParameters() const override {
return std::unique_ptr<WebRTCRtpParameters>(); return std::unique_ptr<webrtc::RtpParameters>();
} }
void SetParameters(WebVector<WebRTCRtpEncodingParameters>, void SetParameters(blink::WebVector<webrtc::RtpEncodingParameters>,
WebRTCDegradationPreference, webrtc::DegradationPreference,
WebRTCVoidRequest) override {} WebRTCVoidRequest) override {}
void GetStats(std::unique_ptr<blink::WebRTCStatsReportCallback>) override {} void GetStats(std::unique_ptr<blink::WebRTCStatsReportCallback>) override {}
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment