aboutsummaryrefslogtreecommitdiff
path: root/audio
diff options
context:
space:
mode:
authorTim Na <natim@webrtc.org>2020-04-21 09:39:25 -0700
committerCommit Bot <commit-bot@chromium.org>2020-04-21 20:19:37 +0000
commit11f92bc81b5dc974305b854a9f8fdd2eb0f8763f (patch)
tree312ecd90f39648b5b14b0005280d211fec881906 /audio
parentf2b06ce5c82dee9874917a235358fb371e23fc96 (diff)
downloadwebrtc-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.gn25
-rw-r--r--audio/voip/audio_egress.cc18
-rw-r--r--audio/voip/audio_egress.h18
-rw-r--r--audio/voip/audio_ingress.cc257
-rw-r--r--audio/voip/audio_ingress.h125
-rw-r--r--audio/voip/test/BUILD.gn19
-rw-r--r--audio/voip/test/audio_egress_unittest.cc18
-rw-r--r--audio/voip/test/audio_ingress_unittest.cc182
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