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:
parent
4baea5b07f
commit
096563e9d2
@ -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 {
|
||||
|
||||
@ -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;
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user