From 24301a67c66e6091418e83da49cfb367ef2c6645 Mon Sep 17 00:00:00 2001 From: "wu@webrtc.org" Date: Fri, 13 Dec 2013 19:17:43 +0000 Subject: [PATCH] Update talk to 58174641 together with http://review.webrtc.org/4319005/. R=turaj@webrtc.org Review URL: https://webrtc-codereview.appspot.com/5809004 git-svn-id: http://webrtc.googlecode.com/svn/trunk@5287 4adac7df-926f-26a2-2b94-8c16560cd09d --- talk/media/webrtc/fakewebrtcvideoengine.h | 18 +- talk/media/webrtc/fakewebrtcvoiceengine.h | 7 + talk/media/webrtc/webrtcmediaengine.h | 3 + talk/media/webrtc/webrtcvideoengine.cc | 77 ++--- talk/media/webrtc/webrtcvideoengine.h | 10 +- .../webrtc/webrtcvideoengine_unittest.cc | 47 +++ webrtc/common_types.h | 19 ++ .../audio_coding/main/acm2/acm_receiver.cc | 11 + .../audio_coding/main/acm2/acm_receiver.h | 7 + .../main/acm2/audio_coding_module.gypi | 2 + .../main/acm2/audio_coding_module_impl.cc | 6 + .../main/acm2/audio_coding_module_impl.h | 2 + .../audio_coding/main/acm2/call_statistics.cc | 55 ++++ .../audio_coding/main/acm2/call_statistics.h | 63 ++++ .../main/acm2/call_statistics_unittest.cc | 55 ++++ .../main/interface/audio_coding_module.h | 3 + .../main/source/audio_coding_module_impl.cc | 13 + .../main/source/audio_coding_module_impl.h | 5 + .../modules/audio_coding/neteq/webrtc_neteq.c | 16 +- .../modules/audio_coding/neteq4/neteq_impl.cc | 4 +- webrtc/modules/modules.gyp | 1 + webrtc/voice_engine/channel.cc | 4 + webrtc/voice_engine/channel.h | 1 + webrtc/voice_engine/include/voe_neteq_stats.h | 4 + webrtc/voice_engine/voe_neteq_stats_impl.cc | 22 +- webrtc/voice_engine/voe_neteq_stats_impl.h | 4 + .../voice_engine/voe_neteq_stats_unittest.cc | 285 ++++++++++++++++++ webrtc/voice_engine/voice_engine.gyp | 1 + 28 files changed, 697 insertions(+), 48 deletions(-) create mode 100644 webrtc/modules/audio_coding/main/acm2/call_statistics.cc create mode 100644 webrtc/modules/audio_coding/main/acm2/call_statistics.h create mode 100644 webrtc/modules/audio_coding/main/acm2/call_statistics_unittest.cc create mode 100644 webrtc/voice_engine/voe_neteq_stats_unittest.cc diff --git a/talk/media/webrtc/fakewebrtcvideoengine.h b/talk/media/webrtc/fakewebrtcvideoengine.h index 070c7317e3..bb75c2a7fe 100644 --- a/talk/media/webrtc/fakewebrtcvideoengine.h +++ b/talk/media/webrtc/fakewebrtcvideoengine.h @@ -339,12 +339,14 @@ class FakeWebRtcVideoEngine }; class Capturer : public webrtc::ViEExternalCapture { public: - Capturer() : channel_id_(-1), denoising_(false), last_capture_time_(0) { } + Capturer() : channel_id_(-1), denoising_(false), + last_capture_time_(0), incoming_frame_num_(0) { } int channel_id() const { return channel_id_; } void set_channel_id(int channel_id) { channel_id_ = channel_id; } bool denoising() const { return denoising_; } void set_denoising(bool denoising) { denoising_ = denoising; } - int64 last_capture_time() { return last_capture_time_; } + int64 last_capture_time() const { return last_capture_time_; } + int incoming_frame_num() const { return incoming_frame_num_; } // From ViEExternalCapture virtual int IncomingFrame(unsigned char* videoFrame, @@ -359,6 +361,7 @@ class FakeWebRtcVideoEngine const webrtc::ViEVideoFrameI420& video_frame, unsigned long long captureTime) { last_capture_time_ = captureTime; + ++incoming_frame_num_; return 0; } @@ -366,6 +369,7 @@ class FakeWebRtcVideoEngine int channel_id_; bool denoising_; int64 last_capture_time_; + int incoming_frame_num_; }; FakeWebRtcVideoEngine(const cricket::VideoCodec* const* codecs, @@ -408,6 +412,16 @@ class FakeWebRtcVideoEngine int GetLastCapturer() const { return last_capturer_; } int GetNumCapturers() const { return static_cast(capturers_.size()); } + int GetIncomingFrameNum(int channel_id) const { + for (std::map::const_iterator iter = capturers_.begin(); + iter != capturers_.end(); ++iter) { + Capturer* capturer = iter->second; + if (capturer->channel_id() == channel_id) { + return capturer->incoming_frame_num(); + } + } + return -1; + } void set_fail_alloc_capturer(bool fail_alloc_capturer) { fail_alloc_capturer_ = fail_alloc_capturer; } diff --git a/talk/media/webrtc/fakewebrtcvoiceengine.h b/talk/media/webrtc/fakewebrtcvoiceengine.h index 809816bc22..a68d65ef75 100644 --- a/talk/media/webrtc/fakewebrtcvoiceengine.h +++ b/talk/media/webrtc/fakewebrtcvoiceengine.h @@ -631,6 +631,13 @@ class FakeWebRtcVoiceEngine // webrtc::VoENetEqStats WEBRTC_STUB(GetNetworkStatistics, (int, webrtc::NetworkStatistics&)); +#ifdef USE_WEBRTC_DEV_BRANCH + WEBRTC_FUNC_CONST(GetDecodingCallStatistics, (int channel, + webrtc::AudioDecodingCallStats*)) { + WEBRTC_CHECK_CHANNEL(channel); + return 0; + } +#endif // webrtc::VoENetwork WEBRTC_FUNC(RegisterExternalTransport, (int channel, diff --git a/talk/media/webrtc/webrtcmediaengine.h b/talk/media/webrtc/webrtcmediaengine.h index 94e7a99d82..82abefa32b 100644 --- a/talk/media/webrtc/webrtcmediaengine.h +++ b/talk/media/webrtc/webrtcmediaengine.h @@ -145,6 +145,9 @@ class WebRtcMediaEngine : public cricket::MediaEngineInterface { virtual void SetVideoLogging(int min_sev, const char* filter) OVERRIDE { delegate_->SetVideoLogging(min_sev, filter); } + virtual bool StartAecDump(FILE* file) OVERRIDE { + return delegate_->StartAecDump(file); + } virtual bool RegisterVoiceProcessor( uint32 ssrc, VoiceProcessor* video_processor, MediaProcessorDirection direction) OVERRIDE { diff --git a/talk/media/webrtc/webrtcvideoengine.cc b/talk/media/webrtc/webrtcvideoengine.cc index 1c1ccc38f6..88e09fc384 100644 --- a/talk/media/webrtc/webrtcvideoengine.cc +++ b/talk/media/webrtc/webrtcvideoengine.cc @@ -2118,18 +2118,6 @@ bool WebRtcVideoMediaChannel::GetSendChannelKey(uint32 local_ssrc, return true; } -WebRtcVideoChannelSendInfo* WebRtcVideoMediaChannel::GetSendChannel( - VideoCapturer* video_capturer) { - for (SendChannelMap::iterator iter = send_channels_.begin(); - iter != send_channels_.end(); ++iter) { - WebRtcVideoChannelSendInfo* send_channel = iter->second; - if (send_channel->video_capturer() == video_capturer) { - return send_channel; - } - } - return NULL; -} - WebRtcVideoChannelSendInfo* WebRtcVideoMediaChannel::GetSendChannel( uint32 local_ssrc) { uint32 key; @@ -2159,6 +2147,18 @@ bool WebRtcVideoMediaChannel::CreateSendChannelKey(uint32 local_ssrc, return true; } +int WebRtcVideoMediaChannel::GetSendChannelNum(VideoCapturer* capturer) { + int num = 0; + for (SendChannelMap::iterator iter = send_channels_.begin(); + iter != send_channels_.end(); ++iter) { + WebRtcVideoChannelSendInfo* send_channel = iter->second; + if (send_channel->video_capturer() == capturer) { + ++num; + } + } + return num; +} + uint32 WebRtcVideoMediaChannel::GetDefaultChannelSsrc() { WebRtcVideoChannelSendInfo* send_channel = send_channels_[0]; const StreamParams* sp = send_channel->stream_params(); @@ -2174,11 +2174,8 @@ bool WebRtcVideoMediaChannel::DeleteSendChannel(uint32 ssrc_key) { return false; } WebRtcVideoChannelSendInfo* send_channel = send_channels_[ssrc_key]; - VideoCapturer* capturer = send_channel->video_capturer(); - if (capturer != NULL) { - capturer->SignalVideoFrame.disconnect(this); - send_channel->set_video_capturer(NULL); - } + MaybeDisconnectCapturer(send_channel->video_capturer()); + send_channel->set_video_capturer(NULL); int channel_id = send_channel->channel_id(); int capture_id = send_channel->capture_id(); @@ -2217,7 +2214,7 @@ bool WebRtcVideoMediaChannel::RemoveCapturer(uint32 ssrc) { if (capturer == NULL) { return false; } - capturer->SignalVideoFrame.disconnect(this); + MaybeDisconnectCapturer(capturer); send_channel->set_video_capturer(NULL); const int64 timestamp = send_channel->local_stream_info()->time_stamp(); if (send_codec_) { @@ -2468,14 +2465,10 @@ bool WebRtcVideoMediaChannel::SetCapturer(uint32 ssrc, return false; } VideoCapturer* old_capturer = send_channel->video_capturer(); - if (old_capturer) { - old_capturer->SignalVideoFrame.disconnect(this); - } + MaybeDisconnectCapturer(old_capturer); send_channel->set_video_capturer(capturer); - capturer->SignalVideoFrame.connect( - this, - &WebRtcVideoMediaChannel::SendFrame); + MaybeConnectCapturer(capturer); if (!capturer->IsScreencast() && ratio_w_ != 0 && ratio_h_ != 0) { capturer->UpdateAspectRatio(ratio_w_, ratio_h_); } @@ -2865,20 +2858,23 @@ bool WebRtcVideoMediaChannel::GetRenderer(uint32 ssrc, return true; } -// TODO(zhurunz): Add unittests to test this function. -// TODO(thorcarpenter): This is broken. One capturer registered on two ssrc -// will not send any video to the second ssrc send channel. We should remove -// GetSendChannel(capturer) and pass in an ssrc here. void WebRtcVideoMediaChannel::SendFrame(VideoCapturer* capturer, const VideoFrame* frame) { - // If there's send channel registers to the |capturer|, then only send the - // frame to that channel and return. Otherwise send the frame to the default - // channel, which currently taking frames from the engine. - WebRtcVideoChannelSendInfo* send_channel = GetSendChannel(capturer); - if (send_channel) { - SendFrame(send_channel, frame, capturer->IsScreencast()); + // If the |capturer| is registered to any send channel, then send the frame + // to those send channels. + bool capturer_is_channel_owned = false; + for (SendChannelMap::iterator iter = send_channels_.begin(); + iter != send_channels_.end(); ++iter) { + WebRtcVideoChannelSendInfo* send_channel = iter->second; + if (send_channel->video_capturer() == capturer) { + SendFrame(send_channel, frame, capturer->IsScreencast()); + capturer_is_channel_owned = true; + } + } + if (capturer_is_channel_owned) { return; } + // TODO(hellner): Remove below for loop once the captured frame no longer // come from the engine, i.e. the engine no longer owns a capturer. for (SendChannelMap::iterator iter = send_channels_.begin(); @@ -3754,6 +3750,19 @@ bool WebRtcVideoMediaChannel::SetLocalRtxSsrc(int channel_id, return true; } +void WebRtcVideoMediaChannel::MaybeConnectCapturer(VideoCapturer* capturer) { + if (capturer != NULL && GetSendChannelNum(capturer) == 1) { + capturer->SignalVideoFrame.connect(this, + &WebRtcVideoMediaChannel::SendFrame); + } +} + +void WebRtcVideoMediaChannel::MaybeDisconnectCapturer(VideoCapturer* capturer) { + if (capturer != NULL && GetSendChannelNum(capturer) == 1) { + capturer->SignalVideoFrame.disconnect(this); + } +} + } // namespace cricket #endif // HAVE_WEBRTC_VIDEO diff --git a/talk/media/webrtc/webrtcvideoengine.h b/talk/media/webrtc/webrtcvideoengine.h index 627846178c..289903a072 100644 --- a/talk/media/webrtc/webrtcvideoengine.h +++ b/talk/media/webrtc/webrtcvideoengine.h @@ -366,11 +366,12 @@ class WebRtcVideoMediaChannel : public talk_base::MessageHandler, // If the local ssrc correspond to that of the default channel the key is 0. // For all other channels the returned key will be the same as the local ssrc. bool GetSendChannelKey(uint32 local_ssrc, uint32* key); - WebRtcVideoChannelSendInfo* GetSendChannel(VideoCapturer* video_capturer); WebRtcVideoChannelSendInfo* GetSendChannel(uint32 local_ssrc); // Creates a new unique key that can be used for inserting a new send channel // into |send_channels_| bool CreateSendChannelKey(uint32 local_ssrc, uint32* key); + // Get the number of the send channels |capturer| registered with. + int GetSendChannelNum(VideoCapturer* capturer); bool IsDefaultChannel(int channel_id) const { return channel_id == vie_channel_; @@ -404,6 +405,13 @@ class WebRtcVideoMediaChannel : public talk_base::MessageHandler, bool SetLocalRtxSsrc(int channel_id, const StreamParams& send_params, uint32 primary_ssrc, int stream_idx); + // Connect |capturer| to WebRtcVideoMediaChannel if it is only registered + // to one send channel, i.e. the first send channel. + void MaybeConnectCapturer(VideoCapturer* capturer); + // Disconnect |capturer| from WebRtcVideoMediaChannel if it is only registered + // to one send channel, i.e. the last send channel. + void MaybeDisconnectCapturer(VideoCapturer* capturer); + // Global state. WebRtcVideoEngine* engine_; VoiceMediaChannel* voice_channel_; diff --git a/talk/media/webrtc/webrtcvideoengine_unittest.cc b/talk/media/webrtc/webrtcvideoengine_unittest.cc index 2b83cceea9..d5886a1382 100644 --- a/talk/media/webrtc/webrtcvideoengine_unittest.cc +++ b/talk/media/webrtc/webrtcvideoengine_unittest.cc @@ -1216,6 +1216,53 @@ TEST_F(WebRtcVideoEngineTestFake, SetOptionsWithDenoising) { EXPECT_FALSE(vie_.GetCaptureDenoising(capture_id)); } +TEST_F(WebRtcVideoEngineTestFake, MultipleSendStreamsWithOneCapturer) { + EXPECT_TRUE(SetupEngine()); + + // Start the capturer + cricket::FakeVideoCapturer capturer; + cricket::VideoFormat capture_format_vga = cricket::VideoFormat(640, 480, + cricket::VideoFormat::FpsToInterval(30), cricket::FOURCC_I420); + EXPECT_EQ(cricket::CS_RUNNING, capturer.Start(capture_format_vga)); + + // Add send streams and connect the capturer + for (unsigned int i = 0; i < sizeof(kSsrcs2)/sizeof(kSsrcs2[0]); ++i) { + EXPECT_TRUE(channel_->AddSendStream( + cricket::StreamParams::CreateLegacy(kSsrcs2[i]))); + // Register the capturer to the ssrc. + EXPECT_TRUE(channel_->SetCapturer(kSsrcs2[i], &capturer)); + } + + const int channel0 = vie_.GetChannelFromLocalSsrc(kSsrcs2[0]); + ASSERT_NE(-1, channel0); + const int channel1 = vie_.GetChannelFromLocalSsrc(kSsrcs2[1]); + ASSERT_NE(-1, channel1); + ASSERT_NE(channel0, channel1); + + // Set send codec. + std::vector codecs; + cricket::VideoCodec send_codec(100, "VP8", 640, 480, 30, 0); + codecs.push_back(send_codec); + EXPECT_TRUE(channel_->SetSendCodecs(codecs)); + + EXPECT_TRUE(capturer.CaptureFrame()); + EXPECT_EQ(1, vie_.GetIncomingFrameNum(channel0)); + EXPECT_EQ(1, vie_.GetIncomingFrameNum(channel1)); + + EXPECT_TRUE(channel_->RemoveSendStream(kSsrcs2[0])); + EXPECT_TRUE(capturer.CaptureFrame()); + // channel0 is the default channel, so it won't be deleted. + // But it should be disconnected from the capturer. + EXPECT_EQ(1, vie_.GetIncomingFrameNum(channel0)); + EXPECT_EQ(2, vie_.GetIncomingFrameNum(channel1)); + + EXPECT_TRUE(channel_->RemoveSendStream(kSsrcs2[1])); + EXPECT_TRUE(capturer.CaptureFrame()); + EXPECT_EQ(1, vie_.GetIncomingFrameNum(channel0)); + // channel1 has already been deleted. + EXPECT_EQ(-1, vie_.GetIncomingFrameNum(channel1)); +} + // Disabled since its flaky: b/11288120 TEST_F(WebRtcVideoEngineTestFake, DISABLED_SendReceiveBitratesStats) { diff --git a/webrtc/common_types.h b/webrtc/common_types.h index 30231ce37c..bcdb5e88c8 100644 --- a/webrtc/common_types.h +++ b/webrtc/common_types.h @@ -382,6 +382,25 @@ struct NetworkStatistics // NETEQ statistics int addedSamples; }; +// Statistics for calls to AudioCodingModule::PlayoutData10Ms(). +struct AudioDecodingCallStats { + AudioDecodingCallStats() + : calls_to_silence_generator(0), + calls_to_neteq(0), + decoded_normal(0), + decoded_plc(0), + decoded_cng(0), + decoded_plc_cng(0) {} + + int calls_to_silence_generator; // Number of calls where silence generated, + // and NetEq was disengaged from decoding. + int calls_to_neteq; // Number of calls to NetEq. + int decoded_normal; // Number of calls where audio RTP packet decoded. + int decoded_plc; // Number of calls resulted in PLC. + int decoded_cng; // Number of calls where comfort noise generated due to DTX. + int decoded_plc_cng; // Number of calls resulted where PLC faded to CNG. +}; + typedef struct { int min; // minumum diff --git a/webrtc/modules/audio_coding/main/acm2/acm_receiver.cc b/webrtc/modules/audio_coding/main/acm2/acm_receiver.cc index 5da42adaf9..ac92198f92 100644 --- a/webrtc/modules/audio_coding/main/acm2/acm_receiver.cc +++ b/webrtc/modules/audio_coding/main/acm2/acm_receiver.cc @@ -19,6 +19,7 @@ #include "webrtc/common_types.h" #include "webrtc/modules/audio_coding/main/acm2/acm_common_defs.h" #include "webrtc/modules/audio_coding/main/acm2/acm_resampler.h" +#include "webrtc/modules/audio_coding/main/acm2/call_statistics.h" #include "webrtc/modules/audio_coding/main/acm2/nack.h" #include "webrtc/modules/audio_coding/neteq4/interface/audio_decoder.h" #include "webrtc/modules/audio_coding/neteq4/interface/neteq.h" @@ -461,6 +462,7 @@ int AcmReceiver::GetAudio(int desired_freq_hz, AudioFrame* audio_frame) { audio_frame->vad_activity_ = previous_audio_activity_; SetAudioFrameActivityAndType(vad_enabled_, type, audio_frame); previous_audio_activity_ = audio_frame->vad_activity_; + call_stats_.DecodedByNetEq(audio_frame->speech_type_); return 0; } @@ -761,6 +763,9 @@ bool AcmReceiver::GetSilence(int desired_sample_rate_hz, AudioFrame* frame) { return false; } + // Update statistics. + call_stats_.DecodedBySilenceGenerator(); + // Set the values if already got a packet, otherwise set to default values. if (last_audio_decoder_ >= 0) { current_sample_rate_hz_ = ACMCodecDB::database_[last_audio_decoder_].plfreq; @@ -832,6 +837,12 @@ void AcmReceiver::InsertStreamOfSyncPackets( } } +void AcmReceiver::GetDecodingCallStatistics( + AudioDecodingCallStats* stats) const { + CriticalSectionScoped lock(neteq_crit_sect_); + *stats = call_stats_.GetDecodingStatistics(); +} + } // namespace acm2 } // namespace webrtc diff --git a/webrtc/modules/audio_coding/main/acm2/acm_receiver.h b/webrtc/modules/audio_coding/main/acm2/acm_receiver.h index 9267c1e68b..81eb5206b8 100644 --- a/webrtc/modules/audio_coding/main/acm2/acm_receiver.h +++ b/webrtc/modules/audio_coding/main/acm2/acm_receiver.h @@ -18,6 +18,7 @@ #include "webrtc/modules/audio_coding/main/interface/audio_coding_module.h" #include "webrtc/modules/audio_coding/main/acm2/acm_codec_database.h" #include "webrtc/modules/audio_coding/main/acm2/acm_resampler.h" +#include "webrtc/modules/audio_coding/main/acm2/call_statistics.h" #include "webrtc/modules/audio_coding/main/acm2/initial_delay_manager.h" #include "webrtc/modules/audio_coding/neteq4/interface/neteq.h" #include "webrtc/modules/interface/module_common_types.h" @@ -320,6 +321,10 @@ class AcmReceiver { // NetEqBackgroundNoiseMode BackgroundNoiseModeForTest() const; + // + // Get statistics of calls to GetAudio(). + void GetDecodingCallStatistics(AudioDecodingCallStats* stats) const; + private: int PayloadType2CodecIndex(uint8_t payload_type) const; @@ -361,6 +366,8 @@ class AcmReceiver { // initial delay is set. scoped_ptr missing_packets_sync_stream_; scoped_ptr late_packets_sync_stream_; + + CallStatistics call_stats_; }; } // namespace acm2 diff --git a/webrtc/modules/audio_coding/main/acm2/audio_coding_module.gypi b/webrtc/modules/audio_coding/main/acm2/audio_coding_module.gypi index 07541266de..f51c3bf7d7 100644 --- a/webrtc/modules/audio_coding/main/acm2/audio_coding_module.gypi +++ b/webrtc/modules/audio_coding/main/acm2/audio_coding_module.gypi @@ -84,6 +84,8 @@ 'audio_coding_module.cc', 'audio_coding_module_impl.cc', 'audio_coding_module_impl.h', + 'call_statistics.cc', + 'call_statistics.h', 'initial_delay_manager.cc', 'initial_delay_manager.h', 'nack.cc', diff --git a/webrtc/modules/audio_coding/main/acm2/audio_coding_module_impl.cc b/webrtc/modules/audio_coding/main/acm2/audio_coding_module_impl.cc index ce05218180..4c64e07dd5 100644 --- a/webrtc/modules/audio_coding/main/acm2/audio_coding_module_impl.cc +++ b/webrtc/modules/audio_coding/main/acm2/audio_coding_module_impl.cc @@ -20,6 +20,7 @@ #include "webrtc/modules/audio_coding/main/acm2/acm_common_defs.h" #include "webrtc/modules/audio_coding/main/acm2/acm_generic_codec.h" #include "webrtc/modules/audio_coding/main/acm2/acm_resampler.h" +#include "webrtc/modules/audio_coding/main/acm2/call_statistics.h" #include "webrtc/system_wrappers/interface/critical_section_wrapper.h" #include "webrtc/system_wrappers/interface/rw_lock_wrapper.h" #include "webrtc/system_wrappers/interface/trace.h" @@ -1979,6 +1980,11 @@ const char* AudioCodingModuleImpl::Version() const { return kExperimentalAcmVersion; } +void AudioCodingModuleImpl::GetDecodingCallStatistics( + AudioDecodingCallStats* call_stats) const { + receiver_.GetDecodingCallStatistics(call_stats); +} + } // namespace acm2 } // namespace webrtc diff --git a/webrtc/modules/audio_coding/main/acm2/audio_coding_module_impl.h b/webrtc/modules/audio_coding/main/acm2/audio_coding_module_impl.h index 17fab396c7..bc4ea0f7a6 100644 --- a/webrtc/modules/audio_coding/main/acm2/audio_coding_module_impl.h +++ b/webrtc/modules/audio_coding/main/acm2/audio_coding_module_impl.h @@ -228,6 +228,8 @@ class AudioCodingModuleImpl : public AudioCodingModule { std::vector GetNackList(int round_trip_time_ms) const; + void GetDecodingCallStatistics(AudioDecodingCallStats* stats) const; + private: int UnregisterReceiveCodecSafe(int payload_type); diff --git a/webrtc/modules/audio_coding/main/acm2/call_statistics.cc b/webrtc/modules/audio_coding/main/acm2/call_statistics.cc new file mode 100644 index 0000000000..9153325afa --- /dev/null +++ b/webrtc/modules/audio_coding/main/acm2/call_statistics.cc @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2013 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 "webrtc/modules/audio_coding/main/acm2/call_statistics.h" + +#include + +namespace webrtc { + +namespace acm2 { + +void CallStatistics::DecodedByNetEq(AudioFrame::SpeechType speech_type) { + ++decoding_stat_.calls_to_neteq; + switch (speech_type) { + case AudioFrame::kNormalSpeech: { + ++decoding_stat_.decoded_normal; + break; + } + case AudioFrame::kPLC: { + ++decoding_stat_.decoded_plc; + break; + } + case AudioFrame::kCNG: { + ++decoding_stat_.decoded_cng; + break; + } + case AudioFrame::kPLCCNG: { + ++decoding_stat_.decoded_plc_cng; + break; + } + case AudioFrame::kUndefined: { + // If the audio is decoded by NetEq, |kUndefined| is not an option. + assert(false); + } + } +} + +void CallStatistics::DecodedBySilenceGenerator() { + ++decoding_stat_.calls_to_silence_generator; +} + +const AudioDecodingCallStats& CallStatistics::GetDecodingStatistics() const { + return decoding_stat_; +} + +} // namespace acm2 + +} // namespace webrtc diff --git a/webrtc/modules/audio_coding/main/acm2/call_statistics.h b/webrtc/modules/audio_coding/main/acm2/call_statistics.h new file mode 100644 index 0000000000..2aece0ff40 --- /dev/null +++ b/webrtc/modules/audio_coding/main/acm2/call_statistics.h @@ -0,0 +1,63 @@ +/* + * Copyright (c) 2013 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 WEBRTC_MODULES_AUDIO_CODING_MAIN_ACM2_CALL_STATISTICS_H_ +#define WEBRTC_MODULES_AUDIO_CODING_MAIN_ACM2_CALL_STATISTICS_H_ + +#include "webrtc/common_types.h" +#include "webrtc/modules/interface/module_common_types.h" + +// +// This class is for book keeping of calls to ACM. It is not useful to log API +// calls which are supposed to be called every 10ms, e.g. PlayoutData10Ms(), +// however, it is useful to know the number of such calls in a given time +// interval. The current implementation covers calls to PlayoutData10Ms() with +// detailed accounting of the decoded speech type. +// +// Thread Safety +// ============= +// Please note that this class in not thread safe. The class must be protected +// if different APIs are called from different threads. +// + +namespace webrtc { + +namespace acm2 { + +class CallStatistics { + public: + CallStatistics() {} + ~CallStatistics() {} + + // Call this method to indicate that NetEq engaged in decoding. |speech_type| + // is the audio-type according to NetEq. + void DecodedByNetEq(AudioFrame::SpeechType speech_type); + + // Call this method to indicate that a decoding call resulted in generating + // silence, i.e. call to NetEq is bypassed and the output audio is zero. + void DecodedBySilenceGenerator(); + + // Get statistics for decoding. The statistics include the number of calls to + // NetEq and silence generator, as well as the type of speech pulled of off + // NetEq, c.f. declaration of AudioDecodingCallStats for detailed description. + const AudioDecodingCallStats& GetDecodingStatistics() const; + + private: + // Reset the decoding statistics. + void ResetDecodingStatistics(); + + AudioDecodingCallStats decoding_stat_; +}; + +} // namespace acm2 + +} // namespace webrtc + +#endif // WEBRTC_MODULES_AUDIO_CODING_MAIN_ACM2_CALL_STATISTICS_H_ diff --git a/webrtc/modules/audio_coding/main/acm2/call_statistics_unittest.cc b/webrtc/modules/audio_coding/main/acm2/call_statistics_unittest.cc new file mode 100644 index 0000000000..61aadd73cf --- /dev/null +++ b/webrtc/modules/audio_coding/main/acm2/call_statistics_unittest.cc @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2013 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 "gtest/gtest.h" +#include "webrtc/modules/audio_coding/main/acm2/call_statistics.h" + +namespace webrtc { + +namespace acm2 { + +TEST(CallStatisticsTest, InitializedZero) { + CallStatistics call_stats; + AudioDecodingCallStats stats; + + stats = call_stats.GetDecodingStatistics(); + EXPECT_EQ(0, stats.calls_to_neteq); + EXPECT_EQ(0, stats.calls_to_silence_generator); + EXPECT_EQ(0, stats.decoded_normal); + EXPECT_EQ(0, stats.decoded_cng); + EXPECT_EQ(0, stats.decoded_plc); + EXPECT_EQ(0, stats.decoded_plc_cng); +} + +TEST(CallStatisticsTest, AllCalls) { + CallStatistics call_stats; + AudioDecodingCallStats stats; + + call_stats.DecodedBySilenceGenerator(); + call_stats.DecodedByNetEq(AudioFrame::kNormalSpeech); + call_stats.DecodedByNetEq(AudioFrame::kPLC); + call_stats.DecodedByNetEq(AudioFrame::kPLCCNG); + call_stats.DecodedByNetEq(AudioFrame::kCNG); + + stats = call_stats.GetDecodingStatistics(); + EXPECT_EQ(4, stats.calls_to_neteq); + EXPECT_EQ(1, stats.calls_to_silence_generator); + EXPECT_EQ(1, stats.decoded_normal); + EXPECT_EQ(1, stats.decoded_cng); + EXPECT_EQ(1, stats.decoded_plc); + EXPECT_EQ(1, stats.decoded_plc_cng); +} + +} // namespace acm2 + +} // namespace webrtc + + + diff --git a/webrtc/modules/audio_coding/main/interface/audio_coding_module.h b/webrtc/modules/audio_coding/main/interface/audio_coding_module.h index f8b9690f84..db45addde2 100644 --- a/webrtc/modules/audio_coding/main/interface/audio_coding_module.h +++ b/webrtc/modules/audio_coding/main/interface/audio_coding_module.h @@ -931,6 +931,9 @@ class AudioCodingModule: public Module { // is returned. // virtual std::vector GetNackList(int round_trip_time_ms) const = 0; + + virtual void GetDecodingCallStatistics( + AudioDecodingCallStats* call_stats) const = 0; }; struct AudioCodingModuleFactory { diff --git a/webrtc/modules/audio_coding/main/source/audio_coding_module_impl.cc b/webrtc/modules/audio_coding/main/source/audio_coding_module_impl.cc index 1e71a04420..556f530ecf 100644 --- a/webrtc/modules/audio_coding/main/source/audio_coding_module_impl.cc +++ b/webrtc/modules/audio_coding/main/source/audio_coding_module_impl.cc @@ -18,6 +18,7 @@ #include "webrtc/engine_configurations.h" #include "webrtc/modules/audio_coding/main/source/acm_codec_database.h" #include "webrtc/modules/audio_coding/main/acm2/acm_common_defs.h" +#include "webrtc/modules/audio_coding/main/acm2/call_statistics.h" #include "webrtc/modules/audio_coding/main/source/acm_dtmf_detection.h" #include "webrtc/modules/audio_coding/main/source/acm_generic_codec.h" #include "webrtc/modules/audio_coding/main/source/acm_resampler.h" @@ -2273,6 +2274,9 @@ int32_t AudioCodingModuleImpl::PlayoutData10Ms( { CriticalSectionScoped lock(acm_crit_sect_); + // Update call statistics. + call_stats_.DecodedByNetEq(audio_frame->speech_type_); + if (update_nack) { assert(nack_.get()); nack_->UpdateLastDecodedPacket(decoded_seq_num, decoded_timestamp); @@ -2879,6 +2883,9 @@ bool AudioCodingModuleImpl::GetSilence(int desired_sample_rate_hz, return false; } + // Record call to silence generator. + call_stats_.DecodedBySilenceGenerator(); + // We stop accumulating packets, if the number of packets or the total size // exceeds a threshold. int max_num_packets; @@ -3030,6 +3037,12 @@ const char* AudioCodingModuleImpl::Version() const { return kLegacyAcmVersion; } +void AudioCodingModuleImpl::GetDecodingCallStatistics( + AudioDecodingCallStats* call_stats) const { + CriticalSectionScoped lock(acm_crit_sect_); + *call_stats = call_stats_.GetDecodingStatistics(); +} + } // namespace acm1 } // namespace webrtc diff --git a/webrtc/modules/audio_coding/main/source/audio_coding_module_impl.h b/webrtc/modules/audio_coding/main/source/audio_coding_module_impl.h index 7acde177db..f0b22f1146 100644 --- a/webrtc/modules/audio_coding/main/source/audio_coding_module_impl.h +++ b/webrtc/modules/audio_coding/main/source/audio_coding_module_impl.h @@ -19,6 +19,7 @@ #include "webrtc/modules/audio_coding/main/source/acm_codec_database.h" #include "webrtc/modules/audio_coding/main/source/acm_neteq.h" #include "webrtc/modules/audio_coding/main/source/acm_resampler.h" +#include "webrtc/modules/audio_coding/main/acm2/call_statistics.h" #include "webrtc/system_wrappers/interface/scoped_ptr.h" namespace webrtc { @@ -303,6 +304,8 @@ class AudioCodingModuleImpl : public AudioCodingModule { // Disable NACK. void DisableNack(); + void GetDecodingCallStatistics(AudioDecodingCallStats* call_stats) const; + private: // Change required states after starting to receive the codec corresponding // to |index|. @@ -441,6 +444,8 @@ class AudioCodingModuleImpl : public AudioCodingModule { Clock* clock_; scoped_ptr nack_; bool nack_enabled_; + + acm2::CallStatistics call_stats_; }; } // namespace acm1 diff --git a/webrtc/modules/audio_coding/neteq/webrtc_neteq.c b/webrtc/modules/audio_coding/neteq/webrtc_neteq.c index de1ccd1e3c..fad690d081 100644 --- a/webrtc/modules/audio_coding/neteq/webrtc_neteq.c +++ b/webrtc/modules/audio_coding/neteq/webrtc_neteq.c @@ -1104,14 +1104,6 @@ int WebRtcNetEQ_GetSpeechOutputType(void *inst, enum WebRtcNetEQOutputType *outp /* If CN or internal CNG */ *outputType = kOutputCNG; -#ifdef NETEQ_VAD - } - else if ( NetEqMainInst->DSPinst.VADInst.VADDecision == 0 ) - { - /* post-decode VAD says passive speaker */ - *outputType = kOutputVADPassive; -#endif /* NETEQ_VAD */ - } else if ((NetEqMainInst->DSPinst.w16_mode == MODE_EXPAND) && (NetEqMainInst->DSPinst.ExpandInst.w16_expandMuteFactor == 0)) @@ -1125,6 +1117,14 @@ int WebRtcNetEQ_GetSpeechOutputType(void *inst, enum WebRtcNetEQOutputType *outp /* PLC mode */ *outputType = kOutputPLC; +#ifdef NETEQ_VAD + } + else if ( NetEqMainInst->DSPinst.VADInst.VADDecision == 0 ) + { + /* post-decode VAD says passive speaker */ + *outputType = kOutputVADPassive; +#endif /* NETEQ_VAD */ + } else { diff --git a/webrtc/modules/audio_coding/neteq4/neteq_impl.cc b/webrtc/modules/audio_coding/neteq4/neteq_impl.cc index 73ca5e4e21..fb27af2cff 100644 --- a/webrtc/modules/audio_coding/neteq4/neteq_impl.cc +++ b/webrtc/modules/audio_coding/neteq4/neteq_impl.cc @@ -1887,13 +1887,13 @@ NetEqOutputType NetEqImpl::LastOutputType() { assert(expand_.get()); if (last_mode_ == kModeCodecInternalCng || last_mode_ == kModeRfc3389Cng) { return kOutputCNG; - } else if (vad_->running() && !vad_->active_speech()) { - return kOutputVADPassive; } else if (last_mode_ == kModeExpand && expand_->MuteFactor(0) == 0) { // Expand mode has faded down to background noise only (very long expand). return kOutputPLCtoCNG; } else if (last_mode_ == kModeExpand) { return kOutputPLC; + } else if (vad_->running() && !vad_->active_speech()) { + return kOutputVADPassive; } else { return kOutputNormal; } diff --git a/webrtc/modules/modules.gyp b/webrtc/modules/modules.gyp index e33816c6e9..ef354abc0f 100644 --- a/webrtc/modules/modules.gyp +++ b/webrtc/modules/modules.gyp @@ -102,6 +102,7 @@ ], 'sources': [ 'audio_coding/main/acm2/acm_receiver_unittest.cc', + 'audio_coding/main/acm2/call_statistics_unittest.cc', 'audio_coding/main/acm2/initial_delay_manager_unittest.cc', 'audio_coding/main/acm2/nack_unittest.cc', 'audio_coding/main/source/acm_neteq_unittest.cc', diff --git a/webrtc/voice_engine/channel.cc b/webrtc/voice_engine/channel.cc index 37016cedab..4e1913dc35 100644 --- a/webrtc/voice_engine/channel.cc +++ b/webrtc/voice_engine/channel.cc @@ -4619,6 +4619,10 @@ Channel::GetNetworkStatistics(NetworkStatistics& stats) return return_value; } +void Channel::GetDecodingCallStatistics(AudioDecodingCallStats* stats) const { + audio_coding_->GetDecodingCallStatistics(stats); +} + bool Channel::GetDelayEstimate(int* jitter_buffer_delay_ms, int* playout_buffer_delay_ms) const { if (_average_jitter_buffer_delay_us == 0) { diff --git a/webrtc/voice_engine/channel.h b/webrtc/voice_engine/channel.h index 4647056b36..da55e9d765 100644 --- a/webrtc/voice_engine/channel.h +++ b/webrtc/voice_engine/channel.h @@ -201,6 +201,7 @@ public: // VoENetEqStats int GetNetworkStatistics(NetworkStatistics& stats); + void GetDecodingCallStatistics(AudioDecodingCallStats* stats) const; // VoEVideoSync bool GetDelayEstimate(int* jitter_buffer_delay_ms, diff --git a/webrtc/voice_engine/include/voe_neteq_stats.h b/webrtc/voice_engine/include/voe_neteq_stats.h index f4e8f7b58a..1e8c2407f5 100644 --- a/webrtc/voice_engine/include/voe_neteq_stats.h +++ b/webrtc/voice_engine/include/voe_neteq_stats.h @@ -35,6 +35,10 @@ public: // The statistics are reset after the query. virtual int GetNetworkStatistics(int channel, NetworkStatistics& stats) = 0; + // Get statistics of calls to AudioCodingModule::PlayoutData10Ms(). + virtual int GetDecodingCallStatistics( + int channel, AudioDecodingCallStats* stats) const = 0; + protected: VoENetEqStats() {} virtual ~VoENetEqStats() {} diff --git a/webrtc/voice_engine/voe_neteq_stats_impl.cc b/webrtc/voice_engine/voe_neteq_stats_impl.cc index 264f4865e7..37d1cdac90 100644 --- a/webrtc/voice_engine/voe_neteq_stats_impl.cc +++ b/webrtc/voice_engine/voe_neteq_stats_impl.cc @@ -17,7 +17,6 @@ #include "webrtc/voice_engine/include/voe_errors.h" #include "webrtc/voice_engine/voice_engine_impl.h" - namespace webrtc { VoENetEqStats* VoENetEqStats::GetInterface(VoiceEngine* voiceEngine) @@ -73,6 +72,27 @@ int VoENetEqStatsImpl::GetNetworkStatistics(int channel, return channelPtr->GetNetworkStatistics(stats); } +int VoENetEqStatsImpl::GetDecodingCallStatistics( + int channel, AudioDecodingCallStats* stats) const { + ANDROID_NOT_SUPPORTED(_shared->statistics()); + + if (!_shared->statistics().Initialized()) { + _shared->SetLastError(VE_NOT_INITED, kTraceError); + return -1; + } + voe::ChannelOwner ch = _shared->channel_manager().GetChannel(channel); + voe::Channel* channelPtr = ch.channel(); + if (channelPtr == NULL) { + _shared->SetLastError(VE_CHANNEL_NOT_VALID, kTraceError, + "GetDecodingCallStatistics() failed to locate " + "channel"); + return -1; + } + + channelPtr->GetDecodingCallStatistics(stats); + return 0; +} + #endif // #ifdef WEBRTC_VOICE_ENGINE_NETEQ_STATS_API } // namespace webrtc diff --git a/webrtc/voice_engine/voe_neteq_stats_impl.h b/webrtc/voice_engine/voe_neteq_stats_impl.h index e99ebc0a0d..74b624b19c 100644 --- a/webrtc/voice_engine/voe_neteq_stats_impl.h +++ b/webrtc/voice_engine/voe_neteq_stats_impl.h @@ -13,6 +13,7 @@ #include "webrtc/voice_engine/include/voe_neteq_stats.h" +#include "webrtc/common_types.h" #include "webrtc/voice_engine/shared_data.h" namespace webrtc { @@ -23,6 +24,9 @@ public: virtual int GetNetworkStatistics(int channel, NetworkStatistics& stats); + virtual int GetDecodingCallStatistics( + int channel, AudioDecodingCallStats* stats) const; + protected: VoENetEqStatsImpl(voe::SharedData* shared); virtual ~VoENetEqStatsImpl(); diff --git a/webrtc/voice_engine/voe_neteq_stats_unittest.cc b/webrtc/voice_engine/voe_neteq_stats_unittest.cc new file mode 100644 index 0000000000..66a9a695c7 --- /dev/null +++ b/webrtc/voice_engine/voe_neteq_stats_unittest.cc @@ -0,0 +1,285 @@ +/* + * Copyright (c) 2013 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 "webrtc/voice_engine/include/voe_neteq_stats.h" + +#include "testing/gtest/include/gtest/gtest.h" +#include "webrtc/modules/audio_device/include/fake_audio_device.h" +#include "webrtc/system_wrappers/interface/scoped_ptr.h" +#include "webrtc/system_wrappers/interface/clock.h" +#include "webrtc/test/testsupport/gtest_disable.h" +#include "webrtc/voice_engine/include/voe_base.h" +#include "webrtc/voice_engine/include/voe_hardware.h" +#include "webrtc/voice_engine/voice_engine_defines.h" +#include "webrtc/modules/audio_coding/main/interface/audio_coding_module.h" +#include "webrtc/modules/audio_coding/main/interface/audio_coding_module_typedefs.h" +#include "webrtc/modules/audio_coding/main/acm2/audio_coding_module_impl.h" +#include "webrtc/modules/audio_coding/main/source/audio_coding_module_impl.h" + +namespace webrtc { +namespace voe { +namespace { + +const int kSampleRateHz = 16000; +const int kNumSamples10ms = kSampleRateHz / 100; +const int kFrameSizeMs = 10; // Multiple of 10. +const int kFrameSizeSamples = kFrameSizeMs / 10 * kNumSamples10ms; +const int kPayloadSizeBytes = kFrameSizeSamples * sizeof(int16_t); +const uint8_t kPayloadType = 111; + +class RtpUtility { + public: + RtpUtility(int samples_per_packet, uint8_t payload_type) + : samples_per_packet_(samples_per_packet), payload_type_(payload_type) {} + + virtual ~RtpUtility() {} + + void Populate(WebRtcRTPHeader* rtp_header) { + rtp_header->header.sequenceNumber = 0xABCD; + rtp_header->header.timestamp = 0xABCDEF01; + rtp_header->header.payloadType = payload_type_; + rtp_header->header.markerBit = false; + rtp_header->header.ssrc = 0x1234; + rtp_header->header.numCSRCs = 0; + rtp_header->frameType = kAudioFrameSpeech; + + rtp_header->header.payload_type_frequency = kSampleRateHz; + rtp_header->type.Audio.channel = 1; + rtp_header->type.Audio.isCNG = false; + } + + void Forward(WebRtcRTPHeader* rtp_header) { + ++rtp_header->header.sequenceNumber; + rtp_header->header.timestamp += samples_per_packet_; + } + + private: + int samples_per_packet_; + uint8_t payload_type_; +}; + +// This factory method allows access to ACM of a channel, facilitating insertion +// of packets to and pulling audio of ACM. +struct InsertAcm : AudioCodingModuleFactory { + explicit InsertAcm(AudioCodingModule* acm) : acm_(acm) {} + ~InsertAcm() {} + virtual AudioCodingModule* Create(int /*id*/) const { return acm_; } + + AudioCodingModule* acm_; +}; + +class VoENetEqStatsTest : public ::testing::Test { + protected: + VoENetEqStatsTest() + : acm1_(new acm1::AudioCodingModuleImpl(1, Clock::GetRealTimeClock())), + acm2_(new acm2::AudioCodingModuleImpl(2)), + voe_(VoiceEngine::Create()), + base_(VoEBase::GetInterface(voe_)), + voe_neteq_stats_(VoENetEqStats::GetInterface(voe_)), + channel_acm1_(-1), + channel_acm2_(-1), + adm_(new FakeAudioDeviceModule), + rtp_utility_(new RtpUtility(kFrameSizeSamples, kPayloadType)) {} + + ~VoENetEqStatsTest() {} + + void TearDown() { + voe_neteq_stats_->Release(); + base_->DeleteChannel(channel_acm1_); + base_->DeleteChannel(channel_acm2_); + base_->Terminate(); + base_->Release(); + VoiceEngine::Delete(voe_); + } + + void SetUp() { + // Check if all components are valid. + ASSERT_TRUE(voe_ != NULL); + ASSERT_TRUE(base_ != NULL); + ASSERT_TRUE(adm_.get() != NULL); + ASSERT_EQ(0, base_->Init(adm_.get())); + + // Set configs. + config_acm1_.Set(new InsertAcm(acm1_)); + config_acm2_.Set(new InsertAcm(acm2_)); + + // Create channe1s; + channel_acm1_ = base_->CreateChannel(config_acm1_); + ASSERT_NE(-1, channel_acm1_); + + channel_acm2_ = base_->CreateChannel(config_acm2_); + ASSERT_NE(-1, channel_acm2_); + + CodecInst codec; + AudioCodingModule::Codec("L16", &codec, kSampleRateHz, 1); + codec.pltype = kPayloadType; + + // Register L16 codec in ACMs. + ASSERT_EQ(0, acm1_->RegisterReceiveCodec(codec)); + ASSERT_EQ(0, acm2_->RegisterReceiveCodec(codec)); + + rtp_utility_->Populate(&rtp_header_); + } + + void InsertPacketAndPullAudio() { + AudioFrame audio_frame; + const uint8_t kPayload[kPayloadSizeBytes] = {0}; + + ASSERT_EQ(0, + acm1_->IncomingPacket(kPayload, kPayloadSizeBytes, rtp_header_)); + ASSERT_EQ(0, + acm2_->IncomingPacket(kPayload, kPayloadSizeBytes, rtp_header_)); + + ASSERT_EQ(0, acm1_->PlayoutData10Ms(-1, &audio_frame)); + ASSERT_EQ(0, acm2_->PlayoutData10Ms(-1, &audio_frame)); + rtp_utility_->Forward(&rtp_header_); + } + + void JustPullAudio() { + AudioFrame audio_frame; + ASSERT_EQ(0, acm1_->PlayoutData10Ms(-1, &audio_frame)); + ASSERT_EQ(0, acm2_->PlayoutData10Ms(-1, &audio_frame)); + } + + Config config_acm1_; + Config config_acm2_; + + // ACMs are inserted into VoE channels, and this class is not the owner of + // them. Therefore, they should not be deleted, not even in destructor. + AudioCodingModule* acm1_; + AudioCodingModule* acm2_; + + VoiceEngine* voe_; + VoEBase* base_; + VoENetEqStats* voe_neteq_stats_; + int channel_acm1_; + int channel_acm2_; + scoped_ptr adm_; + scoped_ptr rtp_utility_; + WebRtcRTPHeader rtp_header_; +}; + +// Check if the statistics are initialized correctly. Before any call to ACM +// all fields have to be zero. +TEST_F(VoENetEqStatsTest, InitializedToZero) { + AudioDecodingCallStats stats; + ASSERT_EQ(0, + voe_neteq_stats_->GetDecodingCallStatistics(channel_acm1_, &stats)); + EXPECT_EQ(0, stats.calls_to_neteq); + EXPECT_EQ(0, stats.calls_to_silence_generator); + EXPECT_EQ(0, stats.decoded_normal); + EXPECT_EQ(0, stats.decoded_cng); + EXPECT_EQ(0, stats.decoded_plc); + EXPECT_EQ(0, stats.decoded_plc_cng); + + ASSERT_EQ(0, + voe_neteq_stats_->GetDecodingCallStatistics(channel_acm2_, &stats)); + EXPECT_EQ(0, stats.calls_to_neteq); + EXPECT_EQ(0, stats.calls_to_silence_generator); + EXPECT_EQ(0, stats.decoded_normal); + EXPECT_EQ(0, stats.decoded_cng); + EXPECT_EQ(0, stats.decoded_plc); + EXPECT_EQ(0, stats.decoded_plc_cng); +} + +// Apply an initial playout delay. Calls to AudioCodingModule::PlayoutData10ms() +// should result in generating silence, check the associated field. +TEST_F(VoENetEqStatsTest, SilenceGeneratorCalled) { + AudioDecodingCallStats stats; + const int kInitialDelay = 100; + + acm1_->SetInitialPlayoutDelay(kInitialDelay); + acm2_->SetInitialPlayoutDelay(kInitialDelay); + + AudioFrame audio_frame; + int num_calls = 0; + for (int time_ms = 0; time_ms < kInitialDelay; + time_ms += kFrameSizeMs, ++num_calls) { + InsertPacketAndPullAudio(); + } + ASSERT_EQ(0, + voe_neteq_stats_->GetDecodingCallStatistics(channel_acm1_, &stats)); + EXPECT_EQ(0, stats.calls_to_neteq); + EXPECT_EQ(num_calls, stats.calls_to_silence_generator); + EXPECT_EQ(0, stats.decoded_normal); + EXPECT_EQ(0, stats.decoded_cng); + EXPECT_EQ(0, stats.decoded_plc); + EXPECT_EQ(0, stats.decoded_plc_cng); + + ASSERT_EQ(0, + voe_neteq_stats_->GetDecodingCallStatistics(channel_acm2_, &stats)); + EXPECT_EQ(0, stats.calls_to_neteq); + EXPECT_EQ(num_calls, stats.calls_to_silence_generator); + EXPECT_EQ(0, stats.decoded_normal); + EXPECT_EQ(0, stats.decoded_cng); + EXPECT_EQ(0, stats.decoded_plc); + EXPECT_EQ(0, stats.decoded_plc_cng); +} + +// Insert some packets and pull audio. Check statistics are valid. Then, +// simulate packet loss and check if PLC and PLC-to-CNG statistics are +// correctly updated. +TEST_F(VoENetEqStatsTest, NetEqCalls) { + AudioDecodingCallStats stats; + const int kNumNormalCalls = 10; + + AudioFrame audio_frame; + for (int num_calls = 0; num_calls < kNumNormalCalls; ++num_calls) { + InsertPacketAndPullAudio(); + } + ASSERT_EQ(0, + voe_neteq_stats_->GetDecodingCallStatistics(channel_acm1_, &stats)); + EXPECT_EQ(kNumNormalCalls, stats.calls_to_neteq); + EXPECT_EQ(0, stats.calls_to_silence_generator); + EXPECT_EQ(kNumNormalCalls, stats.decoded_normal); + EXPECT_EQ(0, stats.decoded_cng); + EXPECT_EQ(0, stats.decoded_plc); + EXPECT_EQ(0, stats.decoded_plc_cng); + + ASSERT_EQ(0, + voe_neteq_stats_->GetDecodingCallStatistics(channel_acm2_, &stats)); + EXPECT_EQ(kNumNormalCalls, stats.calls_to_neteq); + EXPECT_EQ(0, stats.calls_to_silence_generator); + EXPECT_EQ(kNumNormalCalls, stats.decoded_normal); + EXPECT_EQ(0, stats.decoded_cng); + EXPECT_EQ(0, stats.decoded_plc); + EXPECT_EQ(0, stats.decoded_plc_cng); + + const int kNumPlc = 3; + const int kNumPlcCng = 5; + + // Simulate packet-loss. NetEq first performs PLC then PLC fades to CNG. + for (int n = 0; n < kNumPlc + kNumPlcCng; ++n) { + JustPullAudio(); + } + ASSERT_EQ(0, + voe_neteq_stats_->GetDecodingCallStatistics(channel_acm1_, &stats)); + EXPECT_EQ(kNumNormalCalls + kNumPlc + kNumPlcCng, stats.calls_to_neteq); + EXPECT_EQ(0, stats.calls_to_silence_generator); + EXPECT_EQ(kNumNormalCalls, stats.decoded_normal); + EXPECT_EQ(0, stats.decoded_cng); + EXPECT_EQ(kNumPlc, stats.decoded_plc); + EXPECT_EQ(kNumPlcCng, stats.decoded_plc_cng); + + ASSERT_EQ(0, + voe_neteq_stats_->GetDecodingCallStatistics(channel_acm2_, &stats)); + EXPECT_EQ(kNumNormalCalls + kNumPlc + kNumPlcCng, stats.calls_to_neteq); + EXPECT_EQ(0, stats.calls_to_silence_generator); + EXPECT_EQ(kNumNormalCalls, stats.decoded_normal); + EXPECT_EQ(0, stats.decoded_cng); + EXPECT_EQ(kNumPlc, stats.decoded_plc); + EXPECT_EQ(kNumPlcCng, stats.decoded_plc_cng); +} + +} // namespace + +} // namespace voe + +} // namespace webrtc diff --git a/webrtc/voice_engine/voice_engine.gyp b/webrtc/voice_engine/voice_engine.gyp index 5e0007784c..8a06e86952 100644 --- a/webrtc/voice_engine/voice_engine.gyp +++ b/webrtc/voice_engine/voice_engine.gyp @@ -134,6 +134,7 @@ 'voe_audio_processing_unittest.cc', 'voe_base_unittest.cc', 'voe_codec_unittest.cc', + 'voe_neteq_stats_unittest.cc', ], 'conditions': [ # TODO(henrike): remove build_with_chromium==1 when the bots are