Add a "Smart flushing" feature to NetEq.

Instead of flushing all packets, it makes sense to flush down to the target level instead. This CL also initiates a flush when the packet buffer is a multiple of the target level, instead of waiting until it is completely full.

Bug: webrtc:12201
Change-Id: I8775147624536824eb88752f6e8ffe57ec6199cb
Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/193941
Commit-Queue: Ivo Creusen <ivoc@webrtc.org>
Reviewed-by: Jakob Ivarsson <jakobi@webrtc.org>
Cr-Commit-Position: refs/heads/master@{#32701}
This commit is contained in:
Ivo Creusen
2020-11-25 11:32:40 +01:00
committed by Commit Bot
parent 54b91412de
commit 7b463c5f67
12 changed files with 403 additions and 70 deletions

View File

@ -19,6 +19,7 @@
#include "modules/audio_coding/neteq/mock/mock_decoder_database.h"
#include "modules/audio_coding/neteq/mock/mock_statistics_calculator.h"
#include "modules/audio_coding/neteq/packet.h"
#include "test/field_trial.h"
#include "test/gmock.h"
#include "test/gtest.h"
@ -117,10 +118,16 @@ TEST(PacketBuffer, InsertPacket) {
PacketBuffer buffer(10, &tick_timer); // 10 packets.
PacketGenerator gen(17u, 4711u, 0, 10);
StrictMock<MockStatisticsCalculator> mock_stats;
MockDecoderDatabase decoder_database;
const int payload_len = 100;
const Packet packet = gen.NextPacket(payload_len, nullptr);
EXPECT_EQ(0, buffer.InsertPacket(packet.Clone(), &mock_stats));
EXPECT_EQ(0, buffer.InsertPacket(/*packet=*/packet.Clone(),
/*stats=*/&mock_stats,
/*last_decoded_length=*/payload_len,
/*sample_rate=*/10000,
/*target_level_ms=*/60,
/*decoder_database=*/decoder_database));
uint32_t next_ts;
EXPECT_EQ(PacketBuffer::kOK, buffer.NextTimestamp(&next_ts));
EXPECT_EQ(4711u, next_ts);
@ -128,6 +135,7 @@ TEST(PacketBuffer, InsertPacket) {
EXPECT_EQ(1u, buffer.NumPacketsInBuffer());
const Packet* next_packet = buffer.PeekNextPacket();
EXPECT_EQ(packet, *next_packet); // Compare contents.
EXPECT_CALL(decoder_database, Die()); // Called when object is deleted.
// Do not explicitly flush buffer or delete packet to test that it is deleted
// with the buffer. (Tested with Valgrind or similar tool.)
@ -140,20 +148,28 @@ TEST(PacketBuffer, FlushBuffer) {
PacketGenerator gen(0, 0, 0, 10);
const int payload_len = 10;
StrictMock<MockStatisticsCalculator> mock_stats;
MockDecoderDatabase decoder_database;
// Insert 10 small packets; should be ok.
for (int i = 0; i < 10; ++i) {
EXPECT_EQ(
PacketBuffer::kOK,
buffer.InsertPacket(gen.NextPacket(payload_len, nullptr), &mock_stats));
buffer.InsertPacket(/*packet=*/gen.NextPacket(payload_len, nullptr),
/*stats=*/&mock_stats,
/*last_decoded_length=*/payload_len,
/*sample_rate=*/1000,
/*target_level_ms=*/60,
/*decoder_database=*/decoder_database));
}
EXPECT_EQ(10u, buffer.NumPacketsInBuffer());
EXPECT_FALSE(buffer.Empty());
buffer.Flush();
EXPECT_CALL(mock_stats, PacketsDiscarded(1)).Times(10);
buffer.Flush(&mock_stats);
// Buffer should delete the payloads itself.
EXPECT_EQ(0u, buffer.NumPacketsInBuffer());
EXPECT_TRUE(buffer.Empty());
EXPECT_CALL(decoder_database, Die()); // Called when object is deleted.
}
// Test to fill the buffer over the limits, and verify that it flushes.
@ -162,6 +178,7 @@ TEST(PacketBuffer, OverfillBuffer) {
PacketBuffer buffer(10, &tick_timer); // 10 packets.
PacketGenerator gen(0, 0, 0, 10);
StrictMock<MockStatisticsCalculator> mock_stats;
MockDecoderDatabase decoder_database;
// Insert 10 small packets; should be ok.
const int payload_len = 10;
@ -169,7 +186,99 @@ TEST(PacketBuffer, OverfillBuffer) {
for (i = 0; i < 10; ++i) {
EXPECT_EQ(
PacketBuffer::kOK,
buffer.InsertPacket(gen.NextPacket(payload_len, nullptr), &mock_stats));
buffer.InsertPacket(/*packet=*/gen.NextPacket(payload_len, nullptr),
/*stats=*/&mock_stats,
/*last_decoded_length=*/payload_len,
/*sample_rate=*/1000,
/*target_level_ms=*/60,
/*decoder_database=*/decoder_database));
}
EXPECT_EQ(10u, buffer.NumPacketsInBuffer());
uint32_t next_ts;
EXPECT_EQ(PacketBuffer::kOK, buffer.NextTimestamp(&next_ts));
EXPECT_EQ(0u, next_ts); // Expect first inserted packet to be first in line.
EXPECT_CALL(mock_stats, PacketsDiscarded(1)).Times(10);
const Packet packet = gen.NextPacket(payload_len, nullptr);
// Insert 11th packet; should flush the buffer and insert it after flushing.
EXPECT_EQ(PacketBuffer::kFlushed,
buffer.InsertPacket(/*packet=*/packet.Clone(),
/*stats=*/&mock_stats,
/*last_decoded_length=*/payload_len,
/*sample_rate=*/1000,
/*target_level_ms=*/60,
/*decoder_database=*/decoder_database));
EXPECT_EQ(1u, buffer.NumPacketsInBuffer());
EXPECT_EQ(PacketBuffer::kOK, buffer.NextTimestamp(&next_ts));
// Expect last inserted packet to be first in line.
EXPECT_EQ(packet.timestamp, next_ts);
EXPECT_CALL(decoder_database, Die()); // Called when object is deleted.
}
// Test a partial buffer flush.
TEST(PacketBuffer, PartialFlush) {
// Use a field trial to configure smart flushing.
test::ScopedFieldTrials field_trials(
"WebRTC-Audio-NetEqSmartFlushing/enabled:true,"
"target_level_threshold_ms:0,target_level_multiplier:2/");
TickTimer tick_timer;
PacketBuffer buffer(10, &tick_timer); // 10 packets.
PacketGenerator gen(0, 0, 0, 10);
const int payload_len = 10;
StrictMock<MockStatisticsCalculator> mock_stats;
MockDecoderDatabase decoder_database;
// Insert 10 small packets; should be ok.
for (int i = 0; i < 10; ++i) {
EXPECT_EQ(
PacketBuffer::kOK,
buffer.InsertPacket(/*packet=*/gen.NextPacket(payload_len, nullptr),
/*stats=*/&mock_stats,
/*last_decoded_length=*/payload_len,
/*sample_rate=*/1000,
/*target_level_ms=*/100,
/*decoder_database=*/decoder_database));
}
EXPECT_EQ(10u, buffer.NumPacketsInBuffer());
EXPECT_FALSE(buffer.Empty());
EXPECT_CALL(mock_stats, PacketsDiscarded(1)).Times(7);
buffer.PartialFlush(/*target_level_ms=*/30,
/*sample_rate=*/1000,
/*last_decoded_length=*/payload_len,
/*stats=*/&mock_stats);
// There should still be some packets left in the buffer.
EXPECT_EQ(3u, buffer.NumPacketsInBuffer());
EXPECT_FALSE(buffer.Empty());
EXPECT_CALL(decoder_database, Die()); // Called when object is deleted.
}
// Test to fill the buffer over the limits, and verify that the smart flush
// functionality works as expected.
TEST(PacketBuffer, SmartFlushOverfillBuffer) {
// Use a field trial to configure smart flushing.
test::ScopedFieldTrials field_trials(
"WebRTC-Audio-NetEqSmartFlushing/enabled:true,"
"target_level_threshold_ms:0,target_level_multiplier:2/");
TickTimer tick_timer;
PacketBuffer buffer(10, &tick_timer); // 10 packets.
PacketGenerator gen(0, 0, 0, 10);
StrictMock<MockStatisticsCalculator> mock_stats;
MockDecoderDatabase decoder_database;
// Insert 10 small packets; should be ok.
const int payload_len = 10;
int i;
for (i = 0; i < 10; ++i) {
EXPECT_EQ(
PacketBuffer::kOK,
buffer.InsertPacket(/*packet=*/gen.NextPacket(payload_len, nullptr),
/*stats=*/&mock_stats,
/*last_decoded_length=*/payload_len,
/*sample_rate=*/1000,
/*target_level_ms=*/100,
/*decoder_database=*/decoder_database));
}
EXPECT_EQ(10u, buffer.NumPacketsInBuffer());
uint32_t next_ts;
@ -177,16 +286,18 @@ TEST(PacketBuffer, OverfillBuffer) {
EXPECT_EQ(0u, next_ts); // Expect first inserted packet to be first in line.
const Packet packet = gen.NextPacket(payload_len, nullptr);
// Insert 11th packet; should flush the buffer and insert it after flushing.
EXPECT_EQ(PacketBuffer::kFlushed,
buffer.InsertPacket(packet.Clone(), &mock_stats));
EXPECT_EQ(1u, buffer.NumPacketsInBuffer());
EXPECT_EQ(PacketBuffer::kOK, buffer.NextTimestamp(&next_ts));
// Expect last inserted packet to be first in line.
EXPECT_EQ(packet.timestamp, next_ts);
// Flush buffer to delete all packets.
buffer.Flush();
EXPECT_CALL(mock_stats, PacketsDiscarded(1)).Times(6);
// Insert 11th packet; should cause a partial flush and insert the packet
// after flushing.
EXPECT_EQ(PacketBuffer::kPartialFlush,
buffer.InsertPacket(/*packet=*/packet.Clone(),
/*stats=*/&mock_stats,
/*last_decoded_length=*/payload_len,
/*sample_rate=*/1000,
/*target_level_ms=*/40,
/*decoder_database=*/decoder_database));
EXPECT_EQ(5u, buffer.NumPacketsInBuffer());
EXPECT_CALL(decoder_database, Die()); // Called when object is deleted.
}
// Test inserting a list of packets.
@ -213,16 +324,21 @@ TEST(PacketBuffer, InsertPacketList) {
absl::optional<uint8_t> current_pt;
absl::optional<uint8_t> current_cng_pt;
EXPECT_EQ(PacketBuffer::kOK,
buffer.InsertPacketList(&list, decoder_database, &current_pt,
&current_cng_pt, &mock_stats));
EXPECT_EQ(
PacketBuffer::kOK,
buffer.InsertPacketList(/*packet_list=*/&list,
/*decoder_database=*/decoder_database,
/*current_rtp_payload_type=*/&current_pt,
/*current_cng_rtp_payload_type=*/&current_cng_pt,
/*stats=*/&mock_stats,
/*last_decoded_length=*/payload_len,
/*sample_rate=*/1000,
/*target_level_ms=*/30));
EXPECT_TRUE(list.empty()); // The PacketBuffer should have depleted the list.
EXPECT_EQ(10u, buffer.NumPacketsInBuffer());
EXPECT_EQ(0, current_pt); // Current payload type changed to 0.
EXPECT_EQ(absl::nullopt, current_cng_pt); // CNG payload type not changed.
buffer.Flush(); // Clean up.
EXPECT_CALL(decoder_database, Die()); // Called when object is deleted.
}
@ -262,16 +378,22 @@ TEST(PacketBuffer, InsertPacketListChangePayloadType) {
absl::optional<uint8_t> current_pt;
absl::optional<uint8_t> current_cng_pt;
EXPECT_EQ(PacketBuffer::kFlushed,
buffer.InsertPacketList(&list, decoder_database, &current_pt,
&current_cng_pt, &mock_stats));
EXPECT_CALL(mock_stats, PacketsDiscarded(1)).Times(10);
EXPECT_EQ(
PacketBuffer::kFlushed,
buffer.InsertPacketList(/*packet_list=*/&list,
/*decoder_database=*/decoder_database,
/*current_rtp_payload_type=*/&current_pt,
/*current_cng_rtp_payload_type=*/&current_cng_pt,
/*stats=*/&mock_stats,
/*last_decoded_length=*/payload_len,
/*sample_rate=*/1000,
/*target_level_ms=*/30));
EXPECT_TRUE(list.empty()); // The PacketBuffer should have depleted the list.
EXPECT_EQ(1u, buffer.NumPacketsInBuffer()); // Only the last packet.
EXPECT_EQ(1, current_pt); // Current payload type changed to 1.
EXPECT_EQ(absl::nullopt, current_cng_pt); // CNG payload type not changed.
buffer.Flush(); // Clean up.
EXPECT_CALL(decoder_database, Die()); // Called when object is deleted.
}
@ -293,6 +415,7 @@ TEST(PacketBuffer, ExtractOrderRedundancy) {
{0x0005, 0x0000001E, 0, true, -1}, {0x0005, 0x00000014, 1, false, -1},
{0x0006, 0x00000028, 0, true, 8}, {0x0006, 0x0000001E, 1, false, -1},
};
MockDecoderDatabase decoder_database;
const size_t kExpectPacketsInBuffer = 9;
@ -321,7 +444,12 @@ TEST(PacketBuffer, ExtractOrderRedundancy) {
}
EXPECT_CALL(check, Call(i));
EXPECT_EQ(PacketBuffer::kOK,
buffer.InsertPacket(packet.Clone(), &mock_stats));
buffer.InsertPacket(/*packet=*/packet.Clone(),
/*stats=*/&mock_stats,
/*last_decoded_length=*/kPayloadLength,
/*sample_rate=*/1000,
/*target_level_ms=*/60,
/*decoder_database=*/decoder_database));
if (packet_facts[i].extract_order >= 0) {
expect_order[packet_facts[i].extract_order] = std::move(packet);
}
@ -335,6 +463,7 @@ TEST(PacketBuffer, ExtractOrderRedundancy) {
EXPECT_EQ(packet, expect_order[i]); // Compare contents.
}
EXPECT_TRUE(buffer.Empty());
EXPECT_CALL(decoder_database, Die()); // Called when object is deleted.
}
TEST(PacketBuffer, DiscardPackets) {
@ -347,11 +476,17 @@ TEST(PacketBuffer, DiscardPackets) {
PacketList list;
const int payload_len = 10;
StrictMock<MockStatisticsCalculator> mock_stats;
MockDecoderDatabase decoder_database;
constexpr int kTotalPackets = 10;
// Insert 10 small packets.
for (int i = 0; i < kTotalPackets; ++i) {
buffer.InsertPacket(gen.NextPacket(payload_len, nullptr), &mock_stats);
buffer.InsertPacket(/*packet=*/gen.NextPacket(payload_len, nullptr),
/*stats=*/&mock_stats,
/*last_decoded_length=*/payload_len,
/*sample_rate=*/1000,
/*target_level_ms=*/60,
/*decoder_database=*/decoder_database);
}
EXPECT_EQ(10u, buffer.NumPacketsInBuffer());
@ -399,6 +534,7 @@ TEST(PacketBuffer, DiscardPackets) {
&mock_stats);
EXPECT_TRUE(buffer.Empty());
EXPECT_CALL(decoder_database, Die()); // Called when object is deleted.
}
TEST(PacketBuffer, Reordering) {
@ -434,9 +570,16 @@ TEST(PacketBuffer, Reordering) {
StrictMock<MockStatisticsCalculator> mock_stats;
EXPECT_EQ(PacketBuffer::kOK,
buffer.InsertPacketList(&list, decoder_database, &current_pt,
&current_cng_pt, &mock_stats));
EXPECT_EQ(
PacketBuffer::kOK,
buffer.InsertPacketList(/*packet_list=*/&list,
/*decoder_database=*/decoder_database,
/*current_rtp_payload_type=*/&current_pt,
/*current_cng_rtp_payload_type=*/&current_cng_pt,
/*stats=*/&mock_stats,
/*last_decoded_length=*/payload_len,
/*sample_rate=*/1000,
/*target_level_ms=*/30));
EXPECT_EQ(10u, buffer.NumPacketsInBuffer());
// Extract them and make sure that come out in the right order.
@ -483,9 +626,16 @@ TEST(PacketBuffer, CngFirstThenSpeechWithNewSampleRate) {
StrictMock<MockStatisticsCalculator> mock_stats;
EXPECT_EQ(PacketBuffer::kOK,
buffer.InsertPacketList(&list, decoder_database, &current_pt,
&current_cng_pt, &mock_stats));
EXPECT_EQ(
PacketBuffer::kOK,
buffer.InsertPacketList(/*packet_list=*/&list,
/*decoder_database=*/decoder_database,
/*current_rtp_payload_type=*/&current_pt,
/*current_cng_rtp_payload_type=*/&current_cng_pt,
/*stats=*/&mock_stats,
/*last_decoded_length=*/kPayloadLen,
/*sample_rate=*/1000,
/*target_level_ms=*/30));
EXPECT_TRUE(list.empty());
EXPECT_EQ(1u, buffer.NumPacketsInBuffer());
ASSERT_TRUE(buffer.PeekNextPacket());
@ -501,9 +651,17 @@ TEST(PacketBuffer, CngFirstThenSpeechWithNewSampleRate) {
}
// Expect the buffer to flush out the CNG packet, since it does not match the
// new speech sample rate.
EXPECT_EQ(PacketBuffer::kFlushed,
buffer.InsertPacketList(&list, decoder_database, &current_pt,
&current_cng_pt, &mock_stats));
EXPECT_CALL(mock_stats, PacketsDiscarded(1));
EXPECT_EQ(
PacketBuffer::kFlushed,
buffer.InsertPacketList(/*packet_list=*/&list,
/*decoder_database=*/decoder_database,
/*current_rtp_payload_type=*/&current_pt,
/*current_cng_rtp_payload_type=*/&current_cng_pt,
/*stats=*/&mock_stats,
/*last_decoded_length=*/kPayloadLen,
/*sample_rate=*/1000,
/*target_level_ms=*/30));
EXPECT_TRUE(list.empty());
EXPECT_EQ(1u, buffer.NumPacketsInBuffer());
ASSERT_TRUE(buffer.PeekNextPacket());
@ -512,7 +670,6 @@ TEST(PacketBuffer, CngFirstThenSpeechWithNewSampleRate) {
EXPECT_EQ(kSpeechPt, current_pt); // Current payload type set.
EXPECT_EQ(absl::nullopt, current_cng_pt); // CNG payload type reset.
buffer.Flush(); // Clean up.
EXPECT_CALL(decoder_database, Die()); // Called when object is deleted.
}
@ -524,13 +681,19 @@ TEST(PacketBuffer, Failures) {
PacketGenerator gen(start_seq_no, start_ts, 0, ts_increment);
TickTimer tick_timer;
StrictMock<MockStatisticsCalculator> mock_stats;
MockDecoderDatabase decoder_database;
PacketBuffer* buffer = new PacketBuffer(100, &tick_timer); // 100 packets.
{
Packet packet = gen.NextPacket(payload_len, nullptr);
packet.payload.Clear();
EXPECT_EQ(PacketBuffer::kInvalidPacket,
buffer->InsertPacket(std::move(packet), &mock_stats));
buffer->InsertPacket(/*packet=*/std::move(packet),
/*stats=*/&mock_stats,
/*last_decoded_length=*/payload_len,
/*sample_rate=*/1000,
/*target_level_ms=*/60,
/*decoder_database=*/decoder_database));
}
// Buffer should still be empty. Test all empty-checks.
uint32_t temp_ts;
@ -548,7 +711,12 @@ TEST(PacketBuffer, Failures) {
// Insert one packet to make the buffer non-empty.
EXPECT_EQ(
PacketBuffer::kOK,
buffer->InsertPacket(gen.NextPacket(payload_len, nullptr), &mock_stats));
buffer->InsertPacket(/*packet=*/gen.NextPacket(payload_len, nullptr),
/*stats=*/&mock_stats,
/*last_decoded_length=*/payload_len,
/*sample_rate=*/1000,
/*target_level_ms=*/60,
/*decoder_database=*/decoder_database));
EXPECT_EQ(PacketBuffer::kInvalidPointer, buffer->NextTimestamp(NULL));
EXPECT_EQ(PacketBuffer::kInvalidPointer,
buffer->NextHigherTimestamp(0, NULL));
@ -566,7 +734,6 @@ TEST(PacketBuffer, Failures) {
list.push_back(std::move(packet));
}
list.push_back(gen.NextPacket(payload_len, nullptr)); // Valid packet.
MockDecoderDatabase decoder_database;
auto factory = CreateBuiltinAudioDecoderFactory();
const DecoderDatabase::DecoderInfo info(SdpAudioFormat("pcmu", 8000, 1),
absl::nullopt, factory);
@ -574,9 +741,16 @@ TEST(PacketBuffer, Failures) {
.WillRepeatedly(Return(&info));
absl::optional<uint8_t> current_pt;
absl::optional<uint8_t> current_cng_pt;
EXPECT_EQ(PacketBuffer::kInvalidPacket,
buffer->InsertPacketList(&list, decoder_database, &current_pt,
&current_cng_pt, &mock_stats));
EXPECT_EQ(
PacketBuffer::kInvalidPacket,
buffer->InsertPacketList(/*packet_list=*/&list,
/*decoder_database=*/decoder_database,
/*current_rtp_payload_type=*/&current_pt,
/*current_cng_rtp_payload_type=*/&current_cng_pt,
/*stats=*/&mock_stats,
/*last_decoded_length=*/payload_len,
/*sample_rate=*/1000,
/*target_level_ms=*/30));
EXPECT_TRUE(list.empty()); // The PacketBuffer should have depleted the list.
EXPECT_EQ(1u, buffer->NumPacketsInBuffer());
delete buffer;
@ -702,6 +876,7 @@ TEST(PacketBuffer, GetSpanSamples) {
PacketBuffer buffer(3, &tick_timer);
PacketGenerator gen(0, kStartTimeStamp, 0, kFrameSizeSamples);
StrictMock<MockStatisticsCalculator> mock_stats;
MockDecoderDatabase decoder_database;
Packet packet_1 = gen.NextPacket(kPayloadSizeBytes, nullptr);
@ -716,7 +891,12 @@ TEST(PacketBuffer, GetSpanSamples) {
packet_2.timestamp); // Tmestamp wrapped around.
EXPECT_EQ(PacketBuffer::kOK,
buffer.InsertPacket(std::move(packet_1), &mock_stats));
buffer.InsertPacket(/*packet=*/std::move(packet_1),
/*stats=*/&mock_stats,
/*last_decoded_length=*/kFrameSizeSamples,
/*sample_rate=*/1000,
/*target_level_ms=*/60,
/*decoder_database=*/decoder_database));
constexpr size_t kLastDecodedSizeSamples = 2;
// packet_1 has no access to duration, and relies last decoded duration as
@ -726,7 +906,12 @@ TEST(PacketBuffer, GetSpanSamples) {
KCountDtxWaitingTime));
EXPECT_EQ(PacketBuffer::kOK,
buffer.InsertPacket(std::move(packet_2), &mock_stats));
buffer.InsertPacket(/*packet=*/std::move(packet_2),
/*stats=*/&mock_stats,
/*last_decoded_length=*/kFrameSizeSamples,
/*sample_rate=*/1000,
/*target_level_ms=*/60,
/*decoder_database=*/decoder_database));
EXPECT_EQ(kFrameSizeSamples * 2,
buffer.GetSpanSamples(0, kSampleRateHz, KCountDtxWaitingTime));