diff options
author | Tim Na <natim@webrtc.org> | 2020-04-21 09:39:25 -0700 |
---|---|---|
committer | Commit Bot <commit-bot@chromium.org> | 2020-04-21 20:19:37 +0000 |
commit | 11f92bc81b5dc974305b854a9f8fdd2eb0f8763f (patch) | |
tree | 312ecd90f39648b5b14b0005280d211fec881906 /audio | |
parent | f2b06ce5c82dee9874917a235358fb371e23fc96 (diff) | |
download | webrtc-11f92bc81b5dc974305b854a9f8fdd2eb0f8763f.tar.gz |
Audio ingress implementation for voip api.
This is based on channel_receive.cc implementation where non-audio
related logics are trimmed off for smaller footprint in size.
Bug: webrtc:11251
Change-Id: I743c9f93f509fa6fcc12981fa73a6f01ce38348c
Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/172821
Commit-Queue: Tim Na <natim@webrtc.org>
Reviewed-by: Per Ã…hgren <peah@webrtc.org>
Reviewed-by: Mirko Bonadei <mbonadei@webrtc.org>
Cr-Commit-Position: refs/heads/master@{#31117}
Diffstat (limited to 'audio')
-rw-r--r-- | audio/voip/BUILD.gn | 25 | ||||
-rw-r--r-- | audio/voip/audio_egress.cc | 18 | ||||
-rw-r--r-- | audio/voip/audio_egress.h | 18 | ||||
-rw-r--r-- | audio/voip/audio_ingress.cc | 257 | ||||
-rw-r--r-- | audio/voip/audio_ingress.h | 125 | ||||
-rw-r--r-- | audio/voip/test/BUILD.gn | 19 | ||||
-rw-r--r-- | audio/voip/test/audio_egress_unittest.cc | 18 | ||||
-rw-r--r-- | audio/voip/test/audio_ingress_unittest.cc | 182 |
8 files changed, 634 insertions, 28 deletions
diff --git a/audio/voip/BUILD.gn b/audio/voip/BUILD.gn index 9d52121144..8ebc3ce4e7 100644 --- a/audio/voip/BUILD.gn +++ b/audio/voip/BUILD.gn @@ -8,6 +8,31 @@ import("../../webrtc.gni") +rtc_library("audio_ingress") { + sources = [ + "audio_ingress.cc", + "audio_ingress.h", + ] + deps = [ + "../../api:array_view", + "../../api:rtp_headers", + "../../api:scoped_refptr", + "../../api:transport_api", + "../../api/audio:audio_mixer_api", + "../../api/audio_codecs:audio_codecs_api", + "../../audio", + "../../audio/utility:audio_frame_operations", + "../../modules/audio_coding", + "../../modules/rtp_rtcp", + "../../modules/rtp_rtcp:rtp_rtcp_format", + "../../modules/utility", + "../../rtc_base:criticalsection", + "../../rtc_base:logging", + "../../rtc_base:safe_minmax", + "../../rtc_base:timeutils", + ] +} + rtc_library("audio_egress") { sources = [ "audio_egress.cc", diff --git a/audio/voip/audio_egress.cc b/audio/voip/audio_egress.cc index c145201c00..98f73fa37f 100644 --- a/audio/voip/audio_egress.cc +++ b/audio/voip/audio_egress.cc @@ -1,12 +1,12 @@ -// -// 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. -// +/* + * 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" diff --git a/audio/voip/audio_egress.h b/audio/voip/audio_egress.h index fcdafa640b..192d5ff839 100644 --- a/audio/voip/audio_egress.h +++ b/audio/voip/audio_egress.h @@ -1,12 +1,12 @@ -// -// 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. -// +/* + * 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_ diff --git a/audio/voip/audio_ingress.cc b/audio/voip/audio_ingress.cc new file mode 100644 index 0000000000..aae684278a --- /dev/null +++ b/audio/voip/audio_ingress.cc @@ -0,0 +1,257 @@ +/* + * 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_ingress.h" + +#include <algorithm> +#include <utility> +#include <vector> + +#include "api/audio_codecs/audio_format.h" +#include "audio/utility/audio_frame_operations.h" +#include "modules/audio_coding/include/audio_coding_module.h" +#include "rtc_base/critical_section.h" +#include "rtc_base/logging.h" +#include "rtc_base/numerics/safe_minmax.h" + +namespace webrtc { + +namespace { + +AudioCodingModule::Config CreateAcmConfig( + rtc::scoped_refptr<AudioDecoderFactory> decoder_factory) { + AudioCodingModule::Config acm_config; + acm_config.neteq_config.enable_muted_state = true; + acm_config.decoder_factory = decoder_factory; + return acm_config; +} + +} // namespace + +AudioIngress::AudioIngress( + RtpRtcp* rtp_rtcp, + Clock* clock, + rtc::scoped_refptr<AudioDecoderFactory> decoder_factory, + std::unique_ptr<ReceiveStatistics> receive_statistics) + : playing_(false), + remote_ssrc_(0), + first_rtp_timestamp_(-1), + rtp_receive_statistics_(std::move(receive_statistics)), + rtp_rtcp_(rtp_rtcp), + acm_receiver_(CreateAcmConfig(decoder_factory)), + ntp_estimator_(clock) {} + +AudioIngress::~AudioIngress() = default; + +void AudioIngress::StartPlay() { + playing_ = true; +} + +void AudioIngress::StopPlay() { + playing_ = false; + output_audio_level_.ResetLevelFullRange(); +} + +AudioMixer::Source::AudioFrameInfo AudioIngress::GetAudioFrameWithInfo( + int sampling_rate, + AudioFrame* audio_frame) { + audio_frame->sample_rate_hz_ = sampling_rate; + + // Get 10ms raw PCM data from the ACM. + bool muted = false; + if (acm_receiver_.GetAudio(sampling_rate, audio_frame, &muted) == -1) { + RTC_DLOG(LS_ERROR) << "GetAudio() failed!"; + // In all likelihood, the audio in this frame is garbage. We return an + // error so that the audio mixer module doesn't add it to the mix. As + // a result, it won't be played out and the actions skipped here are + // irrelevant. + return AudioMixer::Source::AudioFrameInfo::kError; + } + + if (muted) { + AudioFrameOperations::Mute(audio_frame); + } + + // Measure audio level. + constexpr double kAudioSampleDurationSeconds = 0.01; + output_audio_level_.ComputeLevel(*audio_frame, kAudioSampleDurationSeconds); + + // Set first rtp timestamp with first audio frame with valid timestamp. + if (first_rtp_timestamp_ < 0 && audio_frame->timestamp_ != 0) { + first_rtp_timestamp_ = audio_frame->timestamp_; + } + + if (first_rtp_timestamp_ >= 0) { + // Compute elapsed and NTP times. + int64_t unwrap_timestamp; + { + rtc::CritScope lock(&lock_); + unwrap_timestamp = + timestamp_wrap_handler_.Unwrap(audio_frame->timestamp_); + audio_frame->ntp_time_ms_ = + ntp_estimator_.Estimate(audio_frame->timestamp_); + } + // For clock rate, default to the playout sampling rate if we haven't + // received any packets yet. + absl::optional<std::pair<int, SdpAudioFormat>> decoder = + acm_receiver_.LastDecoder(); + int clock_rate = decoder ? decoder->second.clockrate_hz + : acm_receiver_.last_output_sample_rate_hz(); + RTC_DCHECK_GT(clock_rate, 0); + audio_frame->elapsed_time_ms_ = + (unwrap_timestamp - first_rtp_timestamp_) / (clock_rate / 1000); + } + + return muted ? AudioMixer::Source::AudioFrameInfo::kMuted + : AudioMixer::Source::AudioFrameInfo::kNormal; +} + +int AudioIngress::Ssrc() const { + return rtc::dchecked_cast<int>(remote_ssrc_.load()); +} + +int AudioIngress::PreferredSampleRate() const { + // Return the bigger of playout and receive frequency in the ACM. Note that + // return 0 means anything higher shouldn't cause any quality loss. + return std::max(acm_receiver_.last_packet_sample_rate_hz().value_or(0), + acm_receiver_.last_output_sample_rate_hz()); +} + +void AudioIngress::SetReceiveCodecs( + const std::map<int, SdpAudioFormat>& codecs) { + { + rtc::CritScope lock(&lock_); + for (const auto& kv : codecs) { + receive_codec_info_[kv.first] = kv.second.clockrate_hz; + } + } + acm_receiver_.SetCodecs(codecs); +} + +void AudioIngress::ReceivedRTPPacket(const uint8_t* data, size_t length) { + if (!Playing()) { + return; + } + + RtpPacketReceived rtp_packet; + rtp_packet.Parse(data, length); + + // Set payload type's sampling rate before we feed it into ReceiveStatistics. + { + rtc::CritScope lock(&lock_); + const auto& it = receive_codec_info_.find(rtp_packet.PayloadType()); + // If sampling rate info is not available in our received codec set, it + // would mean that remote media endpoint is sending incorrect payload id + // which can't be processed correctly especially on payload type id in + // dynamic range. + if (it == receive_codec_info_.end()) { + RTC_DLOG(LS_WARNING) << "Unexpected payload id received: " + << rtp_packet.PayloadType(); + return; + } + rtp_packet.set_payload_type_frequency(it->second); + } + + rtp_receive_statistics_->OnRtpPacket(rtp_packet); + + RTPHeader header; + rtp_packet.GetHeader(&header); + + size_t packet_length = rtp_packet.size(); + if (packet_length < header.headerLength || + (packet_length - header.headerLength) < header.paddingLength) { + RTC_DLOG(LS_ERROR) << "Packet length(" << packet_length << ") header(" + << header.headerLength << ") padding(" + << header.paddingLength << ")"; + return; + } + + const uint8_t* payload = rtp_packet.data() + header.headerLength; + size_t payload_length = packet_length - header.headerLength; + size_t payload_data_length = payload_length - header.paddingLength; + auto data_view = rtc::ArrayView<const uint8_t>(payload, payload_data_length); + + // Push the incoming payload (parsed and ready for decoding) into the ACM. + if (acm_receiver_.InsertPacket(header, data_view) != 0) { + RTC_DLOG(LS_ERROR) << "AudioIngress::ReceivedRTPPacket() unable to " + "push data to the ACM"; + } +} + +void AudioIngress::ReceivedRTCPPacket(const uint8_t* data, size_t length) { + // Deliver RTCP packet to RTP/RTCP module for parsing + rtp_rtcp_->IncomingRtcpPacket(data, length); + + int64_t rtt = GetRoundTripTime(); + if (rtt == -1) { + // Waiting for valid RTT. + return; + } + + uint32_t ntp_secs = 0, ntp_frac = 0, rtp_timestamp = 0; + if (rtp_rtcp_->RemoteNTP(&ntp_secs, &ntp_frac, nullptr, nullptr, + &rtp_timestamp) != 0) { + // Waiting for RTCP. + return; + } + + { + rtc::CritScope lock(&lock_); + ntp_estimator_.UpdateRtcpTimestamp(rtt, ntp_secs, ntp_frac, rtp_timestamp); + } +} + +int64_t AudioIngress::GetRoundTripTime() { + const std::vector<ReportBlockData>& report_data = + rtp_rtcp_->GetLatestReportBlockData(); + + // If we do not have report block which means remote RTCP hasn't be received + // yet, return -1 as to indicate uninitialized value. + if (report_data.empty()) { + return -1; + } + + // We don't know in advance the remote SSRC used by the other end's receiver + // reports, so use the SSRC of the first report block as remote SSRC for now. + // TODO(natim@webrtc.org): handle the case where remote end is changing ssrc + // and update accordingly here. + const ReportBlockData& block_data = report_data[0]; + + const uint32_t sender_ssrc = block_data.report_block().sender_ssrc; + + if (sender_ssrc != remote_ssrc_.load()) { + remote_ssrc_.store(sender_ssrc); + rtp_rtcp_->SetRemoteSSRC(sender_ssrc); + } + + return (block_data.has_rtt() ? block_data.last_rtt_ms() : -1); +} + +int AudioIngress::GetSpeechOutputLevelFullRange() const { + return output_audio_level_.LevelFullRange(); +} + +bool AudioIngress::Playing() const { + return playing_; +} + +NetworkStatistics AudioIngress::GetNetworkStatistics() const { + NetworkStatistics stats; + acm_receiver_.GetNetworkStatistics(&stats); + return stats; +} + +AudioDecodingCallStats AudioIngress::GetDecodingStatistics() const { + AudioDecodingCallStats stats; + acm_receiver_.GetDecodingCallStatistics(&stats); + return stats; +} + +} // namespace webrtc diff --git a/audio/voip/audio_ingress.h b/audio/voip/audio_ingress.h new file mode 100644 index 0000000000..f703440d27 --- /dev/null +++ b/audio/voip/audio_ingress.h @@ -0,0 +1,125 @@ +/* + * 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_INGRESS_H_ +#define AUDIO_VOIP_AUDIO_INGRESS_H_ + +#include <atomic> +#include <map> +#include <memory> +#include <utility> + +#include "api/array_view.h" +#include "api/audio/audio_mixer.h" +#include "api/rtp_headers.h" +#include "api/scoped_refptr.h" +#include "audio/audio_level.h" +#include "modules/audio_coding/acm2/acm_receiver.h" +#include "modules/audio_coding/include/audio_coding_module.h" +#include "modules/rtp_rtcp/include/receive_statistics.h" +#include "modules/rtp_rtcp/include/remote_ntp_time_estimator.h" +#include "modules/rtp_rtcp/include/rtp_rtcp.h" +#include "modules/rtp_rtcp/source/rtp_packet_received.h" +#include "rtc_base/critical_section.h" +#include "rtc_base/time_utils.h" + +namespace webrtc { + +// AudioIngress handles incoming RTP/RTCP packets from the remote +// media endpoint. Received RTP packets are injected into AcmReceiver and +// when audio output thread requests for audio samples to play through system +// output such as speaker device, AudioIngress provides the samples via its +// implementation on AudioMixer::Source interface. +// +// Note that this class is originally based on ChannelReceive in +// audio/channel_receive.cc with non-audio related logic trimmed as aimed for +// smaller footprint. +class AudioIngress : public AudioMixer::Source { + public: + AudioIngress(RtpRtcp* rtp_rtcp, + Clock* clock, + rtc::scoped_refptr<AudioDecoderFactory> decoder_factory, + std::unique_ptr<ReceiveStatistics> receive_statistics); + ~AudioIngress() override; + + // Start or stop receiving operation of AudioIngress. + void StartPlay(); + void StopPlay(); + + // Query the state of the AudioIngress. + bool Playing() const; + + // Set the decoder formats and payload type for AcmReceiver where the + // key type (int) of the map is the payload type of SdpAudioFormat. + void SetReceiveCodecs(const std::map<int, SdpAudioFormat>& codecs); + + // APIs to handle received RTP/RTCP packets from caller. + void ReceivedRTPPacket(const uint8_t* data, size_t length); + void ReceivedRTCPPacket(const uint8_t* data, size_t length); + + // Retrieve highest speech output level in last 100 ms. Note that + // this isn't RMS but absolute raw audio level on int16_t sample unit. + // Therefore, the return value will vary between 0 ~ 0xFFFF. This type of + // value may be useful to be used for measuring active speaker gauge. + int GetSpeechOutputLevelFullRange() const; + + // Returns network round trip time (RTT) measued by RTCP exchange with + // remote media endpoint. RTT value -1 indicates that it's not initialized. + int64_t GetRoundTripTime(); + + NetworkStatistics GetNetworkStatistics() const; + AudioDecodingCallStats GetDecodingStatistics() const; + + // Implementation of AudioMixer::Source interface. + AudioMixer::Source::AudioFrameInfo GetAudioFrameWithInfo( + int sampling_rate, + AudioFrame* audio_frame) override; + int Ssrc() const override; + int PreferredSampleRate() const override; + + private: + // Indicate AudioIngress status as caller invokes Start/StopPlaying. + // If not playing, incoming RTP data processing is skipped, thus + // producing no data to output device. + std::atomic<bool> playing_; + + // Currently active remote ssrc from remote media endpoint. + std::atomic<uint32_t> remote_ssrc_; + + // The first rtp timestamp of the output audio frame that is used to + // calculate elasped time for subsequent audio frames. + std::atomic<int64_t> first_rtp_timestamp_; + + // Synchronizaton is handled internally by ReceiveStatistics. + const std::unique_ptr<ReceiveStatistics> rtp_receive_statistics_; + + // Synchronizaton is handled internally by RtpRtcp. + RtpRtcp* const rtp_rtcp_; + + // Synchronizaton is handled internally by acm2::AcmReceiver. + acm2::AcmReceiver acm_receiver_; + + // Synchronizaton is handled internally by voe::AudioLevel. + voe::AudioLevel output_audio_level_; + + rtc::CriticalSection lock_; + + RemoteNtpTimeEstimator ntp_estimator_ RTC_GUARDED_BY(lock_); + + // For receiving RTP statistics, this tracks the sampling rate value + // per payload type set when caller set via SetReceiveCodecs. + std::map<int, int> receive_codec_info_ RTC_GUARDED_BY(lock_); + + rtc::TimestampWrapAroundHandler timestamp_wrap_handler_ RTC_GUARDED_BY(lock_); +}; + +} // namespace webrtc + +#endif // AUDIO_VOIP_AUDIO_INGRESS_H_ diff --git a/audio/voip/test/BUILD.gn b/audio/voip/test/BUILD.gn index e0aedf673e..0decdb2886 100644 --- a/audio/voip/test/BUILD.gn +++ b/audio/voip/test/BUILD.gn @@ -9,13 +9,30 @@ import("../../../webrtc.gni") if (rtc_include_tests) { + rtc_library("audio_ingress_unittests") { + testonly = true + sources = [ "audio_ingress_unittest.cc" ] + deps = [ + "..:audio_egress", + "..:audio_ingress", + "../../../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", + "../../../rtc_base:logging", + "../../../rtc_base:rtc_event", + "../../../test:mock_transport", + "../../../test:test_support", + ] + } + 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", diff --git a/audio/voip/test/audio_egress_unittest.cc b/audio/voip/test/audio_egress_unittest.cc index 23c4e45b0c..a7e3d65eab 100644 --- a/audio/voip/test/audio_egress_unittest.cc +++ b/audio/voip/test/audio_egress_unittest.cc @@ -1,12 +1,12 @@ -// -// 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. -// +/* + * 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" diff --git a/audio/voip/test/audio_ingress_unittest.cc b/audio/voip/test/audio_ingress_unittest.cc new file mode 100644 index 0000000000..752c06c749 --- /dev/null +++ b/audio/voip/test/audio_ingress_unittest.cc @@ -0,0 +1,182 @@ +/* + * 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_ingress.h" +#include "api/audio_codecs/builtin_audio_decoder_factory.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 "audio/voip/audio_egress.h" +#include "modules/audio_mixer/sine_wave_generator.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; + +constexpr int16_t kAudioLevel = 3004; // Used for sine wave level. + +std::unique_ptr<RtpRtcp> CreateRtpStack(Clock* clock, Transport* transport) { + 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 = 0xdeadc0de; + auto rtp_rtcp = RtpRtcp::Create(rtp_config); + rtp_rtcp->SetSendingMediaStatus(false); + rtp_rtcp->SetRTCPStatus(RtcpMode::kCompound); + return rtp_rtcp; +} + +class AudioIngressTest : public ::testing::Test { + public: + const SdpAudioFormat kPcmuFormat = {"pcmu", 8000, 1}; + + AudioIngressTest() + : fake_clock_(123456789), wave_generator_(1000.0, kAudioLevel) { + rtp_rtcp_ = CreateRtpStack(&fake_clock_, &transport_); + task_queue_factory_ = CreateDefaultTaskQueueFactory(); + encoder_factory_ = CreateBuiltinAudioEncoderFactory(); + decoder_factory_ = CreateBuiltinAudioDecoderFactory(); + } + + void SetUp() override { + constexpr int kPcmuPayload = 0; + ingress_ = std::make_unique<AudioIngress>( + rtp_rtcp_.get(), &fake_clock_, decoder_factory_, + ReceiveStatistics::Create(&fake_clock_)); + ingress_->SetReceiveCodecs({{kPcmuPayload, kPcmuFormat}}); + + egress_ = std::make_unique<AudioEgress>(rtp_rtcp_.get(), &fake_clock_, + task_queue_factory_.get()); + egress_->SetEncoder(kPcmuPayload, kPcmuFormat, + encoder_factory_->MakeAudioEncoder( + kPcmuPayload, kPcmuFormat, absl::nullopt)); + egress_->StartSend(); + ingress_->StartPlay(); + rtp_rtcp_->SetSendingStatus(true); + } + + void TearDown() override { + rtp_rtcp_->SetSendingStatus(false); + ingress_->StopPlay(); + egress_->StopSend(); + } + + 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 fake_clock_; + SineWaveGenerator wave_generator_; + NiceMock<MockTransport> transport_; + std::unique_ptr<AudioIngress> ingress_; + rtc::scoped_refptr<AudioDecoderFactory> decoder_factory_; + // Members used to drive the input to ingress. + std::unique_ptr<AudioEgress> egress_; + std::unique_ptr<TaskQueueFactory> task_queue_factory_; + std::shared_ptr<RtpRtcp> rtp_rtcp_; + rtc::scoped_refptr<AudioEncoderFactory> encoder_factory_; +}; + +TEST_F(AudioIngressTest, PlayingAfterStartAndStop) { + EXPECT_EQ(ingress_->Playing(), true); + ingress_->StopPlay(); + EXPECT_EQ(ingress_->Playing(), false); +} + +TEST_F(AudioIngressTest, GetAudioFrameAfterRtpReceived) { + rtc::Event event; + auto handle_rtp = [&](const uint8_t* packet, size_t length, Unused) { + ingress_->ReceivedRTPPacket(packet, length); + event.Set(); + return true; + }; + EXPECT_CALL(transport_, SendRtp).WillRepeatedly(Invoke(handle_rtp)); + egress_->SendAudioData(GetAudioFrame(0)); + egress_->SendAudioData(GetAudioFrame(1)); + event.Wait(/*ms=*/1000); + + AudioFrame audio_frame; + EXPECT_EQ( + ingress_->GetAudioFrameWithInfo(kPcmuFormat.clockrate_hz, &audio_frame), + AudioMixer::Source::AudioFrameInfo::kNormal); + EXPECT_FALSE(audio_frame.muted()); + EXPECT_EQ(audio_frame.num_channels_, 1u); + EXPECT_EQ(audio_frame.samples_per_channel_, + static_cast<size_t>(kPcmuFormat.clockrate_hz / 100)); + EXPECT_EQ(audio_frame.sample_rate_hz_, kPcmuFormat.clockrate_hz); + EXPECT_NE(audio_frame.timestamp_, 0u); + EXPECT_EQ(audio_frame.elapsed_time_ms_, 0); +} + +TEST_F(AudioIngressTest, GetSpeechOutputLevelFullRange) { + // Per audio_level's kUpdateFrequency, we need 11 RTP to get audio level. + constexpr int kNumRtp = 11; + int rtp_count = 0; + rtc::Event event; + auto handle_rtp = [&](const uint8_t* packet, size_t length, Unused) { + ingress_->ReceivedRTPPacket(packet, length); + if (++rtp_count == kNumRtp) { + event.Set(); + } + return true; + }; + EXPECT_CALL(transport_, SendRtp).WillRepeatedly(Invoke(handle_rtp)); + for (int i = 0; i < kNumRtp * 2; i++) { + egress_->SendAudioData(GetAudioFrame(i)); + fake_clock_.AdvanceTimeMilliseconds(10); + } + event.Wait(/*ms=*/1000); + + for (int i = 0; i < kNumRtp; ++i) { + AudioFrame audio_frame; + EXPECT_EQ( + ingress_->GetAudioFrameWithInfo(kPcmuFormat.clockrate_hz, &audio_frame), + AudioMixer::Source::AudioFrameInfo::kNormal); + } + EXPECT_EQ(ingress_->GetSpeechOutputLevelFullRange(), kAudioLevel); +} + +TEST_F(AudioIngressTest, PreferredSampleRate) { + rtc::Event event; + auto handle_rtp = [&](const uint8_t* packet, size_t length, Unused) { + ingress_->ReceivedRTPPacket(packet, length); + event.Set(); + return true; + }; + EXPECT_CALL(transport_, SendRtp).WillRepeatedly(Invoke(handle_rtp)); + egress_->SendAudioData(GetAudioFrame(0)); + egress_->SendAudioData(GetAudioFrame(1)); + event.Wait(/*ms=*/1000); + + AudioFrame audio_frame; + EXPECT_EQ( + ingress_->GetAudioFrameWithInfo(kPcmuFormat.clockrate_hz, &audio_frame), + AudioMixer::Source::AudioFrameInfo::kNormal); + EXPECT_EQ(ingress_->PreferredSampleRate(), kPcmuFormat.clockrate_hz); +} + +} // namespace +} // namespace webrtc |