Adds trial to calculate audio overhead based on available data.

This adds the ability to disable legacy overhead calculation so we'll
use the available data on per packet over head and frame length range
to set the min and max total  allocatable bitrate.

Bug: webrtc:11001
Change-Id: I2a94499433e15bad11a08f81fe7f1dfc27982cdf
Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/155175
Reviewed-by: Oskar Sundbom <ossu@webrtc.org>
Commit-Queue: Sebastian Jansson <srte@webrtc.org>
Cr-Commit-Position: refs/heads/master@{#29368}
This commit is contained in:
Sebastian Jansson
2019-10-02 12:27:06 +02:00
committed by Commit Bot
parent f1e97b9ebd
commit 62aee9379c
9 changed files with 93 additions and 28 deletions

View File

@ -37,6 +37,7 @@ rtc_source_set("audio_codecs_api") {
"../../rtc_base:rtc_base_approved", "../../rtc_base:rtc_base_approved",
"../../rtc_base:sanitizer", "../../rtc_base:sanitizer",
"../../rtc_base/system:rtc_export", "../../rtc_base/system:rtc_export",
"../units:time_delta",
"//third_party/abseil-cpp/absl/strings", "//third_party/abseil-cpp/absl/strings",
"//third_party/abseil-cpp/absl/types:optional", "//third_party/abseil-cpp/absl/types:optional",
] ]

View File

@ -108,4 +108,9 @@ ANAStats AudioEncoder::GetANAStats() const {
return ANAStats(); return ANAStats();
} }
absl::optional<std::pair<TimeDelta, TimeDelta>>
AudioEncoder::GetFrameLengthRange() const {
return absl::nullopt;
}
} // namespace webrtc } // namespace webrtc

View File

@ -13,11 +13,13 @@
#include <memory> #include <memory>
#include <string> #include <string>
#include <utility>
#include <vector> #include <vector>
#include "absl/types/optional.h" #include "absl/types/optional.h"
#include "api/array_view.h" #include "api/array_view.h"
#include "api/call/bitrate_allocation.h" #include "api/call/bitrate_allocation.h"
#include "api/units/time_delta.h"
#include "rtc_base/buffer.h" #include "rtc_base/buffer.h"
#include "rtc_base/deprecation.h" #include "rtc_base/deprecation.h"
@ -241,6 +243,12 @@ class AudioEncoder {
// Get statistics related to audio network adaptation. // Get statistics related to audio network adaptation.
virtual ANAStats GetANAStats() const; virtual ANAStats GetANAStats() const;
// The range of frame lengths that are supported or nullopt if there's no sch
// information. This is used to calculated the full bitrate range, including
// overhead.
virtual absl::optional<std::pair<TimeDelta, TimeDelta>> GetFrameLengthRange()
const;
protected: protected:
// Subclasses implement this to perform the actual encoding. Called by // Subclasses implement this to perform the actual encoding. Called by
// Encode(). // Encode().

View File

@ -133,6 +133,8 @@ AudioSendStream::AudioSendStream(
audio_state_(audio_state), audio_state_(audio_state),
channel_send_(std::move(channel_send)), channel_send_(std::move(channel_send)),
event_log_(event_log), event_log_(event_log),
use_legacy_overhead_calculation_(
!field_trial::IsDisabled("WebRTC-Audio-LegacyOverhead")),
bitrate_allocator_(bitrate_allocator), bitrate_allocator_(bitrate_allocator),
rtp_transport_(rtp_transport), rtp_transport_(rtp_transport),
packet_loss_tracker_(kPacketLossTrackerMaxWindowSizeMs, packet_loss_tracker_(kPacketLossTrackerMaxWindowSizeMs,
@ -481,6 +483,7 @@ void AudioSendStream::DeliverRtcp(const uint8_t* packet, size_t length) {
} }
uint32_t AudioSendStream::OnBitrateUpdated(BitrateAllocationUpdate update) { uint32_t AudioSendStream::OnBitrateUpdated(BitrateAllocationUpdate update) {
RTC_DCHECK_RUN_ON(worker_queue_);
// Pick a target bitrate between the constraints. Overrules the allocator if // Pick a target bitrate between the constraints. Overrules the allocator if
// it 1) allocated a bitrate of zero to disable the stream or 2) allocated a // it 1) allocated a bitrate of zero to disable the stream or 2) allocated a
// higher than max to allow for e.g. extra FEC. // higher than max to allow for e.g. extra FEC.
@ -666,6 +669,11 @@ bool AudioSendStream::SetupSendCodec(AudioSendStream* stream,
encoder->OnReceivedOverhead(stream->GetPerPacketOverheadBytes()); encoder->OnReceivedOverhead(stream->GetPerPacketOverheadBytes());
} }
} }
stream->worker_queue_->PostTask(
[stream, length_range = encoder->GetFrameLengthRange()] {
RTC_DCHECK_RUN_ON(stream->worker_queue_);
stream->frame_length_range_ = length_range;
});
stream->StoreEncoderProperties(encoder->SampleRateHz(), stream->StoreEncoderProperties(encoder->SampleRateHz(),
encoder->NumChannels()); encoder->NumChannels());
@ -872,20 +880,25 @@ AudioSendStream::GetMinMaxBitrateConstraints() const {
if (allocation_settings_.MaxBitrate()) if (allocation_settings_.MaxBitrate())
constraints.max = *allocation_settings_.MaxBitrate(); constraints.max = *allocation_settings_.MaxBitrate();
RTC_DCHECK_GE(constraints.min.bps(), 0); RTC_DCHECK_GE(constraints.min, DataRate::Zero());
RTC_DCHECK_GE(constraints.max.bps(), 0); RTC_DCHECK_GE(constraints.max, DataRate::Zero());
RTC_DCHECK_GE(constraints.max.bps(), constraints.min.bps()); RTC_DCHECK_GE(constraints.max, constraints.min);
// TODO(srte,dklee): Replace these with proper overhead calculations.
if (allocation_settings_.IncludeOverheadInAudioAllocation()) { if (allocation_settings_.IncludeOverheadInAudioAllocation()) {
if (use_legacy_overhead_calculation_) {
// OverheadPerPacket = Ipv4(20B) + UDP(8B) + SRTP(10B) + RTP(12) // OverheadPerPacket = Ipv4(20B) + UDP(8B) + SRTP(10B) + RTP(12)
const DataSize kOverheadPerPacket = DataSize::bytes(20 + 8 + 10 + 12); const DataSize kOverheadPerPacket = DataSize::bytes(20 + 8 + 10 + 12);
const TimeDelta kMaxFrameLength = TimeDelta::ms(60); // Based on Opus spec const TimeDelta kMaxFrameLength =
TimeDelta::ms(60); // Based on Opus spec
const DataRate kMinOverhead = kOverheadPerPacket / kMaxFrameLength; const DataRate kMinOverhead = kOverheadPerPacket / kMaxFrameLength;
constraints.min += kMinOverhead; constraints.min += kMinOverhead;
// TODO(dklee): This is obviously overly conservative to avoid exceeding max
// bitrate. Carefully reconsider the logic when addressing todo above.
constraints.max += kMinOverhead; constraints.max += kMinOverhead;
} else {
RTC_DCHECK(frame_length_range_);
const DataSize kOverheadPerPacket =
DataSize::bytes(total_packet_overhead_bytes_);
constraints.min += kOverheadPerPacket / frame_length_range_->second;
constraints.max += kOverheadPerPacket / frame_length_range_->first;
}
} }
return constraints; return constraints;
} }

View File

@ -12,6 +12,7 @@
#define AUDIO_AUDIO_SEND_STREAM_H_ #define AUDIO_AUDIO_SEND_STREAM_H_
#include <memory> #include <memory>
#include <utility>
#include <vector> #include <vector>
#include "audio/audio_level.h" #include "audio/audio_level.h"
@ -133,7 +134,8 @@ class AudioSendStream final : public webrtc::AudioSendStream,
// Returns bitrate constraints, maybe including overhead when enabled by // Returns bitrate constraints, maybe including overhead when enabled by
// field trial. // field trial.
TargetAudioBitrateConstraints GetMinMaxBitrateConstraints() const; TargetAudioBitrateConstraints GetMinMaxBitrateConstraints() const
RTC_RUN_ON(worker_queue_);
// Sets per-packet overhead on encoded (for ANA) based on current known values // Sets per-packet overhead on encoded (for ANA) based on current known values
// of transport and packetization overheads. // of transport and packetization overheads.
@ -157,6 +159,7 @@ class AudioSendStream final : public webrtc::AudioSendStream,
rtc::scoped_refptr<webrtc::AudioState> audio_state_; rtc::scoped_refptr<webrtc::AudioState> audio_state_;
const std::unique_ptr<voe::ChannelSendInterface> channel_send_; const std::unique_ptr<voe::ChannelSendInterface> channel_send_;
RtcEventLog* const event_log_; RtcEventLog* const event_log_;
const bool use_legacy_overhead_calculation_;
int encoder_sample_rate_hz_ = 0; int encoder_sample_rate_hz_ = 0;
size_t encoder_num_channels_ = 0; size_t encoder_num_channels_ = 0;
@ -204,6 +207,8 @@ class AudioSendStream final : public webrtc::AudioSendStream,
bool registered_with_allocator_ RTC_GUARDED_BY(worker_queue_) = false; bool registered_with_allocator_ RTC_GUARDED_BY(worker_queue_) = false;
size_t total_packet_overhead_bytes_ RTC_GUARDED_BY(worker_queue_) = 0; size_t total_packet_overhead_bytes_ RTC_GUARDED_BY(worker_queue_) = 0;
absl::optional<std::pair<TimeDelta, TimeDelta>> frame_length_range_
RTC_GUARDED_BY(worker_queue_);
RTC_DISALLOW_IMPLICIT_CONSTRUCTORS(AudioSendStream); RTC_DISALLOW_IMPLICIT_CONSTRUCTORS(AudioSendStream);
}; };

View File

@ -83,8 +83,10 @@ const AudioCodecSpec kCodecSpecs[] = {
// should be made more precise in the future. This can be changed when that // should be made more precise in the future. This can be changed when that
// logic is more accurate. // logic is more accurate.
const DataSize kOverheadPerPacket = DataSize::bytes(20 + 8 + 10 + 12); const DataSize kOverheadPerPacket = DataSize::bytes(20 + 8 + 10 + 12);
const TimeDelta kMaxFrameLength = TimeDelta::ms(60); const TimeDelta kMinFrameLength = TimeDelta::ms(20);
const DataRate kOverheadRate = kOverheadPerPacket / kMaxFrameLength; const TimeDelta kMaxFrameLength = TimeDelta::ms(120);
const DataRate kMinOverheadRate = kOverheadPerPacket / kMaxFrameLength;
const DataRate kMaxOverheadRate = kOverheadPerPacket / kMinFrameLength;
class MockLimitObserver : public BitrateAllocator::LimitObserver { class MockLimitObserver : public BitrateAllocator::LimitObserver {
public: public:
@ -104,6 +106,9 @@ std::unique_ptr<MockAudioEncoder> SetupAudioEncoderMock(
.WillByDefault(Return(spec.info.num_channels)); .WillByDefault(Return(spec.info.num_channels));
ON_CALL(*encoder.get(), RtpTimestampRateHz()) ON_CALL(*encoder.get(), RtpTimestampRateHz())
.WillByDefault(Return(spec.format.clockrate_hz)); .WillByDefault(Return(spec.format.clockrate_hz));
ON_CALL(*encoder.get(), GetFrameLengthRange())
.WillByDefault(Return(absl::optional<std::pair<TimeDelta, TimeDelta>>{
{TimeDelta::ms(20), TimeDelta::ms(120)}}));
return encoder; return encoder;
} }
} }
@ -299,6 +304,7 @@ struct ConfigHelper {
EXPECT_CALL(*audio_processing_, GetStatistics(true)) EXPECT_CALL(*audio_processing_, GetStatistics(true))
.WillRepeatedly(Return(audio_processing_stats_)); .WillRepeatedly(Return(audio_processing_stats_));
} }
TaskQueueForTest* worker() { return &worker_queue_; }
private: private:
SimulatedClock clock_; SimulatedClock clock_;
@ -546,7 +552,7 @@ TEST(AudioSendStreamTest, DoesNotPassHigherBitrateThanMaxBitrate) {
update.packet_loss_ratio = 0; update.packet_loss_ratio = 0;
update.round_trip_time = TimeDelta::ms(50); update.round_trip_time = TimeDelta::ms(50);
update.bwe_period = TimeDelta::ms(6000); update.bwe_period = TimeDelta::ms(6000);
send_stream->OnBitrateUpdated(update); helper.worker()->SendTask([&] { send_stream->OnBitrateUpdated(update); });
} }
TEST(AudioSendStreamTest, SSBweTargetInRangeRespected) { TEST(AudioSendStreamTest, SSBweTargetInRangeRespected) {
@ -559,7 +565,7 @@ TEST(AudioSendStreamTest, SSBweTargetInRangeRespected) {
Eq(DataRate::bps(helper.config().max_bitrate_bps - 5000))))); Eq(DataRate::bps(helper.config().max_bitrate_bps - 5000)))));
BitrateAllocationUpdate update; BitrateAllocationUpdate update;
update.target_bitrate = DataRate::bps(helper.config().max_bitrate_bps - 5000); update.target_bitrate = DataRate::bps(helper.config().max_bitrate_bps - 5000);
send_stream->OnBitrateUpdated(update); helper.worker()->SendTask([&] { send_stream->OnBitrateUpdated(update); });
} }
TEST(AudioSendStreamTest, SSBweFieldTrialMinRespected) { TEST(AudioSendStreamTest, SSBweFieldTrialMinRespected) {
@ -574,7 +580,7 @@ TEST(AudioSendStreamTest, SSBweFieldTrialMinRespected) {
Eq(DataRate::kbps(6))))); Eq(DataRate::kbps(6)))));
BitrateAllocationUpdate update; BitrateAllocationUpdate update;
update.target_bitrate = DataRate::kbps(1); update.target_bitrate = DataRate::kbps(1);
send_stream->OnBitrateUpdated(update); helper.worker()->SendTask([&] { send_stream->OnBitrateUpdated(update); });
} }
TEST(AudioSendStreamTest, SSBweFieldTrialMaxRespected) { TEST(AudioSendStreamTest, SSBweFieldTrialMaxRespected) {
@ -589,55 +595,64 @@ TEST(AudioSendStreamTest, SSBweFieldTrialMaxRespected) {
Eq(DataRate::kbps(64))))); Eq(DataRate::kbps(64)))));
BitrateAllocationUpdate update; BitrateAllocationUpdate update;
update.target_bitrate = DataRate::kbps(128); update.target_bitrate = DataRate::kbps(128);
send_stream->OnBitrateUpdated(update); helper.worker()->SendTask([&] { send_stream->OnBitrateUpdated(update); });
} }
TEST(AudioSendStreamTest, SSBweWithOverhead) { TEST(AudioSendStreamTest, SSBweWithOverhead) {
ScopedFieldTrials field_trials( ScopedFieldTrials field_trials(
"WebRTC-Audio-SendSideBwe/Enabled/" "WebRTC-Audio-SendSideBwe/Enabled/"
"WebRTC-SendSideBwe-WithOverhead/Enabled/"); "WebRTC-SendSideBwe-WithOverhead/Enabled/"
"WebRTC-Audio-LegacyOverhead/Disabled/");
ConfigHelper helper(true, true); ConfigHelper helper(true, true);
auto send_stream = helper.CreateAudioSendStream(); auto send_stream = helper.CreateAudioSendStream();
EXPECT_CALL(*helper.channel_send(), CallEncoder(_)).Times(1);
send_stream->OnOverheadChanged(kOverheadPerPacket.bytes<size_t>());
const DataRate bitrate = const DataRate bitrate =
DataRate::bps(helper.config().max_bitrate_bps) + kOverheadRate; DataRate::bps(helper.config().max_bitrate_bps) + kMaxOverheadRate;
EXPECT_CALL(*helper.channel_send(), EXPECT_CALL(*helper.channel_send(),
OnBitrateAllocation(Field( OnBitrateAllocation(Field(
&BitrateAllocationUpdate::target_bitrate, Eq(bitrate)))); &BitrateAllocationUpdate::target_bitrate, Eq(bitrate))));
BitrateAllocationUpdate update; BitrateAllocationUpdate update;
update.target_bitrate = bitrate; update.target_bitrate = bitrate;
send_stream->OnBitrateUpdated(update); helper.worker()->SendTask([&] { send_stream->OnBitrateUpdated(update); });
} }
TEST(AudioSendStreamTest, SSBweWithOverheadMinRespected) { TEST(AudioSendStreamTest, SSBweWithOverheadMinRespected) {
ScopedFieldTrials field_trials( ScopedFieldTrials field_trials(
"WebRTC-Audio-SendSideBwe/Enabled/" "WebRTC-Audio-SendSideBwe/Enabled/"
"WebRTC-SendSideBwe-WithOverhead/Enabled/" "WebRTC-SendSideBwe-WithOverhead/Enabled/"
"WebRTC-Audio-LegacyOverhead/Disabled/"
"WebRTC-Audio-Allocation/min:6kbps,max:64kbps/"); "WebRTC-Audio-Allocation/min:6kbps,max:64kbps/");
ConfigHelper helper(true, true); ConfigHelper helper(true, true);
auto send_stream = helper.CreateAudioSendStream(); auto send_stream = helper.CreateAudioSendStream();
const DataRate bitrate = DataRate::kbps(6) + kOverheadRate; EXPECT_CALL(*helper.channel_send(), CallEncoder(_)).Times(1);
send_stream->OnOverheadChanged(kOverheadPerPacket.bytes<size_t>());
const DataRate bitrate = DataRate::kbps(6) + kMinOverheadRate;
EXPECT_CALL(*helper.channel_send(), EXPECT_CALL(*helper.channel_send(),
OnBitrateAllocation(Field( OnBitrateAllocation(Field(
&BitrateAllocationUpdate::target_bitrate, Eq(bitrate)))); &BitrateAllocationUpdate::target_bitrate, Eq(bitrate))));
BitrateAllocationUpdate update; BitrateAllocationUpdate update;
update.target_bitrate = DataRate::kbps(1); update.target_bitrate = DataRate::kbps(1);
send_stream->OnBitrateUpdated(update); helper.worker()->SendTask([&] { send_stream->OnBitrateUpdated(update); });
} }
TEST(AudioSendStreamTest, SSBweWithOverheadMaxRespected) { TEST(AudioSendStreamTest, SSBweWithOverheadMaxRespected) {
ScopedFieldTrials field_trials( ScopedFieldTrials field_trials(
"WebRTC-Audio-SendSideBwe/Enabled/" "WebRTC-Audio-SendSideBwe/Enabled/"
"WebRTC-SendSideBwe-WithOverhead/Enabled/" "WebRTC-SendSideBwe-WithOverhead/Enabled/"
"WebRTC-Audio-LegacyOverhead/Disabled/"
"WebRTC-Audio-Allocation/min:6kbps,max:64kbps/"); "WebRTC-Audio-Allocation/min:6kbps,max:64kbps/");
ConfigHelper helper(true, true); ConfigHelper helper(true, true);
auto send_stream = helper.CreateAudioSendStream(); auto send_stream = helper.CreateAudioSendStream();
const DataRate bitrate = DataRate::kbps(64) + kOverheadRate; EXPECT_CALL(*helper.channel_send(), CallEncoder(_)).Times(1);
send_stream->OnOverheadChanged(kOverheadPerPacket.bytes<size_t>());
const DataRate bitrate = DataRate::kbps(64) + kMaxOverheadRate;
EXPECT_CALL(*helper.channel_send(), EXPECT_CALL(*helper.channel_send(),
OnBitrateAllocation(Field( OnBitrateAllocation(Field(
&BitrateAllocationUpdate::target_bitrate, Eq(bitrate)))); &BitrateAllocationUpdate::target_bitrate, Eq(bitrate))));
BitrateAllocationUpdate update; BitrateAllocationUpdate update;
update.target_bitrate = DataRate::kbps(128); update.target_bitrate = DataRate::kbps(128);
send_stream->OnBitrateUpdated(update); helper.worker()->SendTask([&] { send_stream->OnBitrateUpdated(update); });
} }
TEST(AudioSendStreamTest, ProbingIntervalOnBitrateUpdated) { TEST(AudioSendStreamTest, ProbingIntervalOnBitrateUpdated) {
@ -652,7 +667,7 @@ TEST(AudioSendStreamTest, ProbingIntervalOnBitrateUpdated) {
update.packet_loss_ratio = 0; update.packet_loss_ratio = 0;
update.round_trip_time = TimeDelta::ms(50); update.round_trip_time = TimeDelta::ms(50);
update.bwe_period = TimeDelta::ms(5000); update.bwe_period = TimeDelta::ms(5000);
send_stream->OnBitrateUpdated(update); helper.worker()->SendTask([&] { send_stream->OnBitrateUpdated(update); });
} }
// Test that AudioSendStream doesn't recreate the encoder unnecessarily. // Test that AudioSendStream doesn't recreate the encoder unnecessarily.

View File

@ -879,4 +879,17 @@ ANAStats AudioEncoderOpusImpl::GetANAStats() const {
return ANAStats(); return ANAStats();
} }
absl::optional<std::pair<TimeDelta, TimeDelta> >
AudioEncoderOpusImpl::GetFrameLengthRange() const {
if (config_.supported_frame_lengths_ms.empty()) {
return absl::nullopt;
} else if (audio_network_adaptor_) {
return {{TimeDelta::ms(config_.supported_frame_lengths_ms.front()),
TimeDelta::ms(config_.supported_frame_lengths_ms.back())}};
} else {
return {{TimeDelta::ms(config_.frame_size_ms),
TimeDelta::ms(config_.frame_size_ms)}};
}
}
} // namespace webrtc } // namespace webrtc

View File

@ -115,6 +115,8 @@ class AudioEncoderOpusImpl final : public AudioEncoder {
void SetReceiverFrameLengthRange(int min_frame_length_ms, void SetReceiverFrameLengthRange(int min_frame_length_ms,
int max_frame_length_ms) override; int max_frame_length_ms) override;
ANAStats GetANAStats() const override; ANAStats GetANAStats() const override;
absl::optional<std::pair<TimeDelta, TimeDelta> > GetFrameLengthRange()
const override;
rtc::ArrayView<const int> supported_frame_lengths_ms() const { rtc::ArrayView<const int> supported_frame_lengths_ms() const {
return config_.supported_frame_lengths_ms; return config_.supported_frame_lengths_ms;
} }

View File

@ -34,6 +34,9 @@ class MockAudioEncoder : public AudioEncoder {
MOCK_CONST_METHOD0(Num10MsFramesInNextPacket, size_t()); MOCK_CONST_METHOD0(Num10MsFramesInNextPacket, size_t());
MOCK_CONST_METHOD0(Max10MsFramesInAPacket, size_t()); MOCK_CONST_METHOD0(Max10MsFramesInAPacket, size_t());
MOCK_CONST_METHOD0(GetTargetBitrate, int()); MOCK_CONST_METHOD0(GetTargetBitrate, int());
MOCK_CONST_METHOD0(GetFrameLengthRange,
absl::optional<std::pair<TimeDelta, TimeDelta>>());
MOCK_METHOD0(Reset, void()); MOCK_METHOD0(Reset, void());
MOCK_METHOD1(SetFec, bool(bool enable)); MOCK_METHOD1(SetFec, bool(bool enable));
MOCK_METHOD1(SetDtx, bool(bool enable)); MOCK_METHOD1(SetDtx, bool(bool enable));