Improve adaptation to reordered packets in delay manager.
This is done by adding a reorder optimizer that estimates the probability of receiving reordered packets. The optimal delay is decided by balancing the cost of increasing the delay against the probability of missing a reordered packet, resulting in a loss. This balance is decided using the `ms_per_loss_percent` parameter. The usage and parameters can be controlled via field trial. Bug: webrtc:10178 Change-Id: Ic484df0412af35610e74b3a6070f2bac7a926a63 Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/231541 Reviewed-by: Ivo Creusen <ivoc@webrtc.org> Commit-Queue: Jakob Ivarsson <jakobi@webrtc.org> Cr-Commit-Position: refs/heads/main@{#34954}
This commit is contained in:
parent
1f38a38b6f
commit
58ed02eae3
@ -976,6 +976,8 @@ rtc_library("neteq") {
|
||||
"neteq/red_payload_splitter.h",
|
||||
"neteq/relative_arrival_delay_tracker.cc",
|
||||
"neteq/relative_arrival_delay_tracker.h",
|
||||
"neteq/reorder_optimizer.cc",
|
||||
"neteq/reorder_optimizer.h",
|
||||
"neteq/statistics_calculator.cc",
|
||||
"neteq/statistics_calculator.h",
|
||||
"neteq/sync_buffer.cc",
|
||||
@ -2016,6 +2018,7 @@ if (rtc_include_tests) {
|
||||
"neteq/random_vector_unittest.cc",
|
||||
"neteq/red_payload_splitter_unittest.cc",
|
||||
"neteq/relative_arrival_delay_tracker_unittest.cc",
|
||||
"neteq/reorder_optimizer_unittest.cc",
|
||||
"neteq/statistics_calculator_unittest.cc",
|
||||
"neteq/sync_buffer_unittest.cc",
|
||||
"neteq/time_stretch_unittest.cc",
|
||||
|
||||
@ -32,6 +32,16 @@ constexpr int kMinBaseMinimumDelayMs = 0;
|
||||
constexpr int kMaxBaseMinimumDelayMs = 10000;
|
||||
constexpr int kStartDelayMs = 80;
|
||||
|
||||
std::unique_ptr<ReorderOptimizer> MaybeCreateReorderOptimizer(
|
||||
const DelayManager::Config& config) {
|
||||
if (!config.use_reorder_optimizer) {
|
||||
return nullptr;
|
||||
}
|
||||
return std::make_unique<ReorderOptimizer>(
|
||||
(1 << 15) * config.reorder_forget_factor, config.ms_per_loss_percent,
|
||||
config.start_forget_weight);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
DelayManager::Config::Config() {
|
||||
@ -47,7 +57,10 @@ void DelayManager::Config::Log() {
|
||||
<< " start_forget_weight=" << start_forget_weight.value_or(0)
|
||||
<< " resample_interval_ms="
|
||||
<< resample_interval_ms.value_or(0)
|
||||
<< " max_history_ms=" << max_history_ms;
|
||||
<< " max_history_ms=" << max_history_ms
|
||||
<< " use_reorder_optimizer=" << use_reorder_optimizer
|
||||
<< " reorder_forget_factor=" << reorder_forget_factor
|
||||
<< " ms_per_loss_percent=" << ms_per_loss_percent;
|
||||
}
|
||||
|
||||
std::unique_ptr<StructParametersParser> DelayManager::Config::Parser() {
|
||||
@ -56,7 +69,10 @@ std::unique_ptr<StructParametersParser> DelayManager::Config::Parser() {
|
||||
"forget_factor", &forget_factor, //
|
||||
"start_forget_weight", &start_forget_weight, //
|
||||
"resample_interval_ms", &resample_interval_ms, //
|
||||
"max_history_ms", &max_history_ms);
|
||||
"max_history_ms", &max_history_ms, //
|
||||
"use_reorder_optimizer", &use_reorder_optimizer, //
|
||||
"reorder_forget_factor", &reorder_forget_factor, //
|
||||
"ms_per_loss_percent", &ms_per_loss_percent);
|
||||
}
|
||||
|
||||
// TODO(jakobi): remove legacy field trial.
|
||||
@ -90,6 +106,7 @@ DelayManager::DelayManager(const Config& config, const TickTimer* tick_timer)
|
||||
(1 << 15) * config.forget_factor,
|
||||
config.start_forget_weight,
|
||||
config.resample_interval_ms),
|
||||
reorder_optimizer_(MaybeCreateReorderOptimizer(config)),
|
||||
relative_arrival_delay_tracker_(tick_timer, config.max_history_ms),
|
||||
base_minimum_delay_ms_(config.base_minimum_delay_ms),
|
||||
effective_minimum_delay_ms_(config.base_minimum_delay_ms),
|
||||
@ -115,9 +132,18 @@ absl::optional<int> DelayManager::Update(uint32_t timestamp,
|
||||
return absl::nullopt;
|
||||
}
|
||||
|
||||
bool reordered =
|
||||
relative_arrival_delay_tracker_.newest_timestamp() != timestamp;
|
||||
if (!reorder_optimizer_ || !reordered) {
|
||||
underrun_optimizer_.Update(*relative_delay);
|
||||
}
|
||||
target_level_ms_ =
|
||||
underrun_optimizer_.GetOptimalDelayMs().value_or(kStartDelayMs);
|
||||
if (reorder_optimizer_) {
|
||||
reorder_optimizer_->Update(*relative_delay, reordered, target_level_ms_);
|
||||
target_level_ms_ = std::max(
|
||||
target_level_ms_, reorder_optimizer_->GetOptimalDelayMs().value_or(0));
|
||||
}
|
||||
target_level_ms_ = std::max(target_level_ms_, effective_minimum_delay_ms_);
|
||||
if (maximum_delay_ms_ > 0) {
|
||||
target_level_ms_ = std::min(target_level_ms_, maximum_delay_ms_);
|
||||
@ -148,6 +174,9 @@ void DelayManager::Reset() {
|
||||
underrun_optimizer_.Reset();
|
||||
relative_arrival_delay_tracker_.Reset();
|
||||
target_level_ms_ = kStartDelayMs;
|
||||
if (reorder_optimizer_) {
|
||||
reorder_optimizer_->Reset();
|
||||
}
|
||||
}
|
||||
|
||||
int DelayManager::TargetDelayMs() const {
|
||||
|
||||
@ -20,6 +20,7 @@
|
||||
#include "api/neteq/tick_timer.h"
|
||||
#include "modules/audio_coding/neteq/histogram.h"
|
||||
#include "modules/audio_coding/neteq/relative_arrival_delay_tracker.h"
|
||||
#include "modules/audio_coding/neteq/reorder_optimizer.h"
|
||||
#include "modules/audio_coding/neteq/underrun_optimizer.h"
|
||||
#include "rtc_base/constructor_magic.h"
|
||||
#include "rtc_base/experiments/struct_parameters_parser.h"
|
||||
@ -39,6 +40,10 @@ class DelayManager {
|
||||
absl::optional<int> resample_interval_ms;
|
||||
int max_history_ms = 2000;
|
||||
|
||||
bool use_reorder_optimizer = true;
|
||||
double reorder_forget_factor = 0.9993;
|
||||
int ms_per_loss_percent = 20;
|
||||
|
||||
// Options that are externally populated.
|
||||
int max_packets_in_buffer = 200;
|
||||
int base_minimum_delay_ms = 0;
|
||||
@ -104,6 +109,7 @@ class DelayManager {
|
||||
// TODO(jakobi): set maximum buffer delay instead of number of packets.
|
||||
const int max_packets_in_buffer_;
|
||||
UnderrunOptimizer underrun_optimizer_;
|
||||
std::unique_ptr<ReorderOptimizer> reorder_optimizer_;
|
||||
RelativeArrivalDelayTracker relative_arrival_delay_tracker_;
|
||||
|
||||
int base_minimum_delay_ms_;
|
||||
|
||||
@ -42,7 +42,7 @@ class Histogram {
|
||||
virtual int NumBuckets() const;
|
||||
|
||||
// Returns the probability for each bucket in Q30.
|
||||
std::vector<int> buckets() const { return buckets_; }
|
||||
const std::vector<int>& buckets() const { return buckets_; }
|
||||
|
||||
// Accessors only intended for testing purposes.
|
||||
int base_forget_factor_for_testing() const { return base_forget_factor_; }
|
||||
|
||||
@ -12,6 +12,8 @@
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
#include "modules/include/module_common_types_public.h"
|
||||
|
||||
namespace webrtc {
|
||||
|
||||
absl::optional<int> RelativeArrivalDelayTracker::Update(uint32_t timestamp,
|
||||
@ -23,6 +25,7 @@ absl::optional<int> RelativeArrivalDelayTracker::Update(uint32_t timestamp,
|
||||
// Restart relative delay esimation from this packet.
|
||||
delay_history_.clear();
|
||||
packet_iat_stopwatch_ = tick_timer_->GetNewStopwatch();
|
||||
newest_timestamp_ = timestamp;
|
||||
last_timestamp_ = timestamp;
|
||||
return absl::nullopt;
|
||||
}
|
||||
@ -37,6 +40,9 @@ absl::optional<int> RelativeArrivalDelayTracker::Update(uint32_t timestamp,
|
||||
|
||||
packet_iat_stopwatch_ = tick_timer_->GetNewStopwatch();
|
||||
last_timestamp_ = timestamp;
|
||||
if (IsNewerTimestamp(timestamp, *newest_timestamp_)) {
|
||||
newest_timestamp_ = timestamp;
|
||||
}
|
||||
|
||||
return relative_delay;
|
||||
}
|
||||
@ -44,6 +50,7 @@ absl::optional<int> RelativeArrivalDelayTracker::Update(uint32_t timestamp,
|
||||
void RelativeArrivalDelayTracker::Reset() {
|
||||
delay_history_.clear();
|
||||
packet_iat_stopwatch_ = tick_timer_->GetNewStopwatch();
|
||||
newest_timestamp_ = absl::nullopt;
|
||||
last_timestamp_ = absl::nullopt;
|
||||
}
|
||||
|
||||
|
||||
@ -28,6 +28,10 @@ class RelativeArrivalDelayTracker {
|
||||
|
||||
void Reset();
|
||||
|
||||
absl::optional<uint32_t> newest_timestamp() const {
|
||||
return newest_timestamp_;
|
||||
}
|
||||
|
||||
private:
|
||||
// Updates `delay_history_`.
|
||||
void UpdateDelayHistory(int iat_delay_ms,
|
||||
@ -46,8 +50,8 @@ class RelativeArrivalDelayTracker {
|
||||
};
|
||||
std::deque<PacketDelay> delay_history_;
|
||||
|
||||
absl::optional<uint32_t>
|
||||
last_timestamp_; // Timestamp for the last received packet.
|
||||
absl::optional<uint32_t> newest_timestamp_;
|
||||
absl::optional<uint32_t> last_timestamp_;
|
||||
|
||||
std::unique_ptr<TickTimer::Stopwatch>
|
||||
packet_iat_stopwatch_; // Time elapsed since last packet.
|
||||
|
||||
@ -49,14 +49,17 @@ TEST(RelativeArrivalDelayTrackerTest, ReorderedPackets) {
|
||||
|
||||
// Insert reordered packet.
|
||||
EXPECT_EQ(tracker.Update(kTs - 4 * kTsIncrement, kFs), 80);
|
||||
EXPECT_EQ(tracker.newest_timestamp(), kTs);
|
||||
|
||||
// Insert another reordered packet.
|
||||
EXPECT_EQ(tracker.Update(kTs - kTsIncrement, kFs), 20);
|
||||
EXPECT_EQ(tracker.newest_timestamp(), kTs);
|
||||
|
||||
// Insert the next packet in order and verify that the relative delay is
|
||||
// estimated based on the first inserted packet.
|
||||
tick_timer.Increment(4 * kFrameSizeMs / tick_timer.ms_per_tick());
|
||||
EXPECT_EQ(tracker.Update(kTs + kTsIncrement, kFs), 60);
|
||||
EXPECT_EQ(tracker.newest_timestamp(), kTs + kTsIncrement);
|
||||
}
|
||||
|
||||
TEST(RelativeArrivalDelayTrackerTest, MaxDelayHistory) {
|
||||
|
||||
75
modules/audio_coding/neteq/reorder_optimizer.cc
Normal file
75
modules/audio_coding/neteq/reorder_optimizer.cc
Normal file
@ -0,0 +1,75 @@
|
||||
/*
|
||||
* Copyright (c) 2021 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 "modules/audio_coding/neteq/reorder_optimizer.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <limits>
|
||||
#include <vector>
|
||||
|
||||
namespace webrtc {
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr int kDelayBuckets = 100;
|
||||
constexpr int kBucketSizeMs = 20;
|
||||
|
||||
} // namespace
|
||||
|
||||
ReorderOptimizer::ReorderOptimizer(int forget_factor,
|
||||
int ms_per_loss_percent,
|
||||
absl::optional<int> start_forget_weight)
|
||||
: histogram_(kDelayBuckets, forget_factor, start_forget_weight),
|
||||
ms_per_loss_percent_(ms_per_loss_percent) {}
|
||||
|
||||
void ReorderOptimizer::Update(int relative_delay_ms,
|
||||
bool reordered,
|
||||
int base_delay_ms) {
|
||||
const int index = reordered ? relative_delay_ms / kBucketSizeMs : 0;
|
||||
if (index < histogram_.NumBuckets()) {
|
||||
// Maximum delay to register is 2000 ms.
|
||||
histogram_.Add(index);
|
||||
}
|
||||
int bucket_index = MinimizeCostFunction(base_delay_ms);
|
||||
optimal_delay_ms_ = (1 + bucket_index) * kBucketSizeMs;
|
||||
}
|
||||
|
||||
void ReorderOptimizer::Reset() {
|
||||
histogram_.Reset();
|
||||
optimal_delay_ms_.reset();
|
||||
}
|
||||
|
||||
int ReorderOptimizer::MinimizeCostFunction(int base_delay_ms) const {
|
||||
const std::vector<int>& buckets = histogram_.buckets();
|
||||
|
||||
// Values are calculated in Q30.
|
||||
int64_t loss_probability = 1 << 30;
|
||||
int64_t min_cost = std::numeric_limits<int64_t>::max();
|
||||
int min_bucket = 0;
|
||||
for (int i = 0; i < static_cast<int>(buckets.size()); ++i) {
|
||||
loss_probability -= buckets[i];
|
||||
int64_t delay_ms =
|
||||
static_cast<int64_t>(std::max(0, i * kBucketSizeMs - base_delay_ms))
|
||||
<< 30;
|
||||
int64_t cost = delay_ms + 100 * ms_per_loss_percent_ * loss_probability;
|
||||
|
||||
if (cost < min_cost) {
|
||||
min_cost = cost;
|
||||
min_bucket = i;
|
||||
}
|
||||
if (loss_probability == 0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return min_bucket;
|
||||
}
|
||||
|
||||
} // namespace webrtc
|
||||
43
modules/audio_coding/neteq/reorder_optimizer.h
Normal file
43
modules/audio_coding/neteq/reorder_optimizer.h
Normal file
@ -0,0 +1,43 @@
|
||||
/*
|
||||
* Copyright (c) 2021 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 MODULES_AUDIO_CODING_NETEQ_REORDER_OPTIMIZER_H_
|
||||
#define MODULES_AUDIO_CODING_NETEQ_REORDER_OPTIMIZER_H_
|
||||
|
||||
#include "absl/types/optional.h"
|
||||
#include "modules/audio_coding/neteq/histogram.h"
|
||||
|
||||
namespace webrtc {
|
||||
|
||||
// Calculates an optimal delay to reduce the chance of missing reordered
|
||||
// packets. The delay/loss trade-off can be tune using the `ms_per_loss_percent`
|
||||
// parameter.
|
||||
class ReorderOptimizer {
|
||||
public:
|
||||
ReorderOptimizer(int forget_factor,
|
||||
int ms_per_loss_percent,
|
||||
absl::optional<int> start_forget_weight);
|
||||
|
||||
void Update(int relative_delay_ms, bool reordered, int base_delay_ms);
|
||||
|
||||
absl::optional<int> GetOptimalDelayMs() const { return optimal_delay_ms_; }
|
||||
|
||||
void Reset();
|
||||
|
||||
private:
|
||||
int MinimizeCostFunction(int base_delay_ms) const;
|
||||
|
||||
Histogram histogram_;
|
||||
const int ms_per_loss_percent_;
|
||||
absl::optional<int> optimal_delay_ms_;
|
||||
};
|
||||
|
||||
} // namespace webrtc
|
||||
#endif // MODULES_AUDIO_CODING_NETEQ_REORDER_OPTIMIZER_H_
|
||||
70
modules/audio_coding/neteq/reorder_optimizer_unittest.cc
Normal file
70
modules/audio_coding/neteq/reorder_optimizer_unittest.cc
Normal file
@ -0,0 +1,70 @@
|
||||
/*
|
||||
* Copyright (c) 2021 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 "modules/audio_coding/neteq/reorder_optimizer.h"
|
||||
|
||||
#include "test/gtest.h"
|
||||
|
||||
namespace webrtc {
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr int kForgetFactor = 32745; // 0.9993 in Q15.
|
||||
constexpr int kMsPerLossPercent = 20;
|
||||
constexpr int kStartForgetWeight = 1;
|
||||
|
||||
} // namespace
|
||||
|
||||
TEST(ReorderOptimizerTest, OnlyIncreaseDelayForReorderedPackets) {
|
||||
ReorderOptimizer reorder_optimizer(kForgetFactor, kMsPerLossPercent,
|
||||
kStartForgetWeight);
|
||||
EXPECT_FALSE(reorder_optimizer.GetOptimalDelayMs());
|
||||
|
||||
// Delay should not increase for in-order packets.
|
||||
reorder_optimizer.Update(60, /*reordered=*/false, 0);
|
||||
EXPECT_EQ(reorder_optimizer.GetOptimalDelayMs(), 20);
|
||||
|
||||
reorder_optimizer.Update(100, /*reordered=*/false, 0);
|
||||
EXPECT_EQ(reorder_optimizer.GetOptimalDelayMs(), 20);
|
||||
|
||||
reorder_optimizer.Update(80, /*reordered=*/true, 0);
|
||||
EXPECT_EQ(reorder_optimizer.GetOptimalDelayMs(), 100);
|
||||
}
|
||||
|
||||
TEST(ReorderOptimizerTest, AvoidIncreasingDelayWhenProbabilityIsLow) {
|
||||
ReorderOptimizer reorder_optimizer(kForgetFactor, kMsPerLossPercent,
|
||||
kStartForgetWeight);
|
||||
|
||||
reorder_optimizer.Update(40, /*reordered=*/true, 0);
|
||||
reorder_optimizer.Update(40, /*reordered=*/true, 0);
|
||||
reorder_optimizer.Update(40, /*reordered=*/true, 0);
|
||||
EXPECT_EQ(reorder_optimizer.GetOptimalDelayMs(), 60);
|
||||
|
||||
// The cost of the delay is too high relative the probability.
|
||||
reorder_optimizer.Update(600, /*reordered=*/true, 0);
|
||||
EXPECT_EQ(reorder_optimizer.GetOptimalDelayMs(), 60);
|
||||
}
|
||||
|
||||
TEST(ReorderOptimizerTest, BaseDelayIsSubtractedFromCost) {
|
||||
constexpr int kBaseDelayMs = 200;
|
||||
ReorderOptimizer reorder_optimizer(kForgetFactor, kMsPerLossPercent,
|
||||
kStartForgetWeight);
|
||||
|
||||
reorder_optimizer.Update(40, /*reordered=*/true, kBaseDelayMs);
|
||||
reorder_optimizer.Update(40, /*reordered=*/true, kBaseDelayMs);
|
||||
reorder_optimizer.Update(40, /*reordered=*/true, kBaseDelayMs);
|
||||
EXPECT_EQ(reorder_optimizer.GetOptimalDelayMs(), 60);
|
||||
|
||||
// The cost of the delay is too high relative the probability.
|
||||
reorder_optimizer.Update(600, /*reordered=*/true, kBaseDelayMs);
|
||||
EXPECT_EQ(reorder_optimizer.GetOptimalDelayMs(), 620);
|
||||
}
|
||||
|
||||
} // namespace webrtc
|
||||
Loading…
x
Reference in New Issue
Block a user