Combine concealment decision logic in NetEq.

Decisions should be the same (almost) regardless of PLC or CNG mode.

The new logic is submitted behind a flag to avoid changing the default
behavior. This results in messy code, but can be simplified once the
flag is removed.

Bug: webrtc:13322
Change-Id: I959d63e069ad7970b75205c4c4173d774b0e4cac
Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/298625
Commit-Queue: Jakob Ivarsson‎ <jakobi@webrtc.org>
Reviewed-by: Henrik Lundin <henrik.lundin@webrtc.org>
Cr-Commit-Position: refs/heads/main@{#39657}
This commit is contained in:
Jakob Ivarsson 2023-03-23 14:39:07 +01:00 committed by WebRTC LUCI CQ
parent 4baea5b07f
commit 096563e9d2
2 changed files with 67 additions and 41 deletions

View File

@ -38,6 +38,7 @@ constexpr int kMaxWaitForPacketMs = 100;
// The granularity of delay adjustments (accelerate/preemptive expand) is 15ms,
// but round up since the clock has a granularity of 10ms.
constexpr int kDelayAdjustmentGranularityMs = 20;
constexpr int kReinitAfterExpandsMs = 1000;
std::unique_ptr<DelayManager> CreateDelayManager(
const NetEqController::Config& neteq_config) {
@ -68,10 +69,10 @@ bool IsExpand(NetEq::Mode mode) {
DecisionLogic::Config::Config() {
StructParametersParser::Create(
"enable_stable_playout_delay", &enable_stable_playout_delay, //
"reinit_after_expand_ms", &reinit_after_expand_ms, //
"packet_history_size_ms", &packet_history_size_ms, //
"cng_timeout_ms", &cng_timeout_ms, //
"enable_stable_playout_delay", &enable_stable_playout_delay, //
"combine_concealment_decision", &combine_concealment_decision, //
"packet_history_size_ms", &packet_history_size_ms, //
"cng_timeout_ms", &cng_timeout_ms, //
"deceleration_target_level_offset_ms",
&deceleration_target_level_offset_ms)
->Parse(webrtc::field_trial::FindFullName(
@ -79,7 +80,8 @@ DecisionLogic::Config::Config() {
RTC_LOG(LS_INFO) << "NetEq decision logic config:"
<< " enable_stable_playout_delay="
<< enable_stable_playout_delay
<< " reinit_after_expand_ms=" << reinit_after_expand_ms
<< " combine_concealment_decision="
<< combine_concealment_decision
<< " packet_history_size_ms=" << packet_history_size_ms
<< " cng_timeout_ms=" << cng_timeout_ms.value_or(-1)
<< " deceleration_target_level_offset_ms="
@ -137,7 +139,8 @@ NetEq::Operation DecisionLogic::GetDecision(const NetEqStatus& status,
if (prev_time_scale_) {
timescale_countdown_ = tick_timer_->GetNewCountdown(kMinTimescaleInterval);
}
if (!IsCng(status.last_mode)) {
if (!IsCng(status.last_mode) &&
!(config_.combine_concealment_decision && IsExpand(status.last_mode))) {
FilterBufferLevel(status.packet_buffer_info.span_samples);
}
@ -162,29 +165,15 @@ NetEq::Operation DecisionLogic::GetDecision(const NetEqStatus& status,
// If the expand period was very long, reset NetEQ since it is likely that the
// sender was restarted.
if (IsExpand(status.last_mode) &&
if (!config_.combine_concealment_decision && IsExpand(status.last_mode) &&
status.generated_noise_samples >
static_cast<size_t>(config_.reinit_after_expand_ms *
sample_rate_khz_)) {
static_cast<size_t>(kReinitAfterExpandsMs * sample_rate_khz_)) {
*reset_decoder = true;
return NetEq::Operation::kNormal;
}
// Make sure we don't restart audio too soon after an expansion to avoid
// running out of data right away again. We should only wait if there are no
// DTX or CNG packets in the buffer (otherwise we should just play out what we
// have, since we cannot know the exact duration of DTX or CNG packets), and
// if the mute factor is low enough (otherwise the expansion was short enough
// to not be noticable).
// Note that the MuteFactor is in Q14, so a value of 16384 corresponds to 1.
const int target_level_samples = TargetLevelMs() * sample_rate_khz_;
if (!config_.enable_stable_playout_delay && IsExpand(status.last_mode) &&
status.expand_mutefactor < 16384 / 2 &&
status.packet_buffer_info.span_samples <
static_cast<size_t>(target_level_samples * kPostponeDecodingLevel /
100) &&
!status.packet_buffer_info.dtx_or_cng) {
return NetEq::Operation::kExpand;
if (PostponeDecode(status)) {
return NoPacket(status);
}
const uint32_t five_seconds_samples =
@ -365,23 +354,31 @@ NetEq::Operation DecisionLogic::ExpectedPacketAvailable(
NetEq::Operation DecisionLogic::FuturePacketAvailable(
NetEqController::NetEqStatus status) {
// Required packet is not available, but a future packet is.
// Check if we should continue with an ongoing expand because the new packet
// is too far into the future.
if (IsExpand(status.last_mode) && ShouldContinueExpand(status)) {
return NoPacket(status);
}
if (IsCng(status.last_mode)) {
int playout_delay_ms = GetNextPacketDelayMs(status);
const bool above_target_delay = playout_delay_ms > HighThresholdCng();
const bool below_target_delay = playout_delay_ms < LowThresholdCng();
if ((PacketTooEarly(status) && !above_target_delay) || below_target_delay) {
// Check if we should continue with an ongoing concealment because the new
// packet is too far into the future.
if (config_.combine_concealment_decision || IsCng(status.last_mode)) {
const int buffer_delay_ms =
status.packet_buffer_info.span_samples / sample_rate_khz_;
const bool above_target_delay = buffer_delay_ms > HighThresholdCng();
const bool below_target_delay = buffer_delay_ms < LowThresholdCng();
if ((PacketTooEarly(status) && !above_target_delay) ||
(below_target_delay && !config_.combine_concealment_decision)) {
return NoPacket(status);
}
uint32_t timestamp_leap =
status.next_packet->timestamp - status.target_timestamp;
time_stretched_cn_samples_ =
timestamp_leap - status.generated_noise_samples;
if (config_.combine_concealment_decision) {
if (timestamp_leap != status.generated_noise_samples) {
// The delay was adjusted, reinitialize the buffer level filter.
buffer_level_filter_->SetFilteredBufferLevel(
status.packet_buffer_info.span_samples);
}
} else {
time_stretched_cn_samples_ =
timestamp_leap - status.generated_noise_samples;
}
} else if (IsExpand(status.last_mode) && ShouldContinueExpand(status)) {
return NoPacket(status);
}
// Time to play the next packet.
@ -403,13 +400,38 @@ bool DecisionLogic::UnderTargetLevel() const {
TargetLevelMs() * sample_rate_khz_;
}
bool DecisionLogic::PostponeDecode(NetEqController::NetEqStatus status) const {
// Make sure we don't restart audio too soon after CNG or expand to avoid
// running out of data right away again.
const size_t min_buffer_level_samples =
TargetLevelMs() * sample_rate_khz_ * kPostponeDecodingLevel / 100;
if (status.packet_buffer_info.span_samples >= min_buffer_level_samples) {
return false;
}
// Don't postpone decoding if there is a future DTX packet in the packet
// buffer.
if (status.packet_buffer_info.dtx_or_cng) {
return false;
}
// Continue CNG until the buffer is at least at the minimum level.
if (config_.combine_concealment_decision && IsCng(status.last_mode)) {
return true;
}
// Only continue expand if the mute factor is low enough (otherwise the
// expansion was short enough to not be noticable). Note that the MuteFactor
// is in Q14, so a value of 16384 corresponds to 1.
if (IsExpand(status.last_mode) && status.expand_mutefactor < 16384 / 2) {
return true;
}
return false;
}
bool DecisionLogic::ReinitAfterExpands(
NetEqController::NetEqStatus status) const {
const uint32_t timestamp_leap =
status.next_packet->timestamp - status.target_timestamp;
return timestamp_leap >=
static_cast<uint32_t>(config_.reinit_after_expand_ms *
sample_rate_khz_);
static_cast<uint32_t>(kReinitAfterExpandsMs * sample_rate_khz_);
}
bool DecisionLogic::PacketTooEarly(NetEqController::NetEqStatus status) const {

View File

@ -134,8 +134,12 @@ class DecisionLogic : public NetEqController {
// Checks if the current (filtered) buffer level is under the target level.
bool UnderTargetLevel() const;
// Checks if an ongoing concealment should be continued due to low buffer
// level, even though the next packet is available.
bool PostponeDecode(NetEqController::NetEqStatus status) const;
// Checks if the timestamp leap is so long into the future that a reset due
// to exceeding `reinit_after_expand_ms` will be done.
// to exceeding the expand limit will be done.
bool ReinitAfterExpands(NetEqController::NetEqStatus status) const;
// Checks if we still have not done enough expands to cover the distance from
@ -157,7 +161,7 @@ class DecisionLogic : public NetEqController {
Config();
bool enable_stable_playout_delay = false;
int reinit_after_expand_ms = 1000;
bool combine_concealment_decision = false;
int deceleration_target_level_offset_ms = 85;
int packet_history_size_ms = 2000;
absl::optional<int> cng_timeout_ms;