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}
This commit is contained in:
Tim Na
2020-04-21 09:39:25 -07:00
committed by Commit Bot
parent f2b06ce5c8
commit 11f92bc81b
9 changed files with 635 additions and 28 deletions

View File

@ -684,6 +684,7 @@ if (rtc_include_tests) {
testonly = true
deps = [
"audio/voip/test:audio_egress_unittests",
"audio/voip/test:audio_ingress_unittests",
"test:test_main",
]
}

View File

@ -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",

View File

@ -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"

View File

@ -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_

257
audio/voip/audio_ingress.cc Normal file
View File

@ -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

125
audio/voip/audio_ingress.h Normal file
View File

@ -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_

View File

@ -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",

View File

@ -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"

View File

@ -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