diff options
author | Tim Na <natim@webrtc.org> | 2020-03-26 17:16:51 -0700 |
---|---|---|
committer | Commit Bot <commit-bot@chromium.org> | 2020-03-27 18:45:43 +0000 |
commit | 8ab3c77c0137022e024e71612ef341c558595c71 (patch) | |
tree | 33fa29f4560ac3d07412f7c7ce67f8c2dd854a39 /audio | |
parent | fa097a21905524314dacf49e8a264324618c3071 (diff) | |
download | webrtc-8ab3c77c0137022e024e71612ef341c558595c71.tar.gz |
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}
Diffstat (limited to 'audio')
-rw-r--r-- | audio/voip/BUILD.gn | 30 | ||||
-rw-r--r-- | audio/voip/audio_egress.cc | 186 | ||||
-rw-r--r-- | audio/voip/audio_egress.h | 137 | ||||
-rw-r--r-- | audio/voip/test/BUILD.gn | 29 | ||||
-rw-r--r-- | audio/voip/test/audio_egress_unittest.cc | 288 |
5 files changed, 670 insertions, 0 deletions
diff --git a/audio/voip/BUILD.gn b/audio/voip/BUILD.gn new file mode 100644 index 0000000000..9d52121144 --- /dev/null +++ b/audio/voip/BUILD.gn @@ -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", + ] +} diff --git a/audio/voip/audio_egress.cc b/audio/voip/audio_egress.cc new file mode 100644 index 0000000000..c145201c00 --- /dev/null +++ b/audio/voip/audio_egress.cc @@ -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 diff --git a/audio/voip/audio_egress.h b/audio/voip/audio_egress.h new file mode 100644 index 0000000000..fcdafa640b --- /dev/null +++ b/audio/voip/audio_egress.h @@ -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_ diff --git a/audio/voip/test/BUILD.gn b/audio/voip/test/BUILD.gn new file mode 100644 index 0000000000..e0aedf673e --- /dev/null +++ b/audio/voip/test/BUILD.gn @@ -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", + ] + } +} diff --git a/audio/voip/test/audio_egress_unittest.cc b/audio/voip/test/audio_egress_unittest.cc new file mode 100644 index 0000000000..23c4e45b0c --- /dev/null +++ b/audio/voip/test/audio_egress_unittest.cc @@ -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 |