Adding a delay line to NetEq's output
This change adds an optional delay to NetEq's output. Note, this is not
equivalent to increasing the jitter buffer with the same extra length.
Bug: b/156734419
Change-Id: I8b70b6b3bffcfd3da296ccf29853864baa03d6bb
Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/175110
Commit-Queue: Henrik Lundin <henrik.lundin@webrtc.org>
Reviewed-by: Karl Wiberg <kwiberg@webrtc.org>
Reviewed-by: Ivo Creusen <ivoc@webrtc.org>
Cr-Commit-Position: refs/heads/master@{#31343}
diff --git a/modules/audio_coding/neteq/neteq_unittest.cc b/modules/audio_coding/neteq/neteq_unittest.cc
index d78e2c6..f92ed1b 100644
--- a/modules/audio_coding/neteq/neteq_unittest.cc
+++ b/modules/audio_coding/neteq/neteq_unittest.cc
@@ -1102,5 +1102,156 @@
EXPECT_EQ(0, stats.preemptive_rate);
}
+namespace {
+// Helper classes and data types and functions for NetEqOutputDelayTest.
+
+class VectorAudioSink : public AudioSink {
+ public:
+ // Does not take ownership of the vector.
+ VectorAudioSink(std::vector<int16_t>* output_vector) : v_(output_vector) {}
+
+ virtual ~VectorAudioSink() = default;
+
+ bool WriteArray(const int16_t* audio, size_t num_samples) override {
+ v_->reserve(v_->size() + num_samples);
+ for (size_t i = 0; i < num_samples; ++i) {
+ v_->push_back(audio[i]);
+ }
+ return true;
+ }
+
+ private:
+ std::vector<int16_t>* const v_;
+};
+
+struct TestResult {
+ NetEqLifetimeStatistics lifetime_stats;
+ NetEqNetworkStatistics network_stats;
+ absl::optional<uint32_t> playout_timestamp;
+ int target_delay_ms;
+ int filtered_current_delay_ms;
+ int sample_rate_hz;
+};
+
+// This class is used as callback object to NetEqTest to collect some stats
+// at the end of the simulation.
+class SimEndStatsCollector : public NetEqSimulationEndedCallback {
+ public:
+ SimEndStatsCollector(TestResult& result) : result_(result) {}
+
+ void SimulationEnded(int64_t /*simulation_time_ms*/, NetEq* neteq) override {
+ result_.playout_timestamp = neteq->GetPlayoutTimestamp();
+ result_.target_delay_ms = neteq->TargetDelayMs();
+ result_.filtered_current_delay_ms = neteq->FilteredCurrentDelayMs();
+ result_.sample_rate_hz = neteq->last_output_sample_rate_hz();
+ }
+
+ private:
+ TestResult& result_;
+};
+
+TestResult DelayLineNetEqTest(int delay_ms,
+ std::vector<int16_t>* output_vector) {
+ NetEq::Config config;
+ config.for_test_no_time_stretching = true;
+ config.extra_output_delay_ms = delay_ms;
+ auto codecs = NetEqTest::StandardDecoderMap();
+ NetEqPacketSourceInput::RtpHeaderExtensionMap rtp_ext_map = {
+ {1, kRtpExtensionAudioLevel},
+ {3, kRtpExtensionAbsoluteSendTime},
+ {5, kRtpExtensionTransportSequenceNumber},
+ {7, kRtpExtensionVideoContentType},
+ {8, kRtpExtensionVideoTiming}};
+ std::unique_ptr<NetEqInput> input = std::make_unique<NetEqRtpDumpInput>(
+ webrtc::test::ResourcePath("audio_coding/neteq_universal_new", "rtp"),
+ rtp_ext_map, absl::nullopt /*No SSRC filter*/);
+ std::unique_ptr<TimeLimitedNetEqInput> input_time_limit(
+ new TimeLimitedNetEqInput(std::move(input), 10000));
+ std::unique_ptr<AudioSink> output =
+ std::make_unique<VectorAudioSink>(output_vector);
+
+ TestResult result;
+ SimEndStatsCollector stats_collector(result);
+ NetEqTest::Callbacks callbacks;
+ callbacks.simulation_ended_callback = &stats_collector;
+
+ NetEqTest test(config, CreateBuiltinAudioDecoderFactory(), codecs,
+ /*text_log=*/nullptr, /*neteq_factory=*/nullptr,
+ /*input=*/std::move(input_time_limit), std::move(output),
+ callbacks);
+ test.Run();
+ result.lifetime_stats = test.LifetimeStats();
+ result.network_stats = test.SimulationStats();
+ return result;
+}
+} // namespace
+
+// Tests the extra output delay functionality of NetEq.
+TEST(NetEqOutputDelayTest, RunTest) {
+ std::vector<int16_t> output;
+ const auto result_no_delay = DelayLineNetEqTest(0, &output);
+ std::vector<int16_t> output_delayed;
+ constexpr int kDelayMs = 100;
+ const auto result_delay = DelayLineNetEqTest(kDelayMs, &output_delayed);
+
+ // Verify that the loss concealment remains unchanged. The point of the delay
+ // is to not affect the jitter buffering behavior.
+ // First verify that there are concealments in the test.
+ EXPECT_GT(result_no_delay.lifetime_stats.concealed_samples, 0u);
+ // And that not all of the output is concealment.
+ EXPECT_GT(result_no_delay.lifetime_stats.total_samples_received,
+ result_no_delay.lifetime_stats.concealed_samples);
+ // Now verify that they remain unchanged by the delay.
+ EXPECT_EQ(result_no_delay.lifetime_stats.concealed_samples,
+ result_delay.lifetime_stats.concealed_samples);
+ // Accelerate and pre-emptive expand should also be unchanged.
+ EXPECT_EQ(result_no_delay.lifetime_stats.inserted_samples_for_deceleration,
+ result_delay.lifetime_stats.inserted_samples_for_deceleration);
+ EXPECT_EQ(result_no_delay.lifetime_stats.removed_samples_for_acceleration,
+ result_delay.lifetime_stats.removed_samples_for_acceleration);
+ // Verify that delay stats are increased with the delay chain.
+ EXPECT_EQ(
+ result_no_delay.lifetime_stats.jitter_buffer_delay_ms +
+ kDelayMs * result_no_delay.lifetime_stats.jitter_buffer_emitted_count,
+ result_delay.lifetime_stats.jitter_buffer_delay_ms);
+ EXPECT_EQ(
+ result_no_delay.lifetime_stats.jitter_buffer_target_delay_ms +
+ kDelayMs * result_no_delay.lifetime_stats.jitter_buffer_emitted_count,
+ result_delay.lifetime_stats.jitter_buffer_target_delay_ms);
+ EXPECT_EQ(result_no_delay.network_stats.current_buffer_size_ms + kDelayMs,
+ result_delay.network_stats.current_buffer_size_ms);
+ EXPECT_EQ(result_no_delay.network_stats.preferred_buffer_size_ms + kDelayMs,
+ result_delay.network_stats.preferred_buffer_size_ms);
+ EXPECT_EQ(result_no_delay.network_stats.mean_waiting_time_ms + kDelayMs,
+ result_delay.network_stats.mean_waiting_time_ms);
+ EXPECT_EQ(result_no_delay.network_stats.median_waiting_time_ms + kDelayMs,
+ result_delay.network_stats.median_waiting_time_ms);
+ EXPECT_EQ(result_no_delay.network_stats.min_waiting_time_ms + kDelayMs,
+ result_delay.network_stats.min_waiting_time_ms);
+ EXPECT_EQ(result_no_delay.network_stats.max_waiting_time_ms + kDelayMs,
+ result_delay.network_stats.max_waiting_time_ms);
+
+ ASSERT_TRUE(result_no_delay.playout_timestamp);
+ ASSERT_TRUE(result_delay.playout_timestamp);
+ EXPECT_EQ(*result_no_delay.playout_timestamp -
+ static_cast<uint32_t>(
+ kDelayMs *
+ rtc::CheckedDivExact(result_no_delay.sample_rate_hz, 1000)),
+ *result_delay.playout_timestamp);
+ EXPECT_EQ(result_no_delay.target_delay_ms + kDelayMs,
+ result_delay.target_delay_ms);
+ EXPECT_EQ(result_no_delay.filtered_current_delay_ms + kDelayMs,
+ result_delay.filtered_current_delay_ms);
+
+ // Verify expected delay in decoded signal. The test vector uses 8 kHz sample
+ // rate, so the delay will be 8 times the delay in ms.
+ constexpr size_t kExpectedDelaySamples = kDelayMs * 8;
+ for (size_t i = 0;
+ i < output.size() && i + kExpectedDelaySamples < output_delayed.size();
+ ++i) {
+ EXPECT_EQ(output[i], output_delayed[i + kExpectedDelaySamples]);
+ }
+}
+
} // namespace test
} // namespace webrtc