blob: 0692ef2df4e407556767349be6551e8ca81f9ecf [file] [log] [blame]
/*
* Copyright (c) 2020 The WebRTC project authors. All Rights Reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree. An additional intellectual property rights grant can be found
* in the file PATENTS. All contributing project authors may
* be found in the AUTHORS file in the root of the source tree.
*/
#include "audio/voip/audio_egress.h"
#include "api/audio_codecs/builtin_audio_encoder_factory.h"
#include "api/call/transport.h"
#include "api/task_queue/default_task_queue_factory.h"
#include "modules/audio_mixer/sine_wave_generator.h"
#include "modules/rtp_rtcp/source/rtp_packet_received.h"
#include "modules/rtp_rtcp/source/rtp_rtcp_impl2.h"
#include "rtc_base/event.h"
#include "rtc_base/logging.h"
#include "test/gmock.h"
#include "test/gtest.h"
#include "test/mock_transport.h"
namespace webrtc {
namespace {
using ::testing::Invoke;
using ::testing::NiceMock;
using ::testing::Unused;
std::unique_ptr<ModuleRtpRtcpImpl2> CreateRtpStack(Clock* clock,
Transport* transport,
uint32_t remote_ssrc) {
RtpRtcpInterface::Configuration rtp_config;
rtp_config.clock = clock;
rtp_config.audio = true;
rtp_config.rtcp_report_interval_ms = 5000;
rtp_config.outgoing_transport = transport;
rtp_config.local_media_ssrc = remote_ssrc;
auto rtp_rtcp = ModuleRtpRtcpImpl2::Create(rtp_config);
rtp_rtcp->SetSendingMediaStatus(false);
rtp_rtcp->SetRTCPStatus(RtcpMode::kCompound);
return rtp_rtcp;
}
constexpr int16_t kAudioLevel = 3004; // Used for sine wave level.
// AudioEgressTest configures audio egress by using Rtp Stack, fake clock,
// and task queue factory. Encoder factory is needed to create codec and
// configure the RTP stack in audio egress.
class AudioEgressTest : public ::testing::Test {
public:
static constexpr uint16_t kSeqNum = 12345;
static constexpr uint64_t kStartTime = 123456789;
static constexpr uint32_t kRemoteSsrc = 0xDEADBEEF;
const SdpAudioFormat kPcmuFormat = {"pcmu", 8000, 1};
AudioEgressTest()
: fake_clock_(kStartTime), wave_generator_(1000.0, kAudioLevel) {
rtp_rtcp_ = CreateRtpStack(&fake_clock_, &transport_, kRemoteSsrc);
task_queue_factory_ = CreateDefaultTaskQueueFactory();
encoder_factory_ = CreateBuiltinAudioEncoderFactory();
}
// Prepare test on audio egress by using PCMu codec with specific
// sequence number and its status to be running.
void SetUp() override {
egress_ = std::make_unique<AudioEgress>(rtp_rtcp_.get(), &fake_clock_,
task_queue_factory_.get());
constexpr int kPcmuPayload = 0;
egress_->SetEncoder(kPcmuPayload, kPcmuFormat,
encoder_factory_->MakeAudioEncoder(
kPcmuPayload, kPcmuFormat, absl::nullopt));
egress_->StartSend();
rtp_rtcp_->SetSequenceNumber(kSeqNum);
rtp_rtcp_->SetSendingStatus(true);
}
// Make sure we have shut down rtp stack and reset egress for each test.
void TearDown() override {
egress_->StopSend();
rtp_rtcp_->SetSendingStatus(false);
egress_.reset();
}
// Create an audio frame prepared for pcmu encoding. Timestamp is
// increased per RTP specification which is the number of samples it contains.
// Wave generator writes sine wave which has expected high level set
// by kAudioLevel.
std::unique_ptr<AudioFrame> GetAudioFrame(int order) {
auto frame = std::make_unique<AudioFrame>();
frame->sample_rate_hz_ = kPcmuFormat.clockrate_hz;
frame->samples_per_channel_ = kPcmuFormat.clockrate_hz / 100; // 10 ms.
frame->num_channels_ = kPcmuFormat.num_channels;
frame->timestamp_ = frame->samples_per_channel_ * order;
wave_generator_.GenerateNextFrame(frame.get());
return frame;
}
// SimulatedClock doesn't directly affect this testcase as the the
// AudioFrame's timestamp is driven by GetAudioFrame.
SimulatedClock fake_clock_;
NiceMock<MockTransport> transport_;
SineWaveGenerator wave_generator_;
std::unique_ptr<ModuleRtpRtcpImpl2> rtp_rtcp_;
std::unique_ptr<TaskQueueFactory> task_queue_factory_;
rtc::scoped_refptr<AudioEncoderFactory> encoder_factory_;
std::unique_ptr<AudioEgress> egress_;
};
TEST_F(AudioEgressTest, SendingStatusAfterStartAndStop) {
EXPECT_TRUE(egress_->IsSending());
egress_->StopSend();
EXPECT_FALSE(egress_->IsSending());
}
TEST_F(AudioEgressTest, ProcessAudioWithMute) {
constexpr int kExpected = 10;
rtc::Event event;
int rtp_count = 0;
RtpPacketReceived rtp;
auto rtp_sent = [&](const uint8_t* packet, size_t length, Unused) {
rtp.Parse(packet, length);
if (++rtp_count == kExpected) {
event.Set();
}
return true;
};
EXPECT_CALL(transport_, SendRtp).WillRepeatedly(Invoke(rtp_sent));
egress_->SetMute(true);
// Two 10 ms audio frames will result in rtp packet with ptime 20.
for (size_t i = 0; i < kExpected * 2; i++) {
egress_->SendAudioData(GetAudioFrame(i));
fake_clock_.AdvanceTimeMilliseconds(10);
}
event.Wait(/*ms=*/1000);
EXPECT_EQ(rtp_count, kExpected);
// we expect on pcmu payload to result in 255 for silenced payload
RTPHeader header;
rtp.GetHeader(&header);
size_t packet_length = rtp.size();
size_t payload_length = packet_length - header.headerLength;
size_t payload_data_length = payload_length - header.paddingLength;
const uint8_t* payload = rtp.data() + header.headerLength;
for (size_t i = 0; i < payload_data_length; ++i) {
EXPECT_EQ(*payload++, 255);
}
}
TEST_F(AudioEgressTest, ProcessAudioWithSineWave) {
constexpr int kExpected = 10;
rtc::Event event;
int rtp_count = 0;
RtpPacketReceived rtp;
auto rtp_sent = [&](const uint8_t* packet, size_t length, Unused) {
rtp.Parse(packet, length);
if (++rtp_count == kExpected) {
event.Set();
}
return true;
};
EXPECT_CALL(transport_, SendRtp).WillRepeatedly(Invoke(rtp_sent));
// Two 10 ms audio frames will result in rtp packet with ptime 20.
for (size_t i = 0; i < kExpected * 2; i++) {
egress_->SendAudioData(GetAudioFrame(i));
fake_clock_.AdvanceTimeMilliseconds(10);
}
event.Wait(/*ms=*/1000);
EXPECT_EQ(rtp_count, kExpected);
// we expect on pcmu to result in < 255 for payload with sine wave
RTPHeader header;
rtp.GetHeader(&header);
size_t packet_length = rtp.size();
size_t payload_length = packet_length - header.headerLength;
size_t payload_data_length = payload_length - header.paddingLength;
const uint8_t* payload = rtp.data() + header.headerLength;
for (size_t i = 0; i < payload_data_length; ++i) {
EXPECT_NE(*payload++, 255);
}
}
TEST_F(AudioEgressTest, SkipAudioEncodingAfterStopSend) {
constexpr int kExpected = 10;
rtc::Event event;
int rtp_count = 0;
auto rtp_sent = [&](const uint8_t* packet, size_t length, Unused) {
if (++rtp_count == kExpected) {
event.Set();
}
return true;
};
EXPECT_CALL(transport_, SendRtp).WillRepeatedly(Invoke(rtp_sent));
// Two 10 ms audio frames will result in rtp packet with ptime 20.
for (size_t i = 0; i < kExpected * 2; i++) {
egress_->SendAudioData(GetAudioFrame(i));
fake_clock_.AdvanceTimeMilliseconds(10);
}
event.Wait(/*ms=*/1000);
EXPECT_EQ(rtp_count, kExpected);
// Now stop send and yet feed more data.
egress_->StopSend();
// It should be safe to exit the test case while encoder_queue_ has
// outstanding data to process. We are making sure that this doesn't
// result in crahses or sanitizer errors due to remaining data.
for (size_t i = 0; i < kExpected * 2; i++) {
egress_->SendAudioData(GetAudioFrame(i));
fake_clock_.AdvanceTimeMilliseconds(10);
}
}
TEST_F(AudioEgressTest, ChangeEncoderFromPcmuToOpus) {
absl::optional<SdpAudioFormat> pcmu = egress_->GetEncoderFormat();
EXPECT_TRUE(pcmu);
EXPECT_EQ(pcmu->clockrate_hz, kPcmuFormat.clockrate_hz);
EXPECT_EQ(pcmu->num_channels, kPcmuFormat.num_channels);
constexpr int kOpusPayload = 120;
const SdpAudioFormat kOpusFormat = {"opus", 48000, 2};
egress_->SetEncoder(kOpusPayload, kOpusFormat,
encoder_factory_->MakeAudioEncoder(
kOpusPayload, kOpusFormat, absl::nullopt));
absl::optional<SdpAudioFormat> opus = egress_->GetEncoderFormat();
EXPECT_TRUE(opus);
EXPECT_EQ(opus->clockrate_hz, kOpusFormat.clockrate_hz);
EXPECT_EQ(opus->num_channels, kOpusFormat.num_channels);
}
TEST_F(AudioEgressTest, SendDTMF) {
constexpr int kExpected = 7;
constexpr int kPayloadType = 100;
constexpr int kDurationMs = 100;
constexpr int kSampleRate = 8000;
constexpr int kEvent = 3;
egress_->RegisterTelephoneEventType(kPayloadType, kSampleRate);
// 100 ms duration will produce total 7 DTMF
// 1 @ 20 ms, 2 @ 40 ms, 3 @ 60 ms, 4 @ 80 ms
// 5, 6, 7 @ 100 ms (last one sends 3 dtmf)
egress_->SendTelephoneEvent(kEvent, kDurationMs);
rtc::Event event;
int dtmf_count = 0;
auto is_dtmf = [&](RtpPacketReceived& rtp) {
return (rtp.PayloadType() == kPayloadType &&
rtp.SequenceNumber() == kSeqNum + dtmf_count &&
rtp.padding_size() == 0 && rtp.Marker() == (dtmf_count == 0) &&
rtp.Ssrc() == kRemoteSsrc);
};
// It's possible that we may have actual audio RTP packets along with
// DTMF packtets. We are only interested in the exact number of DTMF
// packets rtp stack is emitting.
auto rtp_sent = [&](const uint8_t* packet, size_t length, Unused) {
RtpPacketReceived rtp;
rtp.Parse(packet, length);
if (is_dtmf(rtp) && ++dtmf_count == kExpected) {
event.Set();
}
return true;
};
EXPECT_CALL(transport_, SendRtp).WillRepeatedly(Invoke(rtp_sent));
// Two 10 ms audio frames will result in rtp packet with ptime 20.
for (size_t i = 0; i < kExpected * 2; i++) {
egress_->SendAudioData(GetAudioFrame(i));
fake_clock_.AdvanceTimeMilliseconds(10);
}
event.Wait(/*ms=*/1000);
EXPECT_EQ(dtmf_count, kExpected);
}
TEST_F(AudioEgressTest, TestAudioInputLevelAndEnergyDuration) {
// Per audio_level's kUpdateFrequency, we need more than 10 audio samples to
// get audio level from input source.
constexpr int kExpected = 6;
rtc::Event event;
int rtp_count = 0;
auto rtp_sent = [&](const uint8_t* packet, size_t length, Unused) {
if (++rtp_count == kExpected) {
event.Set();
}
return true;
};
EXPECT_CALL(transport_, SendRtp).WillRepeatedly(Invoke(rtp_sent));
// Two 10 ms audio frames will result in rtp packet with ptime 20.
for (size_t i = 0; i < kExpected * 2; i++) {
egress_->SendAudioData(GetAudioFrame(i));
fake_clock_.AdvanceTimeMilliseconds(10);
}
event.Wait(/*give_up_after_ms=*/1000);
EXPECT_EQ(rtp_count, kExpected);
constexpr double kExpectedEnergy = 0.00016809565587789564;
constexpr double kExpectedDuration = 0.11999999999999998;
EXPECT_EQ(egress_->GetInputAudioLevel(), kAudioLevel);
EXPECT_DOUBLE_EQ(egress_->GetInputTotalEnergy(), kExpectedEnergy);
EXPECT_DOUBLE_EQ(egress_->GetInputTotalDuration(), kExpectedDuration);
}
} // namespace
} // namespace webrtc