Audio egress implementation for initial voip api in api/voip.

For simplicity and flexibility on audio only API, it deemed
to be better to trim off all audio unrelated logic to serve
the purpose.

Bug: webrtc:11251
Change-Id: I40e3eba2714c171f7c98b158303a7b3f744ceb78
Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/169462
Reviewed-by: Per Åhgren <peah@webrtc.org>
Reviewed-by: Patrik Höglund <phoglund@webrtc.org>
Reviewed-by: Sebastian Jansson <srte@webrtc.org>
Commit-Queue: Patrik Höglund <phoglund@webrtc.org>
Cr-Commit-Position: refs/heads/master@{#30922}
This commit is contained in:
Tim Na 2020-03-26 17:16:51 -07:00 committed by Commit Bot
parent fa097a2190
commit 8ab3c77c01
6 changed files with 679 additions and 0 deletions

View File

@ -40,6 +40,7 @@ if (!build_with_chromium) {
":rtc_unittests",
":slow_tests",
":video_engine_tests",
":voip_unittests",
":webrtc_nonparallel_tests",
":webrtc_perf_tests",
"common_audio:common_audio_unittests",
@ -673,6 +674,14 @@ if (rtc_include_tests) {
shard_timeout = 900
}
}
rtc_test("voip_unittests") {
testonly = true
deps = [
"audio/voip/test:audio_egress_unittests",
"test:test_main",
]
}
}
# ---- Poisons ----

30
audio/voip/BUILD.gn Normal file
View File

@ -0,0 +1,30 @@
# 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.
import("../../webrtc.gni")
rtc_library("audio_egress") {
sources = [
"audio_egress.cc",
"audio_egress.h",
]
deps = [
"../../api/audio_codecs:audio_codecs_api",
"../../api/task_queue",
"../../audio",
"../../audio/utility:audio_frame_operations",
"../../call:audio_sender_interface",
"../../modules/audio_coding",
"../../modules/rtp_rtcp",
"../../modules/rtp_rtcp:rtp_rtcp_format",
"../../rtc_base:logging",
"../../rtc_base:rtc_task_queue",
"../../rtc_base:thread_checker",
"../../rtc_base:timeutils",
]
}

186
audio/voip/audio_egress.cc Normal file
View File

@ -0,0 +1,186 @@
//
// 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 <utility>
#include <vector>
#include "rtc_base/logging.h"
namespace webrtc {
AudioEgress::AudioEgress(RtpRtcp* rtp_rtcp,
Clock* clock,
TaskQueueFactory* task_queue_factory)
: rtp_rtcp_(rtp_rtcp),
rtp_sender_audio_(clock, rtp_rtcp_->RtpSender()),
audio_coding_(AudioCodingModule::Create(AudioCodingModule::Config())),
encoder_queue_(task_queue_factory->CreateTaskQueue(
"AudioEncoder",
TaskQueueFactory::Priority::NORMAL)) {
audio_coding_->RegisterTransportCallback(this);
}
AudioEgress::~AudioEgress() {
audio_coding_->RegisterTransportCallback(nullptr);
}
bool AudioEgress::IsSending() const {
RTC_DCHECK_RUN_ON(&worker_thread_checker_);
return rtp_rtcp_->SendingMedia();
}
void AudioEgress::SetEncoder(int payload_type,
const SdpAudioFormat& encoder_format,
std::unique_ptr<AudioEncoder> encoder) {
RTC_DCHECK_RUN_ON(&worker_thread_checker_);
RTC_DCHECK_GE(payload_type, 0);
RTC_DCHECK_LE(payload_type, 127);
encoder_format_ = encoder_format;
// The RTP/RTCP module needs to know the RTP timestamp rate (i.e. clockrate)
// as well as some other things, so we collect this info and send it along.
rtp_rtcp_->RegisterSendPayloadFrequency(payload_type,
encoder->RtpTimestampRateHz());
rtp_sender_audio_.RegisterAudioPayload("audio", payload_type,
encoder->RtpTimestampRateHz(),
encoder->NumChannels(), 0);
audio_coding_->SetEncoder(std::move(encoder));
}
absl::optional<SdpAudioFormat> AudioEgress::GetEncoderFormat() const {
RTC_DCHECK_RUN_ON(&worker_thread_checker_);
return encoder_format_;
}
void AudioEgress::StartSend() {
RTC_DCHECK_RUN_ON(&worker_thread_checker_);
rtp_rtcp_->SetSendingMediaStatus(true);
}
void AudioEgress::StopSend() {
RTC_DCHECK_RUN_ON(&worker_thread_checker_);
rtp_rtcp_->SetSendingMediaStatus(false);
}
void AudioEgress::SendAudioData(std::unique_ptr<AudioFrame> audio_frame) {
RTC_DCHECK_GT(audio_frame->samples_per_channel_, 0);
RTC_DCHECK_LE(audio_frame->num_channels_, 8);
encoder_queue_.PostTask(
[this, audio_frame = std::move(audio_frame)]() mutable {
RTC_DCHECK_RUN_ON(&encoder_queue_);
if (!rtp_rtcp_->SendingMedia()) {
return;
}
AudioFrameOperations::Mute(audio_frame.get(),
encoder_context_.previously_muted_,
encoder_context_.mute_);
encoder_context_.previously_muted_ = encoder_context_.mute_;
audio_frame->timestamp_ = encoder_context_.frame_rtp_timestamp_;
// This call will trigger AudioPacketizationCallback::SendData if
// encoding is done and payload is ready for packetization and
// transmission. Otherwise, it will return without invoking the
// callback.
if (audio_coding_->Add10MsData(*audio_frame) < 0) {
RTC_DLOG(LS_ERROR) << "ACM::Add10MsData() failed.";
return;
}
encoder_context_.frame_rtp_timestamp_ +=
rtc::dchecked_cast<uint32_t>(audio_frame->samples_per_channel_);
});
}
int32_t AudioEgress::SendData(AudioFrameType frame_type,
uint8_t payload_type,
uint32_t timestamp,
const uint8_t* payload_data,
size_t payload_size) {
RTC_DCHECK_RUN_ON(&encoder_queue_);
rtc::ArrayView<const uint8_t> payload(payload_data, payload_size);
// Currently we don't get a capture time from downstream modules (ADM,
// AudioTransportImpl).
// TODO(natim@webrtc.org): Integrate once it's ready.
constexpr uint32_t kUndefinedCaptureTime = -1;
// Push data from ACM to RTP/RTCP-module to deliver audio frame for
// packetization.
if (!rtp_rtcp_->OnSendingRtpFrame(timestamp, kUndefinedCaptureTime,
payload_type,
/*force_sender_report=*/false)) {
return -1;
}
const uint32_t rtp_timestamp = timestamp + rtp_rtcp_->StartTimestamp();
// This call will trigger Transport::SendPacket() from the RTP/RTCP module.
if (!rtp_sender_audio_.SendAudio(frame_type, payload_type, rtp_timestamp,
payload.data(), payload.size())) {
RTC_DLOG(LS_ERROR)
<< "AudioEgress::SendData() failed to send data to RTP/RTCP module";
return -1;
}
return 0;
}
void AudioEgress::RegisterTelephoneEventType(int rtp_payload_type,
int sample_rate_hz) {
RTC_DCHECK_RUN_ON(&worker_thread_checker_);
RTC_DCHECK_GE(rtp_payload_type, 0);
RTC_DCHECK_LE(rtp_payload_type, 127);
rtp_rtcp_->RegisterSendPayloadFrequency(rtp_payload_type, sample_rate_hz);
rtp_sender_audio_.RegisterAudioPayload("telephone-event", rtp_payload_type,
sample_rate_hz, 0, 0);
}
bool AudioEgress::SendTelephoneEvent(int dtmf_event, int duration_ms) {
RTC_DCHECK_RUN_ON(&worker_thread_checker_);
RTC_DCHECK_GE(dtmf_event, 0);
RTC_DCHECK_LE(dtmf_event, 255);
RTC_DCHECK_GE(duration_ms, 0);
RTC_DCHECK_LE(duration_ms, 65535);
if (!IsSending()) {
return false;
}
constexpr int kTelephoneEventAttenuationdB = 10;
if (rtp_sender_audio_.SendTelephoneEvent(dtmf_event, duration_ms,
kTelephoneEventAttenuationdB) != 0) {
RTC_DLOG(LS_ERROR) << "SendTelephoneEvent() failed to send event";
return false;
}
return true;
}
void AudioEgress::SetMute(bool mute) {
RTC_DCHECK_RUN_ON(&worker_thread_checker_);
encoder_queue_.PostTask([this, mute] {
RTC_DCHECK_RUN_ON(&encoder_queue_);
encoder_context_.mute_ = mute;
});
}
} // namespace webrtc

137
audio/voip/audio_egress.h Normal file
View File

@ -0,0 +1,137 @@
//
// 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.
//
#ifndef AUDIO_VOIP_AUDIO_EGRESS_H_
#define AUDIO_VOIP_AUDIO_EGRESS_H_
#include <memory>
#include <string>
#include "api/audio_codecs/audio_format.h"
#include "api/task_queue/task_queue_factory.h"
#include "audio/utility/audio_frame_operations.h"
#include "call/audio_sender.h"
#include "modules/audio_coding/include/audio_coding_module.h"
#include "modules/rtp_rtcp/include/report_block_data.h"
#include "modules/rtp_rtcp/include/rtp_rtcp.h"
#include "modules/rtp_rtcp/source/rtp_sender_audio.h"
#include "rtc_base/task_queue.h"
#include "rtc_base/thread_checker.h"
#include "rtc_base/time_utils.h"
namespace webrtc {
// AudioEgress receives input samples from AudioDeviceModule via
// AudioTransportImpl through AudioSender interface. Once it encodes the sample
// via selected encoder through AudioPacketizationCallback interface, the
// encoded payload will be packetized by the RTP stack, resulting in ready to
// send RTP packet to remote endpoint.
//
// This class enforces single worker thread access by caller via SequenceChecker
// in debug mode as expected thread usage pattern. In order to minimize the hold
// on audio input thread from OS, TaskQueue is employed to encode and send RTP
// asynchrounously.
//
// Note that this class is originally based on ChannelSend in
// audio/channel_send.cc with non-audio related logic trimmed as aimed for
// smaller footprint.
class AudioEgress : public AudioSender, public AudioPacketizationCallback {
public:
AudioEgress(RtpRtcp* rtp_rtcp,
Clock* clock,
TaskQueueFactory* task_queue_factory);
~AudioEgress() override;
// Set the encoder format and payload type for AudioCodingModule.
// It's possible to change the encoder type during its active usage.
// |payload_type| must be the type that is negotiated with peer through
// offer/answer.
void SetEncoder(int payload_type,
const SdpAudioFormat& encoder_format,
std::unique_ptr<AudioEncoder> encoder);
// Start or stop sending operation of AudioEgress. This will start/stop
// the RTP stack also causes encoder queue thread to start/stop
// processing input audio samples.
void StartSend();
void StopSend();
// Query the state of the RTP stack. This returns true if StartSend()
// called and false if StopSend() is called.
bool IsSending() const;
// Enable or disable Mute state.
void SetMute(bool mute);
// Retrieve current encoder format info. This returns encoder format set
// by SetEncoder() and if encoder is not set, this will return nullopt.
absl::optional<SdpAudioFormat> GetEncoderFormat() const;
// Register the payload type and sample rate for DTMF (RFC 4733) payload.
void RegisterTelephoneEventType(int rtp_payload_type, int sample_rate_hz);
// Send DTMF named event as specified by
// https://tools.ietf.org/html/rfc4733#section-3.2
// |duration_ms| specifies the duration of DTMF packets that will be emitted
// in place of real RTP packets instead.
// This will return true when requested dtmf event is successfully scheduled
// otherwise false when the dtmf queue reached maximum of 20 events.
bool SendTelephoneEvent(int dtmf_event, int duration_ms);
// Implementation of AudioSender interface.
void SendAudioData(std::unique_ptr<AudioFrame> audio_frame) override;
// Implementation of AudioPacketizationCallback interface.
int32_t SendData(AudioFrameType frame_type,
uint8_t payload_type,
uint32_t timestamp,
const uint8_t* payload_data,
size_t payload_size) override;
private:
// Ensure that single worker thread access.
SequenceChecker worker_thread_checker_;
// Current encoder format selected by caller.
absl::optional<SdpAudioFormat> encoder_format_
RTC_GUARDED_BY(worker_thread_checker_);
// Synchronization is handled internally by RtpRtcp.
RtpRtcp* const rtp_rtcp_;
// Synchronization is handled internally by RTPSenderAudio.
RTPSenderAudio rtp_sender_audio_;
// Synchronization is handled internally by AudioCodingModule.
const std::unique_ptr<AudioCodingModule> audio_coding_;
// Struct that holds all variables used by encoder task queue.
struct EncoderContext {
// Offset used to mark rtp timestamp in sample rate unit in
// newly received audio frame from AudioTransport.
uint32_t frame_rtp_timestamp_ = 0;
// Flag to track mute state from caller. |previously_muted_| is used to
// track previous state as part of input to AudioFrameOperations::Mute
// to implement fading effect when (un)mute is invoked.
bool mute_ = false;
bool previously_muted_ = false;
};
EncoderContext encoder_context_ RTC_GUARDED_BY(encoder_queue_);
// Defined last to ensure that there are no running tasks when the other
// members are destroyed.
rtc::TaskQueue encoder_queue_;
};
} // namespace webrtc
#endif // AUDIO_VOIP_AUDIO_EGRESS_H_

29
audio/voip/test/BUILD.gn Normal file
View File

@ -0,0 +1,29 @@
# 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.
import("../../../webrtc.gni")
if (rtc_include_tests) {
rtc_library("audio_egress_unittests") {
testonly = true
sources = [ "audio_egress_unittest.cc" ]
deps = [
"..:audio_egress",
"../../../api:transport_api",
"../../../api/audio_codecs:builtin_audio_decoder_factory",
"../../../api/audio_codecs:builtin_audio_encoder_factory",
"../../../api/task_queue:default_task_queue_factory",
"../../../modules/audio_mixer:audio_mixer_test_utils",
"../../../modules/rtp_rtcp:rtp_rtcp_format",
"../../../rtc_base:logging",
"../../../rtc_base:rtc_event",
"../../../test:mock_transport",
"../../../test:test_support",
]
}
}

View File

@ -0,0 +1,288 @@
//
// 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 "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<RtpRtcp> CreateRtpStack(Clock* clock,
Transport* transport,
uint32_t remote_ssrc) {
RtpRtcp::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 = RtpRtcp::Create(rtp_config);
rtp_rtcp->SetSendingMediaStatus(false);
rtp_rtcp->SetRTCPStatus(RtcpMode::kCompound);
return rtp_rtcp;
}
// 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 int16_t kAudioLevel = 3004; // Used for sine wave level.
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 {
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<AudioEgress> egress_;
std::unique_ptr<TaskQueueFactory> task_queue_factory_;
std::unique_ptr<RtpRtcp> rtp_rtcp_;
rtc::scoped_refptr<AudioEncoderFactory> encoder_factory_;
};
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);
}
} // namespace
} // namespace webrtc