Refactor NetEq insert packet list.
Move some logic from PacketBuffer to NetEqImpl. Bug: webrtc:13322 Change-Id: I88b1e55c0cd69700730d9ed41be04fcf1effa03f Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/328861 Commit-Queue: Jakob Ivarsson <jakobi@webrtc.org> Reviewed-by: Henrik Lundin <henrik.lundin@webrtc.org> Cr-Commit-Position: refs/heads/main@{#41270}
This commit is contained in:
parent
96e14c82b9
commit
526187708d
@ -27,13 +27,6 @@ class MockPacketBuffer : public PacketBuffer {
|
||||
MOCK_METHOD(void, Flush, (), (override));
|
||||
MOCK_METHOD(bool, Empty, (), (const, override));
|
||||
MOCK_METHOD(int, InsertPacket, (Packet && packet), (override));
|
||||
MOCK_METHOD(int,
|
||||
InsertPacketList,
|
||||
(PacketList * packet_list,
|
||||
const DecoderDatabase& decoder_database,
|
||||
absl::optional<uint8_t>* current_rtp_payload_type,
|
||||
absl::optional<uint8_t>* current_cng_rtp_payload_type),
|
||||
(override));
|
||||
MOCK_METHOD(int,
|
||||
NextTimestamp,
|
||||
(uint32_t * next_timestamp),
|
||||
|
||||
@ -70,6 +70,62 @@ std::unique_ptr<NetEqController> CreateNetEqController(
|
||||
return controller_factory.CreateNetEqController(config);
|
||||
}
|
||||
|
||||
void SetAudioFrameActivityAndType(bool vad_enabled,
|
||||
NetEqImpl::OutputType type,
|
||||
AudioFrame::VADActivity last_vad_activity,
|
||||
AudioFrame* audio_frame) {
|
||||
switch (type) {
|
||||
case NetEqImpl::OutputType::kNormalSpeech: {
|
||||
audio_frame->speech_type_ = AudioFrame::kNormalSpeech;
|
||||
audio_frame->vad_activity_ = AudioFrame::kVadActive;
|
||||
break;
|
||||
}
|
||||
case NetEqImpl::OutputType::kVadPassive: {
|
||||
// This should only be reached if the VAD is enabled.
|
||||
RTC_DCHECK(vad_enabled);
|
||||
audio_frame->speech_type_ = AudioFrame::kNormalSpeech;
|
||||
audio_frame->vad_activity_ = AudioFrame::kVadPassive;
|
||||
break;
|
||||
}
|
||||
case NetEqImpl::OutputType::kCNG: {
|
||||
audio_frame->speech_type_ = AudioFrame::kCNG;
|
||||
audio_frame->vad_activity_ = AudioFrame::kVadPassive;
|
||||
break;
|
||||
}
|
||||
case NetEqImpl::OutputType::kPLC: {
|
||||
audio_frame->speech_type_ = AudioFrame::kPLC;
|
||||
audio_frame->vad_activity_ = last_vad_activity;
|
||||
break;
|
||||
}
|
||||
case NetEqImpl::OutputType::kPLCCNG: {
|
||||
audio_frame->speech_type_ = AudioFrame::kPLCCNG;
|
||||
audio_frame->vad_activity_ = AudioFrame::kVadPassive;
|
||||
break;
|
||||
}
|
||||
case NetEqImpl::OutputType::kCodecPLC: {
|
||||
audio_frame->speech_type_ = AudioFrame::kCodecPLC;
|
||||
audio_frame->vad_activity_ = last_vad_activity;
|
||||
break;
|
||||
}
|
||||
default:
|
||||
RTC_DCHECK_NOTREACHED();
|
||||
}
|
||||
if (!vad_enabled) {
|
||||
// Always set kVadUnknown when receive VAD is inactive.
|
||||
audio_frame->vad_activity_ = AudioFrame::kVadUnknown;
|
||||
}
|
||||
}
|
||||
|
||||
// Returns true if both payload types are known to the decoder database, and
|
||||
// have the same sample rate.
|
||||
bool EqualSampleRates(uint8_t pt1,
|
||||
uint8_t pt2,
|
||||
const DecoderDatabase& decoder_database) {
|
||||
auto* di1 = decoder_database.GetDecoderInfo(pt1);
|
||||
auto* di2 = decoder_database.GetDecoderInfo(pt2);
|
||||
return di1 && di2 && di1->SampleRateHz() == di2->SampleRateHz();
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
NetEqImpl::Dependencies::Dependencies(
|
||||
@ -183,54 +239,6 @@ void NetEqImpl::InsertEmptyPacket(const RTPHeader& rtp_header) {
|
||||
controller_->RegisterEmptyPacket();
|
||||
}
|
||||
|
||||
namespace {
|
||||
void SetAudioFrameActivityAndType(bool vad_enabled,
|
||||
NetEqImpl::OutputType type,
|
||||
AudioFrame::VADActivity last_vad_activity,
|
||||
AudioFrame* audio_frame) {
|
||||
switch (type) {
|
||||
case NetEqImpl::OutputType::kNormalSpeech: {
|
||||
audio_frame->speech_type_ = AudioFrame::kNormalSpeech;
|
||||
audio_frame->vad_activity_ = AudioFrame::kVadActive;
|
||||
break;
|
||||
}
|
||||
case NetEqImpl::OutputType::kVadPassive: {
|
||||
// This should only be reached if the VAD is enabled.
|
||||
RTC_DCHECK(vad_enabled);
|
||||
audio_frame->speech_type_ = AudioFrame::kNormalSpeech;
|
||||
audio_frame->vad_activity_ = AudioFrame::kVadPassive;
|
||||
break;
|
||||
}
|
||||
case NetEqImpl::OutputType::kCNG: {
|
||||
audio_frame->speech_type_ = AudioFrame::kCNG;
|
||||
audio_frame->vad_activity_ = AudioFrame::kVadPassive;
|
||||
break;
|
||||
}
|
||||
case NetEqImpl::OutputType::kPLC: {
|
||||
audio_frame->speech_type_ = AudioFrame::kPLC;
|
||||
audio_frame->vad_activity_ = last_vad_activity;
|
||||
break;
|
||||
}
|
||||
case NetEqImpl::OutputType::kPLCCNG: {
|
||||
audio_frame->speech_type_ = AudioFrame::kPLCCNG;
|
||||
audio_frame->vad_activity_ = AudioFrame::kVadPassive;
|
||||
break;
|
||||
}
|
||||
case NetEqImpl::OutputType::kCodecPLC: {
|
||||
audio_frame->speech_type_ = AudioFrame::kCodecPLC;
|
||||
audio_frame->vad_activity_ = last_vad_activity;
|
||||
break;
|
||||
}
|
||||
default:
|
||||
RTC_DCHECK_NOTREACHED();
|
||||
}
|
||||
if (!vad_enabled) {
|
||||
// Always set kVadUnknown when receive VAD is inactive.
|
||||
audio_frame->vad_activity_ = AudioFrame::kVadUnknown;
|
||||
}
|
||||
}
|
||||
} // namespace
|
||||
|
||||
int NetEqImpl::GetAudio(AudioFrame* audio_frame,
|
||||
bool* muted,
|
||||
int* current_sample_rate_hz,
|
||||
@ -681,18 +689,25 @@ int NetEqImpl::InsertPacketInternal(const RTPHeader& rtp_header,
|
||||
number_of_primary_packets);
|
||||
}
|
||||
|
||||
// Insert packets in buffer.
|
||||
const int ret = packet_buffer_->InsertPacketList(
|
||||
&parsed_packet_list, *decoder_database_, ¤t_rtp_payload_type_,
|
||||
¤t_cng_rtp_payload_type_);
|
||||
bool buffer_flush_occured = false;
|
||||
if (ret == PacketBuffer::kFlushed) {
|
||||
for (Packet& packet : parsed_packet_list) {
|
||||
if (MaybeChangePayloadType(packet.payload_type)) {
|
||||
packet_buffer_->Flush();
|
||||
buffer_flush_occured = true;
|
||||
}
|
||||
int return_val = packet_buffer_->InsertPacket(std::move(packet));
|
||||
if (return_val == PacketBuffer::kFlushed) {
|
||||
buffer_flush_occured = true;
|
||||
} else if (return_val != PacketBuffer::kOK) {
|
||||
// An error occurred.
|
||||
return kOtherError;
|
||||
}
|
||||
}
|
||||
|
||||
if (buffer_flush_occured) {
|
||||
// Reset DSP timestamp etc. if packet buffer flushed.
|
||||
new_codec_ = true;
|
||||
update_sample_rate_and_channels = true;
|
||||
buffer_flush_occured = true;
|
||||
} else if (ret != PacketBuffer::kOK) {
|
||||
return kOtherError;
|
||||
}
|
||||
|
||||
if (first_packet_) {
|
||||
@ -759,6 +774,31 @@ int NetEqImpl::InsertPacketInternal(const RTPHeader& rtp_header,
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool NetEqImpl::MaybeChangePayloadType(uint8_t payload_type) {
|
||||
bool changed = false;
|
||||
if (decoder_database_->IsComfortNoise(payload_type)) {
|
||||
if (current_cng_rtp_payload_type_ &&
|
||||
*current_cng_rtp_payload_type_ != payload_type) {
|
||||
// New CNG payload type implies new codec type.
|
||||
current_rtp_payload_type_ = absl::nullopt;
|
||||
changed = true;
|
||||
}
|
||||
current_cng_rtp_payload_type_ = payload_type;
|
||||
} else if (!decoder_database_->IsDtmf(payload_type)) {
|
||||
// This must be speech.
|
||||
if ((current_rtp_payload_type_ &&
|
||||
*current_rtp_payload_type_ != payload_type) ||
|
||||
(current_cng_rtp_payload_type_ &&
|
||||
!EqualSampleRates(payload_type, *current_cng_rtp_payload_type_,
|
||||
*decoder_database_))) {
|
||||
current_cng_rtp_payload_type_ = absl::nullopt;
|
||||
changed = true;
|
||||
}
|
||||
current_rtp_payload_type_ = payload_type;
|
||||
}
|
||||
return changed;
|
||||
}
|
||||
|
||||
int NetEqImpl::GetAudioInternal(AudioFrame* audio_frame,
|
||||
bool* muted,
|
||||
absl::optional<Operation> action_override) {
|
||||
|
||||
@ -27,6 +27,7 @@
|
||||
#include "modules/audio_coding/neteq/audio_multi_vector.h"
|
||||
#include "modules/audio_coding/neteq/expand_uma_logger.h"
|
||||
#include "modules/audio_coding/neteq/packet.h"
|
||||
#include "modules/audio_coding/neteq/packet_buffer.h"
|
||||
#include "modules/audio_coding/neteq/random_vector.h"
|
||||
#include "modules/audio_coding/neteq/statistics_calculator.h"
|
||||
#include "rtc_base/synchronization/mutex.h"
|
||||
@ -46,7 +47,6 @@ class Expand;
|
||||
class Merge;
|
||||
class NackTracker;
|
||||
class Normal;
|
||||
class PacketBuffer;
|
||||
class RedPayloadSplitter;
|
||||
class PostDecodeVad;
|
||||
class PreemptiveExpand;
|
||||
@ -215,6 +215,12 @@ class NetEqImpl : public webrtc::NetEq {
|
||||
rtc::ArrayView<const uint8_t> payload)
|
||||
RTC_EXCLUSIVE_LOCKS_REQUIRED(mutex_);
|
||||
|
||||
// Returns true if the payload type changed (this should be followed by
|
||||
// resetting various state). Returns false if the current payload type is
|
||||
// unknown or equal to `payload_type`.
|
||||
bool MaybeChangePayloadType(uint8_t payload_type)
|
||||
RTC_EXCLUSIVE_LOCKS_REQUIRED(mutex_);
|
||||
|
||||
// Delivers 10 ms of audio data. The data is written to `audio_frame`.
|
||||
// Returns 0 on success, otherwise an error code.
|
||||
int GetAudioInternal(AudioFrame* audio_frame,
|
||||
|
||||
@ -329,14 +329,9 @@ TEST_F(NetEqImplTest, InsertPacket) {
|
||||
EXPECT_CALL(*mock_packet_buffer_, Empty())
|
||||
.WillOnce(Return(false)); // Called once after first packet is inserted.
|
||||
EXPECT_CALL(*mock_packet_buffer_, Flush()).Times(1);
|
||||
EXPECT_CALL(*mock_packet_buffer_, InsertPacketList(_, _, _, _))
|
||||
EXPECT_CALL(*mock_packet_buffer_, InsertPacket(_))
|
||||
.Times(2)
|
||||
.WillRepeatedly(DoAll(SetArgPointee<2>(kPayloadType),
|
||||
WithArg<0>(Invoke(DeletePacketsAndReturnOk))));
|
||||
// SetArgPointee<2>(kPayloadType) means that the third argument (zero-based
|
||||
// index) is a pointer, and the variable pointed to is set to kPayloadType.
|
||||
// Also invoke the function DeletePacketsAndReturnOk to properly delete all
|
||||
// packets in the list (to avoid memory leaks in the test).
|
||||
.WillRepeatedly(Return(PacketBuffer::kOK));
|
||||
EXPECT_CALL(*mock_packet_buffer_, PeekNextPacket())
|
||||
.Times(1)
|
||||
.WillOnce(Return(&fake_packet));
|
||||
@ -1642,6 +1637,74 @@ TEST_F(NetEqImplTest, NoCrashWith1000Channels) {
|
||||
}
|
||||
}
|
||||
|
||||
// The test first inserts a packet with narrow-band CNG, then a packet with
|
||||
// wide-band speech. The expected behavior is to detect a change in sample rate,
|
||||
// even though no speech packet has been inserted before, and flush out the CNG
|
||||
// packet.
|
||||
TEST_F(NetEqImplTest, CngFirstThenSpeechWithNewSampleRate) {
|
||||
UseNoMocks();
|
||||
CreateInstance();
|
||||
constexpr int kCnPayloadType = 7;
|
||||
neteq_->RegisterPayloadType(kCnPayloadType, SdpAudioFormat("cn", 8000, 1));
|
||||
constexpr int kSpeechPayloadType = 8;
|
||||
neteq_->RegisterPayloadType(kSpeechPayloadType,
|
||||
SdpAudioFormat("l16", 16000, 1));
|
||||
|
||||
RTPHeader header;
|
||||
header.payloadType = kCnPayloadType;
|
||||
uint8_t payload[320] = {0};
|
||||
|
||||
EXPECT_EQ(neteq_->InsertPacket(header, payload), NetEq::kOK);
|
||||
EXPECT_EQ(neteq_->GetLifetimeStatistics().packets_discarded, 0u);
|
||||
|
||||
header.payloadType = kSpeechPayloadType;
|
||||
header.timestamp += 160;
|
||||
EXPECT_EQ(neteq_->InsertPacket(header, payload), NetEq::kOK);
|
||||
// CN packet should be discarded, since it does not match the
|
||||
// new speech sample rate.
|
||||
EXPECT_EQ(neteq_->GetLifetimeStatistics().packets_discarded, 1u);
|
||||
|
||||
// Next decoded packet should be speech.
|
||||
AudioFrame audio_frame;
|
||||
bool muted;
|
||||
EXPECT_EQ(neteq_->GetAudio(&audio_frame, &muted), NetEq::kOK);
|
||||
EXPECT_EQ(audio_frame.sample_rate_hz(), 16000);
|
||||
EXPECT_EQ(audio_frame.speech_type_, AudioFrame::SpeechType::kNormalSpeech);
|
||||
}
|
||||
|
||||
TEST_F(NetEqImplTest, InsertPacketChangePayloadType) {
|
||||
UseNoMocks();
|
||||
CreateInstance();
|
||||
constexpr int kPcmuPayloadType = 7;
|
||||
neteq_->RegisterPayloadType(kPcmuPayloadType,
|
||||
SdpAudioFormat("pcmu", 8000, 1));
|
||||
constexpr int kPcmaPayloadType = 8;
|
||||
neteq_->RegisterPayloadType(kPcmaPayloadType,
|
||||
SdpAudioFormat("pcma", 8000, 1));
|
||||
|
||||
RTPHeader header;
|
||||
header.payloadType = kPcmuPayloadType;
|
||||
header.timestamp = 1234;
|
||||
uint8_t payload[160] = {0};
|
||||
|
||||
EXPECT_EQ(neteq_->InsertPacket(header, payload), NetEq::kOK);
|
||||
EXPECT_EQ(neteq_->GetLifetimeStatistics().packets_discarded, 0u);
|
||||
|
||||
header.payloadType = kPcmaPayloadType;
|
||||
header.timestamp += 80;
|
||||
EXPECT_EQ(neteq_->InsertPacket(header, payload), NetEq::kOK);
|
||||
// The previous packet should be discarded since the codec changed.
|
||||
EXPECT_EQ(neteq_->GetLifetimeStatistics().packets_discarded, 1u);
|
||||
|
||||
// Next decoded packet should be speech.
|
||||
AudioFrame audio_frame;
|
||||
bool muted;
|
||||
EXPECT_EQ(neteq_->GetAudio(&audio_frame, &muted), NetEq::kOK);
|
||||
EXPECT_EQ(audio_frame.sample_rate_hz(), 8000);
|
||||
EXPECT_EQ(audio_frame.speech_type_, AudioFrame::SpeechType::kNormalSpeech);
|
||||
// TODO(jakobi): check active decoder.
|
||||
}
|
||||
|
||||
class Decoder120ms : public AudioDecoder {
|
||||
public:
|
||||
Decoder120ms(int sample_rate_hz, SpeechType speech_type)
|
||||
|
||||
@ -44,16 +44,6 @@ class NewTimestampIsLarger {
|
||||
const Packet& new_packet_;
|
||||
};
|
||||
|
||||
// Returns true if both payload types are known to the decoder database, and
|
||||
// have the same sample rate.
|
||||
bool EqualSampleRates(uint8_t pt1,
|
||||
uint8_t pt2,
|
||||
const DecoderDatabase& decoder_database) {
|
||||
auto* di1 = decoder_database.GetDecoderInfo(pt1);
|
||||
auto* di2 = decoder_database.GetDecoderInfo(pt2);
|
||||
return di1 && di2 && di1->SampleRateHz() == di2->SampleRateHz();
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
PacketBuffer::PacketBuffer(size_t max_number_of_packets,
|
||||
@ -128,50 +118,6 @@ int PacketBuffer::InsertPacket(Packet&& packet) {
|
||||
return return_val;
|
||||
}
|
||||
|
||||
int PacketBuffer::InsertPacketList(
|
||||
PacketList* packet_list,
|
||||
const DecoderDatabase& decoder_database,
|
||||
absl::optional<uint8_t>* current_rtp_payload_type,
|
||||
absl::optional<uint8_t>* current_cng_rtp_payload_type) {
|
||||
bool flushed = false;
|
||||
for (auto& packet : *packet_list) {
|
||||
if (decoder_database.IsComfortNoise(packet.payload_type)) {
|
||||
if (*current_cng_rtp_payload_type &&
|
||||
**current_cng_rtp_payload_type != packet.payload_type) {
|
||||
// New CNG payload type implies new codec type.
|
||||
*current_rtp_payload_type = absl::nullopt;
|
||||
Flush();
|
||||
flushed = true;
|
||||
}
|
||||
*current_cng_rtp_payload_type = packet.payload_type;
|
||||
} else if (!decoder_database.IsDtmf(packet.payload_type)) {
|
||||
// This must be speech.
|
||||
if ((*current_rtp_payload_type &&
|
||||
**current_rtp_payload_type != packet.payload_type) ||
|
||||
(*current_cng_rtp_payload_type &&
|
||||
!EqualSampleRates(packet.payload_type,
|
||||
**current_cng_rtp_payload_type,
|
||||
decoder_database))) {
|
||||
*current_cng_rtp_payload_type = absl::nullopt;
|
||||
Flush();
|
||||
flushed = true;
|
||||
}
|
||||
*current_rtp_payload_type = packet.payload_type;
|
||||
}
|
||||
int return_val = InsertPacket(std::move(packet));
|
||||
if (return_val == kFlushed) {
|
||||
// The buffer flushed, but this is not an error. We can still continue.
|
||||
flushed = true;
|
||||
} else if (return_val != kOK) {
|
||||
// An error occurred. Delete remaining packets in list and return.
|
||||
packet_list->clear();
|
||||
return return_val;
|
||||
}
|
||||
}
|
||||
packet_list->clear();
|
||||
return flushed ? kFlushed : kOK;
|
||||
}
|
||||
|
||||
int PacketBuffer::NextTimestamp(uint32_t* next_timestamp) const {
|
||||
if (Empty()) {
|
||||
return kBufferEmpty;
|
||||
|
||||
@ -58,20 +58,6 @@ class PacketBuffer {
|
||||
// was flushed due to overfilling.
|
||||
virtual int InsertPacket(Packet&& packet);
|
||||
|
||||
// Inserts a list of packets into the buffer. The buffer will take over
|
||||
// ownership of the packet objects.
|
||||
// Returns PacketBuffer::kOK if all packets were inserted successfully.
|
||||
// If the buffer was flushed due to overfilling, only a subset of the list is
|
||||
// inserted, and PacketBuffer::kFlushed is returned.
|
||||
// The last three parameters are included for legacy compatibility.
|
||||
// TODO(hlundin): Redesign to not use current_*_payload_type and
|
||||
// decoder_database.
|
||||
virtual int InsertPacketList(
|
||||
PacketList* packet_list,
|
||||
const DecoderDatabase& decoder_database,
|
||||
absl::optional<uint8_t>* current_rtp_payload_type,
|
||||
absl::optional<uint8_t>* current_cng_rtp_payload_type);
|
||||
|
||||
// Gets the timestamp for the first packet in the buffer and writes it to the
|
||||
// output variable `next_timestamp`.
|
||||
// Returns PacketBuffer::kBufferEmpty if the buffer is empty,
|
||||
|
||||
@ -196,92 +196,6 @@ TEST(PacketBuffer, OverfillBuffer) {
|
||||
EXPECT_CALL(decoder_database, Die()); // Called when object is deleted.
|
||||
}
|
||||
|
||||
// Test inserting a list of packets.
|
||||
TEST(PacketBuffer, InsertPacketList) {
|
||||
TickTimer tick_timer;
|
||||
StrictMock<MockStatisticsCalculator> mock_stats;
|
||||
PacketBuffer buffer(10, &tick_timer, &mock_stats); // 10 packets.
|
||||
PacketGenerator gen(0, 0, 0, 10);
|
||||
PacketList list;
|
||||
const int payload_len = 10;
|
||||
|
||||
// Insert 10 small packets.
|
||||
for (int i = 0; i < 10; ++i) {
|
||||
list.push_back(gen.NextPacket(payload_len, nullptr));
|
||||
}
|
||||
|
||||
MockDecoderDatabase decoder_database;
|
||||
auto factory = CreateBuiltinAudioDecoderFactory();
|
||||
const DecoderDatabase::DecoderInfo info(SdpAudioFormat("pcmu", 8000, 1),
|
||||
absl::nullopt, factory.get());
|
||||
EXPECT_CALL(decoder_database, GetDecoderInfo(0))
|
||||
.WillRepeatedly(Return(&info));
|
||||
|
||||
absl::optional<uint8_t> current_pt;
|
||||
absl::optional<uint8_t> current_cng_pt;
|
||||
EXPECT_EQ(PacketBuffer::kOK,
|
||||
buffer.InsertPacketList(
|
||||
/*packet_list=*/&list,
|
||||
/*decoder_database=*/decoder_database,
|
||||
/*current_rtp_payload_type=*/¤t_pt,
|
||||
/*current_cng_rtp_payload_type=*/¤t_cng_pt));
|
||||
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.
|
||||
|
||||
EXPECT_CALL(decoder_database, Die()); // Called when object is deleted.
|
||||
}
|
||||
|
||||
// Test inserting a list of packets. Last packet is of a different payload type.
|
||||
// Expecting the buffer to flush.
|
||||
// TODO(hlundin): Remove this test when legacy operation is no longer needed.
|
||||
TEST(PacketBuffer, InsertPacketListChangePayloadType) {
|
||||
TickTimer tick_timer;
|
||||
StrictMock<MockStatisticsCalculator> mock_stats;
|
||||
PacketBuffer buffer(10, &tick_timer, &mock_stats); // 10 packets.
|
||||
PacketGenerator gen(0, 0, 0, 10);
|
||||
PacketList list;
|
||||
const int payload_len = 10;
|
||||
|
||||
// Insert 10 small packets.
|
||||
for (int i = 0; i < 10; ++i) {
|
||||
list.push_back(gen.NextPacket(payload_len, nullptr));
|
||||
}
|
||||
// Insert 11th packet of another payload type (not CNG).
|
||||
{
|
||||
Packet packet = gen.NextPacket(payload_len, nullptr);
|
||||
packet.payload_type = 1;
|
||||
list.push_back(std::move(packet));
|
||||
}
|
||||
|
||||
MockDecoderDatabase decoder_database;
|
||||
auto factory = CreateBuiltinAudioDecoderFactory();
|
||||
const DecoderDatabase::DecoderInfo info0(SdpAudioFormat("pcmu", 8000, 1),
|
||||
absl::nullopt, factory.get());
|
||||
EXPECT_CALL(decoder_database, GetDecoderInfo(0))
|
||||
.WillRepeatedly(Return(&info0));
|
||||
const DecoderDatabase::DecoderInfo info1(SdpAudioFormat("pcma", 8000, 1),
|
||||
absl::nullopt, factory.get());
|
||||
EXPECT_CALL(decoder_database, GetDecoderInfo(1))
|
||||
.WillRepeatedly(Return(&info1));
|
||||
|
||||
absl::optional<uint8_t> current_pt;
|
||||
absl::optional<uint8_t> current_cng_pt;
|
||||
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=*/¤t_pt,
|
||||
/*current_cng_rtp_payload_type=*/¤t_cng_pt));
|
||||
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.
|
||||
|
||||
EXPECT_CALL(decoder_database, Die()); // Called when object is deleted.
|
||||
}
|
||||
|
||||
TEST(PacketBuffer, ExtractOrderRedundancy) {
|
||||
TickTimer tick_timer;
|
||||
@ -434,21 +348,9 @@ TEST(PacketBuffer, Reordering) {
|
||||
}
|
||||
}
|
||||
|
||||
MockDecoderDatabase decoder_database;
|
||||
auto factory = CreateBuiltinAudioDecoderFactory();
|
||||
const DecoderDatabase::DecoderInfo info(SdpAudioFormat("pcmu", 8000, 1),
|
||||
absl::nullopt, factory.get());
|
||||
EXPECT_CALL(decoder_database, GetDecoderInfo(0))
|
||||
.WillRepeatedly(Return(&info));
|
||||
absl::optional<uint8_t> current_pt;
|
||||
absl::optional<uint8_t> current_cng_pt;
|
||||
|
||||
EXPECT_EQ(PacketBuffer::kOK,
|
||||
buffer.InsertPacketList(
|
||||
/*packet_list=*/&list,
|
||||
/*decoder_database=*/decoder_database,
|
||||
/*current_rtp_payload_type=*/¤t_pt,
|
||||
/*current_cng_rtp_payload_type=*/¤t_cng_pt));
|
||||
for (Packet& packet : list) {
|
||||
EXPECT_EQ(PacketBuffer::kOK, buffer.InsertPacket(std::move(packet)));
|
||||
}
|
||||
EXPECT_EQ(10u, buffer.NumPacketsInBuffer());
|
||||
|
||||
// Extract them and make sure that come out in the right order.
|
||||
@ -460,77 +362,6 @@ TEST(PacketBuffer, Reordering) {
|
||||
current_ts += ts_increment;
|
||||
}
|
||||
EXPECT_TRUE(buffer.Empty());
|
||||
|
||||
EXPECT_CALL(decoder_database, Die()); // Called when object is deleted.
|
||||
}
|
||||
|
||||
// The test first inserts a packet with narrow-band CNG, then a packet with
|
||||
// wide-band speech. The expected behavior of the packet buffer is to detect a
|
||||
// change in sample rate, even though no speech packet has been inserted before,
|
||||
// and flush out the CNG packet.
|
||||
TEST(PacketBuffer, CngFirstThenSpeechWithNewSampleRate) {
|
||||
TickTimer tick_timer;
|
||||
StrictMock<MockStatisticsCalculator> mock_stats;
|
||||
PacketBuffer buffer(10, &tick_timer, &mock_stats); // 10 packets.
|
||||
const uint8_t kCngPt = 13;
|
||||
const int kPayloadLen = 10;
|
||||
const uint8_t kSpeechPt = 100;
|
||||
|
||||
MockDecoderDatabase decoder_database;
|
||||
auto factory = CreateBuiltinAudioDecoderFactory();
|
||||
const DecoderDatabase::DecoderInfo info_cng(SdpAudioFormat("cn", 8000, 1),
|
||||
absl::nullopt, factory.get());
|
||||
EXPECT_CALL(decoder_database, GetDecoderInfo(kCngPt))
|
||||
.WillRepeatedly(Return(&info_cng));
|
||||
const DecoderDatabase::DecoderInfo info_speech(
|
||||
SdpAudioFormat("l16", 16000, 1), absl::nullopt, factory.get());
|
||||
EXPECT_CALL(decoder_database, GetDecoderInfo(kSpeechPt))
|
||||
.WillRepeatedly(Return(&info_speech));
|
||||
|
||||
// Insert first packet, which is narrow-band CNG.
|
||||
PacketGenerator gen(0, 0, kCngPt, 10);
|
||||
PacketList list;
|
||||
list.push_back(gen.NextPacket(kPayloadLen, nullptr));
|
||||
absl::optional<uint8_t> current_pt;
|
||||
absl::optional<uint8_t> current_cng_pt;
|
||||
|
||||
EXPECT_EQ(PacketBuffer::kOK,
|
||||
buffer.InsertPacketList(
|
||||
/*packet_list=*/&list,
|
||||
/*decoder_database=*/decoder_database,
|
||||
/*current_rtp_payload_type=*/¤t_pt,
|
||||
/*current_cng_rtp_payload_type=*/¤t_cng_pt));
|
||||
EXPECT_TRUE(list.empty());
|
||||
EXPECT_EQ(1u, buffer.NumPacketsInBuffer());
|
||||
ASSERT_TRUE(buffer.PeekNextPacket());
|
||||
EXPECT_EQ(kCngPt, buffer.PeekNextPacket()->payload_type);
|
||||
EXPECT_EQ(current_pt, absl::nullopt); // Current payload type not set.
|
||||
EXPECT_EQ(kCngPt, current_cng_pt); // CNG payload type set.
|
||||
|
||||
// Insert second packet, which is wide-band speech.
|
||||
{
|
||||
Packet packet = gen.NextPacket(kPayloadLen, nullptr);
|
||||
packet.payload_type = kSpeechPt;
|
||||
list.push_back(std::move(packet));
|
||||
}
|
||||
// Expect the buffer to flush out the CNG packet, since it does not match the
|
||||
// new speech sample rate.
|
||||
EXPECT_CALL(mock_stats, PacketsDiscarded(1));
|
||||
EXPECT_EQ(PacketBuffer::kFlushed,
|
||||
buffer.InsertPacketList(
|
||||
/*packet_list=*/&list,
|
||||
/*decoder_database=*/decoder_database,
|
||||
/*current_rtp_payload_type=*/¤t_pt,
|
||||
/*current_cng_rtp_payload_type=*/¤t_cng_pt));
|
||||
EXPECT_TRUE(list.empty());
|
||||
EXPECT_EQ(1u, buffer.NumPacketsInBuffer());
|
||||
ASSERT_TRUE(buffer.PeekNextPacket());
|
||||
EXPECT_EQ(kSpeechPt, buffer.PeekNextPacket()->payload_type);
|
||||
|
||||
EXPECT_EQ(kSpeechPt, current_pt); // Current payload type set.
|
||||
EXPECT_EQ(absl::nullopt, current_cng_pt); // CNG payload type reset.
|
||||
|
||||
EXPECT_CALL(decoder_database, Die()); // Called when object is deleted.
|
||||
}
|
||||
|
||||
TEST(PacketBuffer, Failures) {
|
||||
@ -541,66 +372,26 @@ 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, &mock_stats); // 100 packets.
|
||||
PacketBuffer buffer(100, &tick_timer, &mock_stats); // 100 packets.
|
||||
{
|
||||
Packet packet = gen.NextPacket(payload_len, nullptr);
|
||||
packet.payload.Clear();
|
||||
EXPECT_EQ(PacketBuffer::kInvalidPacket,
|
||||
buffer->InsertPacket(/*packet=*/std::move(packet)));
|
||||
buffer.InsertPacket(/*packet=*/std::move(packet)));
|
||||
}
|
||||
// Buffer should still be empty. Test all empty-checks.
|
||||
uint32_t temp_ts;
|
||||
EXPECT_EQ(PacketBuffer::kBufferEmpty, buffer->NextTimestamp(&temp_ts));
|
||||
EXPECT_EQ(PacketBuffer::kBufferEmpty, buffer.NextTimestamp(&temp_ts));
|
||||
EXPECT_EQ(PacketBuffer::kBufferEmpty,
|
||||
buffer->NextHigherTimestamp(0, &temp_ts));
|
||||
EXPECT_EQ(NULL, buffer->PeekNextPacket());
|
||||
EXPECT_FALSE(buffer->GetNextPacket());
|
||||
buffer.NextHigherTimestamp(0, &temp_ts));
|
||||
EXPECT_EQ(NULL, buffer.PeekNextPacket());
|
||||
EXPECT_FALSE(buffer.GetNextPacket());
|
||||
|
||||
// Discarding packets will not invoke mock_stats.PacketDiscarded() because the
|
||||
// packet buffer is empty.
|
||||
EXPECT_EQ(PacketBuffer::kBufferEmpty, buffer->DiscardNextPacket());
|
||||
buffer->DiscardAllOldPackets(0);
|
||||
|
||||
// Insert one packet to make the buffer non-empty.
|
||||
EXPECT_EQ(PacketBuffer::kOK, buffer->InsertPacket(/*packet=*/gen.NextPacket(
|
||||
payload_len, nullptr)));
|
||||
EXPECT_EQ(PacketBuffer::kInvalidPointer, buffer->NextTimestamp(NULL));
|
||||
EXPECT_EQ(PacketBuffer::kInvalidPointer,
|
||||
buffer->NextHigherTimestamp(0, NULL));
|
||||
delete buffer;
|
||||
|
||||
// Insert packet list of three packets, where the second packet has an invalid
|
||||
// payload. Expect first packet to be inserted, and the remaining two to be
|
||||
// discarded.
|
||||
buffer = new PacketBuffer(100, &tick_timer, &mock_stats); // 100 packets.
|
||||
PacketList list;
|
||||
list.push_back(gen.NextPacket(payload_len, nullptr)); // Valid packet.
|
||||
{
|
||||
Packet packet = gen.NextPacket(payload_len, nullptr);
|
||||
packet.payload.Clear(); // Invalid.
|
||||
list.push_back(std::move(packet));
|
||||
}
|
||||
list.push_back(gen.NextPacket(payload_len, nullptr)); // Valid packet.
|
||||
auto factory = CreateBuiltinAudioDecoderFactory();
|
||||
const DecoderDatabase::DecoderInfo info(SdpAudioFormat("pcmu", 8000, 1),
|
||||
absl::nullopt, factory.get());
|
||||
EXPECT_CALL(decoder_database, GetDecoderInfo(0))
|
||||
.WillRepeatedly(Return(&info));
|
||||
absl::optional<uint8_t> current_pt;
|
||||
absl::optional<uint8_t> current_cng_pt;
|
||||
EXPECT_EQ(PacketBuffer::kInvalidPacket,
|
||||
buffer->InsertPacketList(
|
||||
/*packet_list=*/&list,
|
||||
/*decoder_database=*/decoder_database,
|
||||
/*current_rtp_payload_type=*/¤t_pt,
|
||||
/*current_cng_rtp_payload_type=*/¤t_cng_pt));
|
||||
EXPECT_TRUE(list.empty()); // The PacketBuffer should have depleted the list.
|
||||
EXPECT_EQ(1u, buffer->NumPacketsInBuffer());
|
||||
delete buffer;
|
||||
EXPECT_CALL(decoder_database, Die()); // Called when object is deleted.
|
||||
EXPECT_EQ(PacketBuffer::kBufferEmpty, buffer.DiscardNextPacket());
|
||||
buffer.DiscardAllOldPackets(0);
|
||||
}
|
||||
|
||||
// Test packet comparison function.
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user