|  | /* | 
|  | *  Copyright (c) 2015 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/audio_send_stream.h" | 
|  |  | 
|  | #include <memory> | 
|  | #include <string> | 
|  | #include <thread> | 
|  | #include <utility> | 
|  | #include <vector> | 
|  |  | 
|  | #include "api/task_queue/default_task_queue_factory.h" | 
|  | #include "api/test/mock_frame_encryptor.h" | 
|  | #include "audio/audio_state.h" | 
|  | #include "audio/conversion.h" | 
|  | #include "audio/mock_voe_channel_proxy.h" | 
|  | #include "call/test/mock_rtp_transport_controller_send.h" | 
|  | #include "logging/rtc_event_log/mock/mock_rtc_event_log.h" | 
|  | #include "modules/audio_device/include/mock_audio_device.h" | 
|  | #include "modules/audio_mixer/audio_mixer_impl.h" | 
|  | #include "modules/audio_mixer/sine_wave_generator.h" | 
|  | #include "modules/audio_processing/include/audio_processing_statistics.h" | 
|  | #include "modules/audio_processing/include/mock_audio_processing.h" | 
|  | #include "modules/rtp_rtcp/mocks/mock_rtcp_bandwidth_observer.h" | 
|  | #include "modules/rtp_rtcp/mocks/mock_rtcp_rtt_stats.h" | 
|  | #include "modules/rtp_rtcp/mocks/mock_rtp_rtcp.h" | 
|  | #include "rtc_base/task_queue_for_test.h" | 
|  | #include "system_wrappers/include/clock.h" | 
|  | #include "test/field_trial.h" | 
|  | #include "test/gtest.h" | 
|  | #include "test/mock_audio_encoder.h" | 
|  | #include "test/mock_audio_encoder_factory.h" | 
|  |  | 
|  | namespace webrtc { | 
|  | namespace test { | 
|  | namespace { | 
|  |  | 
|  | using ::testing::_; | 
|  | using ::testing::AnyNumber; | 
|  | using ::testing::Eq; | 
|  | using ::testing::Field; | 
|  | using ::testing::Invoke; | 
|  | using ::testing::Ne; | 
|  | using ::testing::Return; | 
|  | using ::testing::StrEq; | 
|  |  | 
|  | static const float kTolerance = 0.0001f; | 
|  |  | 
|  | const uint32_t kSsrc = 1234; | 
|  | const char* kCName = "foo_name"; | 
|  | const int kAudioLevelId = 2; | 
|  | const int kTransportSequenceNumberId = 4; | 
|  | const int32_t kEchoDelayMedian = 254; | 
|  | const int32_t kEchoDelayStdDev = -3; | 
|  | const double kDivergentFilterFraction = 0.2f; | 
|  | const double kEchoReturnLoss = -65; | 
|  | const double kEchoReturnLossEnhancement = 101; | 
|  | const double kResidualEchoLikelihood = -1.0f; | 
|  | const double kResidualEchoLikelihoodMax = 23.0f; | 
|  | const CallSendStatistics kCallStats = {112, 12, 13456, 17890}; | 
|  | const ReportBlock kReportBlock = {456, 780, 123, 567, 890, 132, 143, 13354}; | 
|  | const int kTelephoneEventPayloadType = 123; | 
|  | const int kTelephoneEventPayloadFrequency = 65432; | 
|  | const int kTelephoneEventCode = 45; | 
|  | const int kTelephoneEventDuration = 6789; | 
|  | constexpr int kIsacPayloadType = 103; | 
|  | const SdpAudioFormat kIsacFormat = {"isac", 16000, 1}; | 
|  | const SdpAudioFormat kOpusFormat = {"opus", 48000, 2}; | 
|  | const SdpAudioFormat kG722Format = {"g722", 8000, 1}; | 
|  | const AudioCodecSpec kCodecSpecs[] = { | 
|  | {kIsacFormat, {16000, 1, 32000, 10000, 32000}}, | 
|  | {kOpusFormat, {48000, 1, 32000, 6000, 510000}}, | 
|  | {kG722Format, {16000, 1, 64000}}}; | 
|  |  | 
|  | // TODO(dklee): This mirrors calculation in audio_send_stream.cc, which | 
|  | // should be made more precise in the future. This can be changed when that | 
|  | // logic is more accurate. | 
|  | const DataSize kOverheadPerPacket = DataSize::bytes(20 + 8 + 10 + 12); | 
|  | const TimeDelta kMinFrameLength = TimeDelta::ms(20); | 
|  | const TimeDelta kMaxFrameLength = TimeDelta::ms(120); | 
|  | const DataRate kMinOverheadRate = kOverheadPerPacket / kMaxFrameLength; | 
|  | const DataRate kMaxOverheadRate = kOverheadPerPacket / kMinFrameLength; | 
|  |  | 
|  | class MockLimitObserver : public BitrateAllocator::LimitObserver { | 
|  | public: | 
|  | MOCK_METHOD1(OnAllocationLimitsChanged, void(BitrateAllocationLimits)); | 
|  | }; | 
|  |  | 
|  | std::unique_ptr<MockAudioEncoder> SetupAudioEncoderMock( | 
|  | int payload_type, | 
|  | const SdpAudioFormat& format) { | 
|  | for (const auto& spec : kCodecSpecs) { | 
|  | if (format == spec.format) { | 
|  | std::unique_ptr<MockAudioEncoder> encoder( | 
|  | new ::testing::NiceMock<MockAudioEncoder>()); | 
|  | ON_CALL(*encoder.get(), SampleRateHz()) | 
|  | .WillByDefault(Return(spec.info.sample_rate_hz)); | 
|  | ON_CALL(*encoder.get(), NumChannels()) | 
|  | .WillByDefault(Return(spec.info.num_channels)); | 
|  | ON_CALL(*encoder.get(), RtpTimestampRateHz()) | 
|  | .WillByDefault(Return(spec.format.clockrate_hz)); | 
|  | ON_CALL(*encoder.get(), GetFrameLengthRange()) | 
|  | .WillByDefault(Return(absl::optional<std::pair<TimeDelta, TimeDelta>>{ | 
|  | {TimeDelta::ms(20), TimeDelta::ms(120)}})); | 
|  | return encoder; | 
|  | } | 
|  | } | 
|  | return nullptr; | 
|  | } | 
|  |  | 
|  | rtc::scoped_refptr<MockAudioEncoderFactory> SetupEncoderFactoryMock() { | 
|  | rtc::scoped_refptr<MockAudioEncoderFactory> factory = | 
|  | new rtc::RefCountedObject<MockAudioEncoderFactory>(); | 
|  | ON_CALL(*factory.get(), GetSupportedEncoders()) | 
|  | .WillByDefault(Return(std::vector<AudioCodecSpec>( | 
|  | std::begin(kCodecSpecs), std::end(kCodecSpecs)))); | 
|  | ON_CALL(*factory.get(), QueryAudioEncoder(_)) | 
|  | .WillByDefault(Invoke( | 
|  | [](const SdpAudioFormat& format) -> absl::optional<AudioCodecInfo> { | 
|  | for (const auto& spec : kCodecSpecs) { | 
|  | if (format == spec.format) { | 
|  | return spec.info; | 
|  | } | 
|  | } | 
|  | return absl::nullopt; | 
|  | })); | 
|  | ON_CALL(*factory.get(), MakeAudioEncoderMock(_, _, _, _)) | 
|  | .WillByDefault(Invoke([](int payload_type, const SdpAudioFormat& format, | 
|  | absl::optional<AudioCodecPairId> codec_pair_id, | 
|  | std::unique_ptr<AudioEncoder>* return_value) { | 
|  | *return_value = SetupAudioEncoderMock(payload_type, format); | 
|  | })); | 
|  | return factory; | 
|  | } | 
|  |  | 
|  | struct ConfigHelper { | 
|  | ConfigHelper(bool audio_bwe_enabled, bool expect_set_encoder_call) | 
|  | : clock_(1000000), | 
|  | task_queue_factory_(CreateDefaultTaskQueueFactory()), | 
|  | stream_config_(/*send_transport=*/nullptr), | 
|  | audio_processing_(new rtc::RefCountedObject<MockAudioProcessing>()), | 
|  | bitrate_allocator_(&limit_observer_), | 
|  | worker_queue_(task_queue_factory_->CreateTaskQueue( | 
|  | "ConfigHelper_worker_queue", | 
|  | TaskQueueFactory::Priority::NORMAL)), | 
|  | audio_encoder_(nullptr) { | 
|  | using ::testing::Invoke; | 
|  |  | 
|  | AudioState::Config config; | 
|  | config.audio_mixer = AudioMixerImpl::Create(); | 
|  | config.audio_processing = audio_processing_; | 
|  | config.audio_device_module = | 
|  | new rtc::RefCountedObject<MockAudioDeviceModule>(); | 
|  | audio_state_ = AudioState::Create(config); | 
|  |  | 
|  | SetupDefaultChannelSend(audio_bwe_enabled); | 
|  | SetupMockForSetupSendCodec(expect_set_encoder_call); | 
|  |  | 
|  | // Use ISAC as default codec so as to prevent unnecessary |channel_proxy_| | 
|  | // calls from the default ctor behavior. | 
|  | stream_config_.send_codec_spec = | 
|  | AudioSendStream::Config::SendCodecSpec(kIsacPayloadType, kIsacFormat); | 
|  | stream_config_.rtp.ssrc = kSsrc; | 
|  | stream_config_.rtp.c_name = kCName; | 
|  | stream_config_.rtp.extensions.push_back( | 
|  | RtpExtension(RtpExtension::kAudioLevelUri, kAudioLevelId)); | 
|  | if (audio_bwe_enabled) { | 
|  | AddBweToConfig(&stream_config_); | 
|  | } | 
|  | stream_config_.encoder_factory = SetupEncoderFactoryMock(); | 
|  | stream_config_.min_bitrate_bps = 10000; | 
|  | stream_config_.max_bitrate_bps = 65000; | 
|  | } | 
|  |  | 
|  | std::unique_ptr<internal::AudioSendStream> CreateAudioSendStream() { | 
|  | EXPECT_CALL(rtp_transport_, GetWorkerQueue()) | 
|  | .WillRepeatedly(Return(&worker_queue_)); | 
|  | return std::unique_ptr<internal::AudioSendStream>( | 
|  | new internal::AudioSendStream( | 
|  | Clock::GetRealTimeClock(), stream_config_, audio_state_, | 
|  | task_queue_factory_.get(), &rtp_transport_, &bitrate_allocator_, | 
|  | &event_log_, &rtcp_rtt_stats_, absl::nullopt, | 
|  | std::unique_ptr<voe::ChannelSendInterface>(channel_send_))); | 
|  | } | 
|  |  | 
|  | AudioSendStream::Config& config() { return stream_config_; } | 
|  | MockAudioEncoderFactory& mock_encoder_factory() { | 
|  | return *static_cast<MockAudioEncoderFactory*>( | 
|  | stream_config_.encoder_factory.get()); | 
|  | } | 
|  | MockRtpRtcp* rtp_rtcp() { return &rtp_rtcp_; } | 
|  | MockChannelSend* channel_send() { return channel_send_; } | 
|  | RtpTransportControllerSendInterface* transport() { return &rtp_transport_; } | 
|  |  | 
|  | static void AddBweToConfig(AudioSendStream::Config* config) { | 
|  | config->rtp.extensions.push_back(RtpExtension( | 
|  | RtpExtension::kTransportSequenceNumberUri, kTransportSequenceNumberId)); | 
|  | config->send_codec_spec->transport_cc_enabled = true; | 
|  | } | 
|  |  | 
|  | void SetupDefaultChannelSend(bool audio_bwe_enabled) { | 
|  | EXPECT_TRUE(channel_send_ == nullptr); | 
|  | channel_send_ = new ::testing::StrictMock<MockChannelSend>(); | 
|  | EXPECT_CALL(*channel_send_, GetRtpRtcp()).WillRepeatedly(Invoke([this]() { | 
|  | return &this->rtp_rtcp_; | 
|  | })); | 
|  | EXPECT_CALL(rtp_rtcp_, SSRC).WillRepeatedly(Return(kSsrc)); | 
|  | EXPECT_CALL(*channel_send_, SetRTCP_CNAME(StrEq(kCName))).Times(1); | 
|  | EXPECT_CALL(*channel_send_, SetFrameEncryptor(_)).Times(1); | 
|  | EXPECT_CALL(rtp_rtcp_, SetExtmapAllowMixed(false)).Times(1); | 
|  | EXPECT_CALL(*channel_send_, | 
|  | SetSendAudioLevelIndicationStatus(true, kAudioLevelId)) | 
|  | .Times(1); | 
|  | EXPECT_CALL(rtp_transport_, GetBandwidthObserver()) | 
|  | .WillRepeatedly(Return(&bandwidth_observer_)); | 
|  | if (audio_bwe_enabled) { | 
|  | EXPECT_CALL(rtp_rtcp_, | 
|  | RegisterRtpHeaderExtension(TransportSequenceNumber::kUri, | 
|  | kTransportSequenceNumberId)) | 
|  | .Times(1); | 
|  | EXPECT_CALL(*channel_send_, | 
|  | RegisterSenderCongestionControlObjects( | 
|  | &rtp_transport_, Eq(&bandwidth_observer_))) | 
|  | .Times(1); | 
|  | } else { | 
|  | EXPECT_CALL(*channel_send_, RegisterSenderCongestionControlObjects( | 
|  | &rtp_transport_, Eq(nullptr))) | 
|  | .Times(1); | 
|  | } | 
|  | EXPECT_CALL(*channel_send_, ResetSenderCongestionControlObjects()).Times(1); | 
|  | EXPECT_CALL(rtp_rtcp_, SetRid(std::string())).Times(1); | 
|  | } | 
|  |  | 
|  | void SetupMockForSetupSendCodec(bool expect_set_encoder_call) { | 
|  | if (expect_set_encoder_call) { | 
|  | EXPECT_CALL(*channel_send_, SetEncoderForMock(_, _)) | 
|  | .WillOnce(Invoke( | 
|  | [this](int payload_type, std::unique_ptr<AudioEncoder>* encoder) { | 
|  | this->audio_encoder_ = std::move(*encoder); | 
|  | return true; | 
|  | })); | 
|  | } | 
|  | } | 
|  |  | 
|  | void SetupMockForCallEncoder() { | 
|  | // Let ModifyEncoder to invoke mock audio encoder. | 
|  | EXPECT_CALL(*channel_send_, CallEncoder(_)) | 
|  | .WillRepeatedly( | 
|  | [this](rtc::FunctionView<void(AudioEncoder*)> modifier) { | 
|  | if (this->audio_encoder_) | 
|  | modifier(this->audio_encoder_.get()); | 
|  | }); | 
|  | } | 
|  |  | 
|  | void SetupMockForSendTelephoneEvent() { | 
|  | EXPECT_TRUE(channel_send_); | 
|  | EXPECT_CALL(*channel_send_, SetSendTelephoneEventPayloadType( | 
|  | kTelephoneEventPayloadType, | 
|  | kTelephoneEventPayloadFrequency)); | 
|  | EXPECT_CALL( | 
|  | *channel_send_, | 
|  | SendTelephoneEventOutband(kTelephoneEventCode, kTelephoneEventDuration)) | 
|  | .WillOnce(Return(true)); | 
|  | } | 
|  |  | 
|  | void SetupMockForGetStats() { | 
|  | using ::testing::DoAll; | 
|  | using ::testing::SetArgPointee; | 
|  | using ::testing::SetArgReferee; | 
|  |  | 
|  | std::vector<ReportBlock> report_blocks; | 
|  | webrtc::ReportBlock block = kReportBlock; | 
|  | report_blocks.push_back(block);  // Has wrong SSRC. | 
|  | block.source_SSRC = kSsrc; | 
|  | report_blocks.push_back(block);  // Correct block. | 
|  | block.fraction_lost = 0; | 
|  | report_blocks.push_back(block);  // Duplicate SSRC, bad fraction_lost. | 
|  |  | 
|  | EXPECT_TRUE(channel_send_); | 
|  | EXPECT_CALL(*channel_send_, GetRTCPStatistics()) | 
|  | .WillRepeatedly(Return(kCallStats)); | 
|  | EXPECT_CALL(*channel_send_, GetRemoteRTCPReportBlocks()) | 
|  | .WillRepeatedly(Return(report_blocks)); | 
|  | EXPECT_CALL(*channel_send_, GetANAStatistics()) | 
|  | .WillRepeatedly(Return(ANAStats())); | 
|  | EXPECT_CALL(*channel_send_, GetBitrate()).WillRepeatedly(Return(0)); | 
|  |  | 
|  | audio_processing_stats_.echo_return_loss = kEchoReturnLoss; | 
|  | audio_processing_stats_.echo_return_loss_enhancement = | 
|  | kEchoReturnLossEnhancement; | 
|  | audio_processing_stats_.delay_median_ms = kEchoDelayMedian; | 
|  | audio_processing_stats_.delay_standard_deviation_ms = kEchoDelayStdDev; | 
|  | audio_processing_stats_.divergent_filter_fraction = | 
|  | kDivergentFilterFraction; | 
|  | audio_processing_stats_.residual_echo_likelihood = kResidualEchoLikelihood; | 
|  | audio_processing_stats_.residual_echo_likelihood_recent_max = | 
|  | kResidualEchoLikelihoodMax; | 
|  |  | 
|  | EXPECT_CALL(*audio_processing_, GetStatistics(true)) | 
|  | .WillRepeatedly(Return(audio_processing_stats_)); | 
|  | } | 
|  | TaskQueueForTest* worker() { return &worker_queue_; } | 
|  |  | 
|  | private: | 
|  | SimulatedClock clock_; | 
|  | std::unique_ptr<TaskQueueFactory> task_queue_factory_; | 
|  | rtc::scoped_refptr<AudioState> audio_state_; | 
|  | AudioSendStream::Config stream_config_; | 
|  | ::testing::StrictMock<MockChannelSend>* channel_send_ = nullptr; | 
|  | rtc::scoped_refptr<MockAudioProcessing> audio_processing_; | 
|  | AudioProcessingStats audio_processing_stats_; | 
|  | ::testing::StrictMock<MockRtcpBandwidthObserver> bandwidth_observer_; | 
|  | ::testing::NiceMock<MockRtcEventLog> event_log_; | 
|  | ::testing::NiceMock<MockRtpTransportControllerSend> rtp_transport_; | 
|  | ::testing::NiceMock<MockRtpRtcp> rtp_rtcp_; | 
|  | MockRtcpRttStats rtcp_rtt_stats_; | 
|  | ::testing::NiceMock<MockLimitObserver> limit_observer_; | 
|  | BitrateAllocator bitrate_allocator_; | 
|  | // |worker_queue| is defined last to ensure all pending tasks are cancelled | 
|  | // and deleted before any other members. | 
|  | TaskQueueForTest worker_queue_; | 
|  | std::unique_ptr<AudioEncoder> audio_encoder_; | 
|  | }; | 
|  |  | 
|  | // The audio level ranges linearly [0,32767]. | 
|  | std::unique_ptr<AudioFrame> CreateAudioFrame1kHzSineWave(int16_t audio_level, | 
|  | int duration_ms, | 
|  | int sample_rate_hz, | 
|  | size_t num_channels) { | 
|  | size_t samples_per_channel = sample_rate_hz / (1000 / duration_ms); | 
|  | std::vector<int16_t> audio_data(samples_per_channel * num_channels, 0); | 
|  | std::unique_ptr<AudioFrame> audio_frame = std::make_unique<AudioFrame>(); | 
|  | audio_frame->UpdateFrame(0 /* RTP timestamp */, &audio_data[0], | 
|  | samples_per_channel, sample_rate_hz, | 
|  | AudioFrame::SpeechType::kNormalSpeech, | 
|  | AudioFrame::VADActivity::kVadUnknown, num_channels); | 
|  | SineWaveGenerator wave_generator(1000.0, audio_level); | 
|  | wave_generator.GenerateNextFrame(audio_frame.get()); | 
|  | return audio_frame; | 
|  | } | 
|  |  | 
|  | }  // namespace | 
|  |  | 
|  | TEST(AudioSendStreamTest, ConfigToString) { | 
|  | AudioSendStream::Config config(/*send_transport=*/nullptr); | 
|  | config.rtp.ssrc = kSsrc; | 
|  | config.rtp.c_name = kCName; | 
|  | config.min_bitrate_bps = 12000; | 
|  | config.max_bitrate_bps = 34000; | 
|  | config.send_codec_spec = | 
|  | AudioSendStream::Config::SendCodecSpec(kIsacPayloadType, kIsacFormat); | 
|  | config.send_codec_spec->nack_enabled = true; | 
|  | config.send_codec_spec->transport_cc_enabled = false; | 
|  | config.send_codec_spec->cng_payload_type = 42; | 
|  | config.encoder_factory = MockAudioEncoderFactory::CreateUnusedFactory(); | 
|  | config.rtp.extmap_allow_mixed = true; | 
|  | config.rtp.extensions.push_back( | 
|  | RtpExtension(RtpExtension::kAudioLevelUri, kAudioLevelId)); | 
|  | config.rtcp_report_interval_ms = 2500; | 
|  | EXPECT_EQ( | 
|  | "{rtp: {ssrc: 1234, extmap-allow-mixed: true, extensions: [{uri: " | 
|  | "urn:ietf:params:rtp-hdrext:ssrc-audio-level, id: 2}], " | 
|  | "c_name: foo_name}, rtcp_report_interval_ms: 2500, " | 
|  | "send_transport: null, " | 
|  | "min_bitrate_bps: 12000, max_bitrate_bps: 34000, " | 
|  | "send_codec_spec: {nack_enabled: true, transport_cc_enabled: false, " | 
|  | "cng_payload_type: 42, payload_type: 103, " | 
|  | "format: {name: isac, clockrate_hz: 16000, num_channels: 1, " | 
|  | "parameters: {}}}}", | 
|  | config.ToString()); | 
|  | } | 
|  |  | 
|  | TEST(AudioSendStreamTest, ConstructDestruct) { | 
|  | ConfigHelper helper(false, true); | 
|  | auto send_stream = helper.CreateAudioSendStream(); | 
|  | } | 
|  |  | 
|  | TEST(AudioSendStreamTest, SendTelephoneEvent) { | 
|  | ConfigHelper helper(false, true); | 
|  | auto send_stream = helper.CreateAudioSendStream(); | 
|  | helper.SetupMockForSendTelephoneEvent(); | 
|  | EXPECT_TRUE(send_stream->SendTelephoneEvent( | 
|  | kTelephoneEventPayloadType, kTelephoneEventPayloadFrequency, | 
|  | kTelephoneEventCode, kTelephoneEventDuration)); | 
|  | } | 
|  |  | 
|  | TEST(AudioSendStreamTest, SetMuted) { | 
|  | ConfigHelper helper(false, true); | 
|  | auto send_stream = helper.CreateAudioSendStream(); | 
|  | EXPECT_CALL(*helper.channel_send(), SetInputMute(true)); | 
|  | send_stream->SetMuted(true); | 
|  | } | 
|  |  | 
|  | TEST(AudioSendStreamTest, AudioBweCorrectObjectsOnChannelProxy) { | 
|  | ScopedFieldTrials field_trials("WebRTC-Audio-SendSideBwe/Enabled/"); | 
|  | ConfigHelper helper(true, true); | 
|  | auto send_stream = helper.CreateAudioSendStream(); | 
|  | } | 
|  |  | 
|  | TEST(AudioSendStreamTest, NoAudioBweCorrectObjectsOnChannelProxy) { | 
|  | ConfigHelper helper(false, true); | 
|  | auto send_stream = helper.CreateAudioSendStream(); | 
|  | } | 
|  |  | 
|  | TEST(AudioSendStreamTest, GetStats) { | 
|  | ConfigHelper helper(false, true); | 
|  | auto send_stream = helper.CreateAudioSendStream(); | 
|  | helper.SetupMockForGetStats(); | 
|  | AudioSendStream::Stats stats = send_stream->GetStats(true); | 
|  | EXPECT_EQ(kSsrc, stats.local_ssrc); | 
|  | EXPECT_EQ(kCallStats.payload_bytes_sent, stats.payload_bytes_sent); | 
|  | EXPECT_EQ(kCallStats.header_and_padding_bytes_sent, | 
|  | stats.header_and_padding_bytes_sent); | 
|  | EXPECT_EQ(kCallStats.packetsSent, stats.packets_sent); | 
|  | EXPECT_EQ(kReportBlock.cumulative_num_packets_lost, stats.packets_lost); | 
|  | EXPECT_EQ(Q8ToFloat(kReportBlock.fraction_lost), stats.fraction_lost); | 
|  | EXPECT_EQ(kIsacFormat.name, stats.codec_name); | 
|  | EXPECT_EQ(static_cast<int32_t>(kReportBlock.interarrival_jitter / | 
|  | (kIsacFormat.clockrate_hz / 1000)), | 
|  | stats.jitter_ms); | 
|  | EXPECT_EQ(kCallStats.rttMs, stats.rtt_ms); | 
|  | EXPECT_EQ(0, stats.audio_level); | 
|  | EXPECT_EQ(0, stats.total_input_energy); | 
|  | EXPECT_EQ(0, stats.total_input_duration); | 
|  | EXPECT_EQ(kEchoDelayMedian, stats.apm_statistics.delay_median_ms); | 
|  | EXPECT_EQ(kEchoDelayStdDev, stats.apm_statistics.delay_standard_deviation_ms); | 
|  | EXPECT_EQ(kEchoReturnLoss, stats.apm_statistics.echo_return_loss); | 
|  | EXPECT_EQ(kEchoReturnLossEnhancement, | 
|  | stats.apm_statistics.echo_return_loss_enhancement); | 
|  | EXPECT_EQ(kDivergentFilterFraction, | 
|  | stats.apm_statistics.divergent_filter_fraction); | 
|  | EXPECT_EQ(kResidualEchoLikelihood, | 
|  | stats.apm_statistics.residual_echo_likelihood); | 
|  | EXPECT_EQ(kResidualEchoLikelihoodMax, | 
|  | stats.apm_statistics.residual_echo_likelihood_recent_max); | 
|  | EXPECT_FALSE(stats.typing_noise_detected); | 
|  | } | 
|  |  | 
|  | TEST(AudioSendStreamTest, GetStatsAudioLevel) { | 
|  | ConfigHelper helper(false, true); | 
|  | auto send_stream = helper.CreateAudioSendStream(); | 
|  | helper.SetupMockForGetStats(); | 
|  | EXPECT_CALL(*helper.channel_send(), ProcessAndEncodeAudioForMock(_)) | 
|  | .Times(AnyNumber()); | 
|  |  | 
|  | constexpr int kSampleRateHz = 48000; | 
|  | constexpr size_t kNumChannels = 1; | 
|  |  | 
|  | constexpr int16_t kSilentAudioLevel = 0; | 
|  | constexpr int16_t kMaxAudioLevel = 32767;  // Audio level is [0,32767]. | 
|  | constexpr int kAudioFrameDurationMs = 10; | 
|  |  | 
|  | // Process 10 audio frames (100 ms) of silence. After this, on the next | 
|  | // (11-th) frame, the audio level will be updated with the maximum audio level | 
|  | // of the first 11 frames. See AudioLevel. | 
|  | for (size_t i = 0; i < 10; ++i) { | 
|  | send_stream->SendAudioData(CreateAudioFrame1kHzSineWave( | 
|  | kSilentAudioLevel, kAudioFrameDurationMs, kSampleRateHz, kNumChannels)); | 
|  | } | 
|  | AudioSendStream::Stats stats = send_stream->GetStats(); | 
|  | EXPECT_EQ(kSilentAudioLevel, stats.audio_level); | 
|  | EXPECT_NEAR(0.0f, stats.total_input_energy, kTolerance); | 
|  | EXPECT_NEAR(0.1f, stats.total_input_duration, kTolerance);  // 100 ms = 0.1 s | 
|  |  | 
|  | // Process 10 audio frames (100 ms) of maximum audio level. | 
|  | // Note that AudioLevel updates the audio level every 11th frame, processing | 
|  | // 10 frames above was needed to see a non-zero audio level here. | 
|  | for (size_t i = 0; i < 10; ++i) { | 
|  | send_stream->SendAudioData(CreateAudioFrame1kHzSineWave( | 
|  | kMaxAudioLevel, kAudioFrameDurationMs, kSampleRateHz, kNumChannels)); | 
|  | } | 
|  | stats = send_stream->GetStats(); | 
|  | EXPECT_EQ(kMaxAudioLevel, stats.audio_level); | 
|  | // Energy increases by energy*duration, where energy is audio level in [0,1]. | 
|  | EXPECT_NEAR(0.1f, stats.total_input_energy, kTolerance);    // 0.1 s of max | 
|  | EXPECT_NEAR(0.2f, stats.total_input_duration, kTolerance);  // 200 ms = 0.2 s | 
|  | } | 
|  |  | 
|  | TEST(AudioSendStreamTest, SendCodecAppliesAudioNetworkAdaptor) { | 
|  | ConfigHelper helper(false, true); | 
|  | helper.config().send_codec_spec = | 
|  | AudioSendStream::Config::SendCodecSpec(0, kOpusFormat); | 
|  | const std::string kAnaConfigString = "abcde"; | 
|  | const std::string kAnaReconfigString = "12345"; | 
|  |  | 
|  | helper.config().audio_network_adaptor_config = kAnaConfigString; | 
|  |  | 
|  | EXPECT_CALL(helper.mock_encoder_factory(), MakeAudioEncoderMock(_, _, _, _)) | 
|  | .WillOnce(Invoke([&kAnaConfigString, &kAnaReconfigString]( | 
|  | int payload_type, const SdpAudioFormat& format, | 
|  | absl::optional<AudioCodecPairId> codec_pair_id, | 
|  | std::unique_ptr<AudioEncoder>* return_value) { | 
|  | auto mock_encoder = SetupAudioEncoderMock(payload_type, format); | 
|  | EXPECT_CALL(*mock_encoder, | 
|  | EnableAudioNetworkAdaptor(StrEq(kAnaConfigString), _)) | 
|  | .WillOnce(Return(true)); | 
|  | EXPECT_CALL(*mock_encoder, | 
|  | EnableAudioNetworkAdaptor(StrEq(kAnaReconfigString), _)) | 
|  | .WillOnce(Return(true)); | 
|  | *return_value = std::move(mock_encoder); | 
|  | })); | 
|  |  | 
|  | auto send_stream = helper.CreateAudioSendStream(); | 
|  |  | 
|  | auto stream_config = helper.config(); | 
|  | stream_config.audio_network_adaptor_config = kAnaReconfigString; | 
|  |  | 
|  | helper.SetupMockForCallEncoder(); | 
|  | send_stream->Reconfigure(stream_config); | 
|  | } | 
|  |  | 
|  | // VAD is applied when codec is mono and the CNG frequency matches the codec | 
|  | // clock rate. | 
|  | TEST(AudioSendStreamTest, SendCodecCanApplyVad) { | 
|  | ConfigHelper helper(false, false); | 
|  | helper.config().send_codec_spec = | 
|  | AudioSendStream::Config::SendCodecSpec(9, kG722Format); | 
|  | helper.config().send_codec_spec->cng_payload_type = 105; | 
|  | using ::testing::Invoke; | 
|  | std::unique_ptr<AudioEncoder> stolen_encoder; | 
|  | EXPECT_CALL(*helper.channel_send(), SetEncoderForMock(_, _)) | 
|  | .WillOnce( | 
|  | Invoke([&stolen_encoder](int payload_type, | 
|  | std::unique_ptr<AudioEncoder>* encoder) { | 
|  | stolen_encoder = std::move(*encoder); | 
|  | return true; | 
|  | })); | 
|  | EXPECT_CALL(*helper.channel_send(), RegisterCngPayloadType(105, 8000)); | 
|  |  | 
|  | auto send_stream = helper.CreateAudioSendStream(); | 
|  |  | 
|  | // We cannot truly determine if the encoder created is an AudioEncoderCng.  It | 
|  | // is the only reasonable implementation that will return something from | 
|  | // ReclaimContainedEncoders, though. | 
|  | ASSERT_TRUE(stolen_encoder); | 
|  | EXPECT_FALSE(stolen_encoder->ReclaimContainedEncoders().empty()); | 
|  | } | 
|  |  | 
|  | TEST(AudioSendStreamTest, DoesNotPassHigherBitrateThanMaxBitrate) { | 
|  | ConfigHelper helper(false, true); | 
|  | auto send_stream = helper.CreateAudioSendStream(); | 
|  | EXPECT_CALL(*helper.channel_send(), | 
|  | OnBitrateAllocation( | 
|  | Field(&BitrateAllocationUpdate::target_bitrate, | 
|  | Eq(DataRate::bps(helper.config().max_bitrate_bps))))); | 
|  | BitrateAllocationUpdate update; | 
|  | update.target_bitrate = DataRate::bps(helper.config().max_bitrate_bps + 5000); | 
|  | update.packet_loss_ratio = 0; | 
|  | update.round_trip_time = TimeDelta::ms(50); | 
|  | update.bwe_period = TimeDelta::ms(6000); | 
|  | helper.worker()->SendTask([&] { send_stream->OnBitrateUpdated(update); }, | 
|  | RTC_FROM_HERE); | 
|  | } | 
|  |  | 
|  | TEST(AudioSendStreamTest, SSBweTargetInRangeRespected) { | 
|  | ScopedFieldTrials field_trials("WebRTC-Audio-SendSideBwe/Enabled/"); | 
|  | ConfigHelper helper(true, true); | 
|  | auto send_stream = helper.CreateAudioSendStream(); | 
|  | EXPECT_CALL(*helper.channel_send(), | 
|  | OnBitrateAllocation(Field( | 
|  | &BitrateAllocationUpdate::target_bitrate, | 
|  | Eq(DataRate::bps(helper.config().max_bitrate_bps - 5000))))); | 
|  | BitrateAllocationUpdate update; | 
|  | update.target_bitrate = DataRate::bps(helper.config().max_bitrate_bps - 5000); | 
|  | helper.worker()->SendTask([&] { send_stream->OnBitrateUpdated(update); }, | 
|  | RTC_FROM_HERE); | 
|  | } | 
|  |  | 
|  | TEST(AudioSendStreamTest, SSBweFieldTrialMinRespected) { | 
|  | ScopedFieldTrials field_trials( | 
|  | "WebRTC-Audio-SendSideBwe/Enabled/" | 
|  | "WebRTC-Audio-Allocation/min:6kbps,max:64kbps/"); | 
|  | ConfigHelper helper(true, true); | 
|  | auto send_stream = helper.CreateAudioSendStream(); | 
|  | EXPECT_CALL( | 
|  | *helper.channel_send(), | 
|  | OnBitrateAllocation(Field(&BitrateAllocationUpdate::target_bitrate, | 
|  | Eq(DataRate::kbps(6))))); | 
|  | BitrateAllocationUpdate update; | 
|  | update.target_bitrate = DataRate::kbps(1); | 
|  | helper.worker()->SendTask([&] { send_stream->OnBitrateUpdated(update); }, | 
|  | RTC_FROM_HERE); | 
|  | } | 
|  |  | 
|  | TEST(AudioSendStreamTest, SSBweFieldTrialMaxRespected) { | 
|  | ScopedFieldTrials field_trials( | 
|  | "WebRTC-Audio-SendSideBwe/Enabled/" | 
|  | "WebRTC-Audio-Allocation/min:6kbps,max:64kbps/"); | 
|  | ConfigHelper helper(true, true); | 
|  | auto send_stream = helper.CreateAudioSendStream(); | 
|  | EXPECT_CALL( | 
|  | *helper.channel_send(), | 
|  | OnBitrateAllocation(Field(&BitrateAllocationUpdate::target_bitrate, | 
|  | Eq(DataRate::kbps(64))))); | 
|  | BitrateAllocationUpdate update; | 
|  | update.target_bitrate = DataRate::kbps(128); | 
|  | helper.worker()->SendTask([&] { send_stream->OnBitrateUpdated(update); }, | 
|  | RTC_FROM_HERE); | 
|  | } | 
|  |  | 
|  | TEST(AudioSendStreamTest, SSBweWithOverhead) { | 
|  | ScopedFieldTrials field_trials( | 
|  | "WebRTC-Audio-SendSideBwe/Enabled/" | 
|  | "WebRTC-SendSideBwe-WithOverhead/Enabled/" | 
|  | "WebRTC-Audio-LegacyOverhead/Disabled/"); | 
|  | ConfigHelper helper(true, true); | 
|  | auto send_stream = helper.CreateAudioSendStream(); | 
|  | EXPECT_CALL(*helper.channel_send(), CallEncoder(_)).Times(1); | 
|  | send_stream->OnOverheadChanged(kOverheadPerPacket.bytes<size_t>()); | 
|  | const DataRate bitrate = | 
|  | DataRate::bps(helper.config().max_bitrate_bps) + kMaxOverheadRate; | 
|  | EXPECT_CALL(*helper.channel_send(), | 
|  | OnBitrateAllocation(Field( | 
|  | &BitrateAllocationUpdate::target_bitrate, Eq(bitrate)))); | 
|  | BitrateAllocationUpdate update; | 
|  | update.target_bitrate = bitrate; | 
|  | helper.worker()->SendTask([&] { send_stream->OnBitrateUpdated(update); }, | 
|  | RTC_FROM_HERE); | 
|  | } | 
|  |  | 
|  | TEST(AudioSendStreamTest, SSBweWithOverheadMinRespected) { | 
|  | ScopedFieldTrials field_trials( | 
|  | "WebRTC-Audio-SendSideBwe/Enabled/" | 
|  | "WebRTC-SendSideBwe-WithOverhead/Enabled/" | 
|  | "WebRTC-Audio-LegacyOverhead/Disabled/" | 
|  | "WebRTC-Audio-Allocation/min:6kbps,max:64kbps/"); | 
|  | ConfigHelper helper(true, true); | 
|  | auto send_stream = helper.CreateAudioSendStream(); | 
|  | EXPECT_CALL(*helper.channel_send(), CallEncoder(_)).Times(1); | 
|  | send_stream->OnOverheadChanged(kOverheadPerPacket.bytes<size_t>()); | 
|  | const DataRate bitrate = DataRate::kbps(6) + kMinOverheadRate; | 
|  | EXPECT_CALL(*helper.channel_send(), | 
|  | OnBitrateAllocation(Field( | 
|  | &BitrateAllocationUpdate::target_bitrate, Eq(bitrate)))); | 
|  | BitrateAllocationUpdate update; | 
|  | update.target_bitrate = DataRate::kbps(1); | 
|  | helper.worker()->SendTask([&] { send_stream->OnBitrateUpdated(update); }, | 
|  | RTC_FROM_HERE); | 
|  | } | 
|  |  | 
|  | TEST(AudioSendStreamTest, SSBweWithOverheadMaxRespected) { | 
|  | ScopedFieldTrials field_trials( | 
|  | "WebRTC-Audio-SendSideBwe/Enabled/" | 
|  | "WebRTC-SendSideBwe-WithOverhead/Enabled/" | 
|  | "WebRTC-Audio-LegacyOverhead/Disabled/" | 
|  | "WebRTC-Audio-Allocation/min:6kbps,max:64kbps/"); | 
|  | ConfigHelper helper(true, true); | 
|  | auto send_stream = helper.CreateAudioSendStream(); | 
|  | EXPECT_CALL(*helper.channel_send(), CallEncoder(_)).Times(1); | 
|  | send_stream->OnOverheadChanged(kOverheadPerPacket.bytes<size_t>()); | 
|  | const DataRate bitrate = DataRate::kbps(64) + kMaxOverheadRate; | 
|  | EXPECT_CALL(*helper.channel_send(), | 
|  | OnBitrateAllocation(Field( | 
|  | &BitrateAllocationUpdate::target_bitrate, Eq(bitrate)))); | 
|  | BitrateAllocationUpdate update; | 
|  | update.target_bitrate = DataRate::kbps(128); | 
|  | helper.worker()->SendTask([&] { send_stream->OnBitrateUpdated(update); }, | 
|  | RTC_FROM_HERE); | 
|  | } | 
|  |  | 
|  | TEST(AudioSendStreamTest, ProbingIntervalOnBitrateUpdated) { | 
|  | ConfigHelper helper(false, true); | 
|  | auto send_stream = helper.CreateAudioSendStream(); | 
|  |  | 
|  | EXPECT_CALL(*helper.channel_send(), | 
|  | OnBitrateAllocation(Field(&BitrateAllocationUpdate::bwe_period, | 
|  | Eq(TimeDelta::ms(5000))))); | 
|  | BitrateAllocationUpdate update; | 
|  | update.target_bitrate = DataRate::bps(helper.config().max_bitrate_bps + 5000); | 
|  | update.packet_loss_ratio = 0; | 
|  | update.round_trip_time = TimeDelta::ms(50); | 
|  | update.bwe_period = TimeDelta::ms(5000); | 
|  | helper.worker()->SendTask([&] { send_stream->OnBitrateUpdated(update); }, | 
|  | RTC_FROM_HERE); | 
|  | } | 
|  |  | 
|  | // Test that AudioSendStream doesn't recreate the encoder unnecessarily. | 
|  | TEST(AudioSendStreamTest, DontRecreateEncoder) { | 
|  | ConfigHelper helper(false, false); | 
|  | // WillOnce is (currently) the default used by ConfigHelper if asked to set an | 
|  | // expectation for SetEncoder. Since this behavior is essential for this test | 
|  | // to be correct, it's instead set-up manually here. Otherwise a simple change | 
|  | // to ConfigHelper (say to WillRepeatedly) would silently make this test | 
|  | // useless. | 
|  | EXPECT_CALL(*helper.channel_send(), SetEncoderForMock(_, _)) | 
|  | .WillOnce(Return()); | 
|  |  | 
|  | EXPECT_CALL(*helper.channel_send(), RegisterCngPayloadType(105, 8000)); | 
|  |  | 
|  | helper.config().send_codec_spec = | 
|  | AudioSendStream::Config::SendCodecSpec(9, kG722Format); | 
|  | helper.config().send_codec_spec->cng_payload_type = 105; | 
|  | auto send_stream = helper.CreateAudioSendStream(); | 
|  | send_stream->Reconfigure(helper.config()); | 
|  | } | 
|  |  | 
|  | TEST(AudioSendStreamTest, ReconfigureTransportCcResetsFirst) { | 
|  | ScopedFieldTrials field_trials("WebRTC-Audio-SendSideBwe/Enabled/"); | 
|  | ConfigHelper helper(false, true); | 
|  | auto send_stream = helper.CreateAudioSendStream(); | 
|  | auto new_config = helper.config(); | 
|  | ConfigHelper::AddBweToConfig(&new_config); | 
|  |  | 
|  | EXPECT_CALL(*helper.rtp_rtcp(), | 
|  | RegisterRtpHeaderExtension(TransportSequenceNumber::kUri, | 
|  | kTransportSequenceNumberId)) | 
|  | .Times(1); | 
|  | { | 
|  | ::testing::InSequence seq; | 
|  | EXPECT_CALL(*helper.channel_send(), ResetSenderCongestionControlObjects()) | 
|  | .Times(1); | 
|  | EXPECT_CALL(*helper.channel_send(), RegisterSenderCongestionControlObjects( | 
|  | helper.transport(), Ne(nullptr))) | 
|  | .Times(1); | 
|  | } | 
|  |  | 
|  | send_stream->Reconfigure(new_config); | 
|  | } | 
|  |  | 
|  | TEST(AudioSendStreamTest, OnTransportOverheadChanged) { | 
|  | ConfigHelper helper(false, true); | 
|  | auto send_stream = helper.CreateAudioSendStream(); | 
|  | auto new_config = helper.config(); | 
|  |  | 
|  | // CallEncoder will be called on overhead change. | 
|  | EXPECT_CALL(*helper.channel_send(), CallEncoder(::testing::_)).Times(1); | 
|  |  | 
|  | const size_t transport_overhead_per_packet_bytes = 333; | 
|  | send_stream->SetTransportOverhead(transport_overhead_per_packet_bytes); | 
|  |  | 
|  | EXPECT_EQ(transport_overhead_per_packet_bytes, | 
|  | send_stream->TestOnlyGetPerPacketOverheadBytes()); | 
|  | } | 
|  |  | 
|  | TEST(AudioSendStreamTest, OnAudioOverheadChanged) { | 
|  | ConfigHelper helper(false, true); | 
|  | auto send_stream = helper.CreateAudioSendStream(); | 
|  | auto new_config = helper.config(); | 
|  |  | 
|  | // CallEncoder will be called on overhead change. | 
|  | EXPECT_CALL(*helper.channel_send(), CallEncoder(::testing::_)).Times(1); | 
|  |  | 
|  | const size_t audio_overhead_per_packet_bytes = 555; | 
|  | send_stream->OnOverheadChanged(audio_overhead_per_packet_bytes); | 
|  | EXPECT_EQ(audio_overhead_per_packet_bytes, | 
|  | send_stream->TestOnlyGetPerPacketOverheadBytes()); | 
|  | } | 
|  |  | 
|  | TEST(AudioSendStreamTest, OnAudioAndTransportOverheadChanged) { | 
|  | ConfigHelper helper(false, true); | 
|  | auto send_stream = helper.CreateAudioSendStream(); | 
|  | auto new_config = helper.config(); | 
|  |  | 
|  | // CallEncoder will be called when each of overhead changes. | 
|  | EXPECT_CALL(*helper.channel_send(), CallEncoder(::testing::_)).Times(2); | 
|  |  | 
|  | const size_t transport_overhead_per_packet_bytes = 333; | 
|  | send_stream->SetTransportOverhead(transport_overhead_per_packet_bytes); | 
|  |  | 
|  | const size_t audio_overhead_per_packet_bytes = 555; | 
|  | send_stream->OnOverheadChanged(audio_overhead_per_packet_bytes); | 
|  |  | 
|  | EXPECT_EQ( | 
|  | transport_overhead_per_packet_bytes + audio_overhead_per_packet_bytes, | 
|  | send_stream->TestOnlyGetPerPacketOverheadBytes()); | 
|  | } | 
|  |  | 
|  | // Validates that reconfiguring the AudioSendStream with a Frame encryptor | 
|  | // correctly reconfigures on the object without crashing. | 
|  | TEST(AudioSendStreamTest, ReconfigureWithFrameEncryptor) { | 
|  | ConfigHelper helper(false, true); | 
|  | auto send_stream = helper.CreateAudioSendStream(); | 
|  | auto new_config = helper.config(); | 
|  |  | 
|  | rtc::scoped_refptr<FrameEncryptorInterface> mock_frame_encryptor_0( | 
|  | new rtc::RefCountedObject<MockFrameEncryptor>()); | 
|  | new_config.frame_encryptor = mock_frame_encryptor_0; | 
|  | EXPECT_CALL(*helper.channel_send(), SetFrameEncryptor(Ne(nullptr))).Times(1); | 
|  | send_stream->Reconfigure(new_config); | 
|  |  | 
|  | // Not updating the frame encryptor shouldn't force it to reconfigure. | 
|  | EXPECT_CALL(*helper.channel_send(), SetFrameEncryptor(_)).Times(0); | 
|  | send_stream->Reconfigure(new_config); | 
|  |  | 
|  | // Updating frame encryptor to a new object should force a call to the proxy. | 
|  | rtc::scoped_refptr<FrameEncryptorInterface> mock_frame_encryptor_1( | 
|  | new rtc::RefCountedObject<MockFrameEncryptor>()); | 
|  | new_config.frame_encryptor = mock_frame_encryptor_1; | 
|  | new_config.crypto_options.sframe.require_frame_encryption = true; | 
|  | EXPECT_CALL(*helper.channel_send(), SetFrameEncryptor(Ne(nullptr))).Times(1); | 
|  | send_stream->Reconfigure(new_config); | 
|  | } | 
|  | }  // namespace test | 
|  | }  // namespace webrtc |