diff --git a/talk/app/webrtc/audiotrack.h b/talk/app/webrtc/audiotrack.h index fc09d2b9e..19db73f83 100644 --- a/talk/app/webrtc/audiotrack.h +++ b/talk/app/webrtc/audiotrack.h @@ -41,17 +41,21 @@ class AudioTrack : public MediaStreamTrack { static talk_base::scoped_refptr Create( const std::string& id, AudioSourceInterface* source); - virtual AudioSourceInterface* GetSource() const { + // AudioTrackInterface implementation. + virtual AudioSourceInterface* GetSource() const OVERRIDE { return audio_source_.get(); } - - // This method is used for supporting multiple sources/sinks for AudioTracks. - virtual cricket::AudioRenderer* GetRenderer() { + // TODO(xians): Implement these methods. + virtual void AddSink(AudioTrackSinkInterface* sink) OVERRIDE {} + virtual void RemoveSink(AudioTrackSinkInterface* sink) OVERRIDE {} + virtual bool GetSignalLevel(int* level) OVERRIDE { return false; } + virtual AudioProcessorInterface* GetAudioProcessor() OVERRIDE { return NULL; } + virtual cricket::AudioRenderer* GetRenderer() OVERRIDE { return NULL; } - // Implement MediaStreamTrack - virtual std::string kind() const; + // MediaStreamTrack implementation. + virtual std::string kind() const OVERRIDE; protected: AudioTrack(const std::string& label, AudioSourceInterface* audio_source); diff --git a/talk/app/webrtc/mediastreaminterface.h b/talk/app/webrtc/mediastreaminterface.h index fa0572e38..acd5bf612 100644 --- a/talk/app/webrtc/mediastreaminterface.h +++ b/talk/app/webrtc/mediastreaminterface.h @@ -174,18 +174,55 @@ class AudioTrackSinkInterface { virtual ~AudioTrackSinkInterface() {} }; +// Interface of the audio processor used by the audio track to collect +// statistics. +class AudioProcessorInterface : public talk_base::RefCountInterface { + public: + struct AudioProcessorStats { + AudioProcessorStats() : typing_noise_detected(false), + echo_return_loss(0), + echo_return_loss_enhancement(0), + echo_delay_median_ms(0), + aec_quality_min(0.0), + echo_delay_std_ms(0) {} + ~AudioProcessorStats() {} + + bool typing_noise_detected; + int echo_return_loss; + int echo_return_loss_enhancement; + int echo_delay_median_ms; + float aec_quality_min; + int echo_delay_std_ms; + }; + + // Get audio processor statistics. + virtual void GetStats(AudioProcessorStats* stats) = 0; + + protected: + virtual ~AudioProcessorInterface() {} +}; + class AudioTrackInterface : public MediaStreamTrackInterface { public: // TODO(xians): Figure out if the following interface should be const or not. virtual AudioSourceInterface* GetSource() const = 0; - // Adds/Removes a sink that will receive the audio data from the track. - // TODO(xians): Make them pure virtual after Chrome implements these - // interfaces. - virtual void AddSink(AudioTrackSinkInterface* sink) {} - virtual void RemoveSink(AudioTrackSinkInterface* sink) {} + // Add/Remove a sink that will receive the audio data from the track. + virtual void AddSink(AudioTrackSinkInterface* sink) = 0; + virtual void RemoveSink(AudioTrackSinkInterface* sink) = 0; - // Gets a pointer to the audio renderer of this AudioTrack. + // Get the signal level from the audio track. + // Return true on success, otherwise false. + // TODO(xians): Change the interface to int GetSignalLevel() and pure virtual + // after Chrome has the correct implementation of the interface. + virtual bool GetSignalLevel(int* level) { return false; } + + // Get the audio processor used by the audio track. Return NULL if the track + // does not have any processor. + // TODO(xians): Make the interface pure virtual. + virtual AudioProcessorInterface* GetAudioProcessor() { return NULL; } + + // Get a pointer to the audio renderer of this AudioTrack. // The pointer is valid for the lifetime of this AudioTrack. // TODO(xians): Remove the following interface after Chrome switches to // AddSink() and RemoveSink() interfaces. diff --git a/talk/app/webrtc/mediastreamsignaling.cc b/talk/app/webrtc/mediastreamsignaling.cc index 14648eee4..e1b33f102 100644 --- a/talk/app/webrtc/mediastreamsignaling.cc +++ b/talk/app/webrtc/mediastreamsignaling.cc @@ -321,8 +321,29 @@ bool MediaStreamSignaling::AddLocalStream(MediaStreamInterface* local_stream) { void MediaStreamSignaling::RemoveLocalStream( MediaStreamInterface* local_stream) { - local_streams_->RemoveStream(local_stream); + AudioTrackVector audio_tracks = local_stream->GetAudioTracks(); + for (AudioTrackVector::const_iterator it = audio_tracks.begin(); + it != audio_tracks.end(); ++it) { + const TrackInfo* track_info = FindTrackInfo(local_audio_tracks_, + local_stream->label(), + (*it)->id()); + if (track_info) { + stream_observer_->OnRemoveLocalAudioTrack(local_stream, *it, + track_info->ssrc); + } + } + VideoTrackVector video_tracks = local_stream->GetVideoTracks(); + for (VideoTrackVector::const_iterator it = video_tracks.begin(); + it != video_tracks.end(); ++it) { + const TrackInfo* track_info = FindTrackInfo(local_video_tracks_, + local_stream->label(), + (*it)->id()); + if (track_info) { + stream_observer_->OnRemoveLocalVideoTrack(local_stream, *it); + } + } + local_streams_->RemoveStream(local_stream); stream_observer_->OnRemoveLocalStream(local_stream); } @@ -725,7 +746,8 @@ void MediaStreamSignaling::UpdateLocalTracks( cricket::StreamParams params; if (!cricket::GetStreamBySsrc(streams, info.ssrc, ¶ms) || params.id != info.track_id || params.sync_label != info.stream_label) { - OnLocalTrackRemoved(info.stream_label, info.track_id, media_type); + OnLocalTrackRemoved(info.stream_label, info.track_id, info.ssrc, + media_type); track_it = current_tracks->erase(track_it); } else { ++track_it; @@ -787,6 +809,7 @@ void MediaStreamSignaling::OnLocalTrackSeen( void MediaStreamSignaling::OnLocalTrackRemoved( const std::string& stream_label, const std::string& track_id, + uint32 ssrc, cricket::MediaType media_type) { MediaStreamInterface* stream = local_streams_->find(stream_label); if (!stream) { @@ -803,7 +826,7 @@ void MediaStreamSignaling::OnLocalTrackRemoved( if (!audio_track) { return; } - stream_observer_->OnRemoveLocalAudioTrack(stream, audio_track); + stream_observer_->OnRemoveLocalAudioTrack(stream, audio_track, ssrc); } else if (media_type == cricket::MEDIA_TYPE_VIDEO) { VideoTrackInterface* video_track = stream->FindVideoTrack(track_id); if (!video_track) { diff --git a/talk/app/webrtc/mediastreamsignaling.h b/talk/app/webrtc/mediastreamsignaling.h index c730b468d..dc83b47da 100644 --- a/talk/app/webrtc/mediastreamsignaling.h +++ b/talk/app/webrtc/mediastreamsignaling.h @@ -92,7 +92,8 @@ class MediaStreamSignalingObserver { // Triggered when the local SessionDescription has removed an audio track. virtual void OnRemoveLocalAudioTrack(MediaStreamInterface* stream, - AudioTrackInterface* audio_track) = 0; + AudioTrackInterface* audio_track, + uint32 ssrc) = 0; // Triggered when the local SessionDescription has removed a video track. virtual void OnRemoveLocalVideoTrack(MediaStreamInterface* stream, @@ -354,6 +355,7 @@ class MediaStreamSignaling { // MediaStreamTrack in a MediaStream in |local_streams_|. void OnLocalTrackRemoved(const std::string& stream_label, const std::string& track_id, + uint32 ssrc, cricket::MediaType media_type); void UpdateLocalRtpDataChannels(const cricket::StreamParamsVec& streams); diff --git a/talk/app/webrtc/mediastreamsignaling_unittest.cc b/talk/app/webrtc/mediastreamsignaling_unittest.cc index 49625ef52..03452738b 100644 --- a/talk/app/webrtc/mediastreamsignaling_unittest.cc +++ b/talk/app/webrtc/mediastreamsignaling_unittest.cc @@ -298,7 +298,8 @@ class MockSignalingObserver : public webrtc::MediaStreamSignalingObserver { } virtual void OnRemoveLocalAudioTrack(MediaStreamInterface* stream, - AudioTrackInterface* audio_track) { + AudioTrackInterface* audio_track, + uint32 ssrc) { RemoveTrack(&local_audio_tracks_, stream, audio_track); } diff --git a/talk/app/webrtc/mediastreamtrackproxy.h b/talk/app/webrtc/mediastreamtrackproxy.h index 7c622e733..f12522e55 100644 --- a/talk/app/webrtc/mediastreamtrackproxy.h +++ b/talk/app/webrtc/mediastreamtrackproxy.h @@ -42,6 +42,10 @@ BEGIN_PROXY_MAP(AudioTrack) PROXY_CONSTMETHOD0(TrackState, state) PROXY_CONSTMETHOD0(bool, enabled) PROXY_CONSTMETHOD0(AudioSourceInterface*, GetSource) + PROXY_METHOD1(void, AddSink, AudioTrackSinkInterface*) + PROXY_METHOD1(void, RemoveSink, AudioTrackSinkInterface*) + PROXY_METHOD1(bool, GetSignalLevel, int*) + PROXY_METHOD0(AudioProcessorInterface*, GetAudioProcessor) PROXY_METHOD0(cricket::AudioRenderer*, GetRenderer) PROXY_METHOD1(bool, set_enabled, bool) diff --git a/talk/app/webrtc/peerconnection.cc b/talk/app/webrtc/peerconnection.cc index 72f37174b..af33d02dd 100644 --- a/talk/app/webrtc/peerconnection.cc +++ b/talk/app/webrtc/peerconnection.cc @@ -706,6 +706,7 @@ void PeerConnection::OnAddLocalAudioTrack(MediaStreamInterface* stream, AudioTrackInterface* audio_track, uint32 ssrc) { stream_handler_container_->AddLocalAudioTrack(stream, audio_track, ssrc); + stats_.AddLocalAudioTrack(audio_track, ssrc); } void PeerConnection::OnAddLocalVideoTrack(MediaStreamInterface* stream, VideoTrackInterface* video_track, @@ -714,8 +715,10 @@ void PeerConnection::OnAddLocalVideoTrack(MediaStreamInterface* stream, } void PeerConnection::OnRemoveLocalAudioTrack(MediaStreamInterface* stream, - AudioTrackInterface* audio_track) { + AudioTrackInterface* audio_track, + uint32 ssrc) { stream_handler_container_->RemoveLocalTrack(stream, audio_track); + stats_.RemoveLocalAudioTrack(audio_track, ssrc); } void PeerConnection::OnRemoveLocalVideoTrack(MediaStreamInterface* stream, diff --git a/talk/app/webrtc/peerconnection.h b/talk/app/webrtc/peerconnection.h index 70155d992..b4c45c316 100644 --- a/talk/app/webrtc/peerconnection.h +++ b/talk/app/webrtc/peerconnection.h @@ -136,7 +136,8 @@ class PeerConnection : public PeerConnectionInterface, uint32 ssrc) OVERRIDE; virtual void OnRemoveLocalAudioTrack( MediaStreamInterface* stream, - AudioTrackInterface* audio_track) OVERRIDE; + AudioTrackInterface* audio_track, + uint32 ssrc) OVERRIDE; virtual void OnRemoveLocalVideoTrack( MediaStreamInterface* stream, VideoTrackInterface* video_track) OVERRIDE; diff --git a/talk/app/webrtc/statscollector.cc b/talk/app/webrtc/statscollector.cc index 4d5b32418..6af1e16ad 100644 --- a/talk/app/webrtc/statscollector.cc +++ b/talk/app/webrtc/statscollector.cc @@ -201,6 +201,19 @@ void StatsReport::AddBoolean(const std::string& name, bool value) { AddValue(name, value ? "true" : "false"); } +void StatsReport::ReplaceValue(const std::string& name, + const std::string& value) { + for (Values::iterator it = values.begin(); it != values.end(); ++it) { + if ((*it).name == name) { + it->value = value; + return; + } + } + // It is not reachable here, add an ASSERT to make sure the overwriting is + // always a success. + ASSERT(false); +} + namespace { typedef std::map StatsMap; @@ -458,6 +471,32 @@ void StatsCollector::AddStream(MediaStreamInterface* stream) { &reports_); } +void StatsCollector::AddLocalAudioTrack(AudioTrackInterface* audio_track, + uint32 ssrc) { + ASSERT(audio_track != NULL); +#ifdef _DEBUG + for (LocalAudioTrackVector::iterator it = local_audio_tracks_.begin(); + it != local_audio_tracks_.end(); ++it) { + ASSERT(it->first != audio_track || it->second != ssrc); + } +#endif + local_audio_tracks_.push_back(std::make_pair(audio_track, ssrc)); +} + +void StatsCollector::RemoveLocalAudioTrack(AudioTrackInterface* audio_track, + uint32 ssrc) { + ASSERT(audio_track != NULL); + for (LocalAudioTrackVector::iterator it = local_audio_tracks_.begin(); + it != local_audio_tracks_.end(); ++it) { + if (it->first == audio_track && it->second == ssrc) { + local_audio_tracks_.erase(it); + return; + } + } + + ASSERT(false); +} + bool StatsCollector::GetStats(MediaStreamTrackInterface* track, StatsReports* reports) { ASSERT(reports != NULL); @@ -784,6 +823,8 @@ void StatsCollector::ExtractVoiceInfo() { } ExtractStatsFromList(voice_info.receivers, transport_id, this); ExtractStatsFromList(voice_info.senders, transport_id, this); + + UpdateStatsFromExistingLocalAudioTracks(); } void StatsCollector::ExtractVideoInfo( @@ -840,19 +881,86 @@ bool StatsCollector::GetTransportIdFromProxy(const std::string& proxy, return true; } -StatsReport* StatsCollector::GetOrCreateReport(const std::string& type, - const std::string& id) { +StatsReport* StatsCollector::GetReport(const std::string& type, + const std::string& id) { std::string statsid = StatsId(type, id); StatsReport* report = NULL; std::map::iterator it = reports_.find(statsid); - if (it == reports_.end()) { - report = &reports_[statsid]; // Create new element. - report->id = statsid; - report->type = type; - } else { + if (it != reports_.end()) report = &(it->second); - } + return report; } +StatsReport* StatsCollector::GetOrCreateReport(const std::string& type, + const std::string& id) { + StatsReport* report = GetReport(type, id); + if (report == NULL) { + std::string statsid = StatsId(type, id); + report = &reports_[statsid]; // Create new element. + report->id = statsid; + report->type = type; + } + + return report; +} + +void StatsCollector::UpdateStatsFromExistingLocalAudioTracks() { + // Loop through the existing local audio tracks. + for (LocalAudioTrackVector::const_iterator it = local_audio_tracks_.begin(); + it != local_audio_tracks_.end(); ++it) { + AudioTrackInterface* track = it->first; + uint32 ssrc = it->second; + std::string ssrc_id = talk_base::ToString(ssrc); + StatsReport* report = GetReport(StatsReport::kStatsReportTypeSsrc, + ssrc_id); + ASSERT(report != NULL); + + // The same ssrc can be used by both local and remote audio tracks. + std::string track_id; + if (!ExtractValueFromReport(*report, + StatsReport::kStatsValueNameTrackId, + &track_id) || + track_id != track->id()) { + continue; + } + + UpdateReportFromAudioTrack(track, report); + } +} + +void StatsCollector::UpdateReportFromAudioTrack(AudioTrackInterface* track, + StatsReport* report) { + ASSERT(track != NULL); + if (report == NULL) + return; + + int signal_level = 0; + if (track->GetSignalLevel(&signal_level)) { + report->ReplaceValue(StatsReport::kStatsValueNameAudioInputLevel, + talk_base::ToString(signal_level)); + } + + talk_base::scoped_refptr audio_processor( + track->GetAudioProcessor()); + if (audio_processor.get() == NULL) + return; + + AudioProcessorInterface::AudioProcessorStats stats; + audio_processor->GetStats(&stats); + report->ReplaceValue(StatsReport::kStatsValueNameTypingNoiseState, + stats.typing_noise_detected ? "true" : "false"); + report->ReplaceValue(StatsReport::kStatsValueNameEchoReturnLoss, + talk_base::ToString(stats.echo_return_loss)); + report->ReplaceValue( + StatsReport::kStatsValueNameEchoReturnLossEnhancement, + talk_base::ToString(stats.echo_return_loss_enhancement)); + report->ReplaceValue(StatsReport::kStatsValueNameEchoDelayMedian, + talk_base::ToString(stats.echo_delay_median_ms)); + report->ReplaceValue(StatsReport::kStatsValueNameEchoCancellationQualityMin, + talk_base::ToString(stats.aec_quality_min)); + report->ReplaceValue(StatsReport::kStatsValueNameEchoDelayStdDev, + talk_base::ToString(stats.echo_delay_std_ms)); +} + } // namespace webrtc diff --git a/talk/app/webrtc/statscollector.h b/talk/app/webrtc/statscollector.h index 6256d7786..fdb296157 100644 --- a/talk/app/webrtc/statscollector.h +++ b/talk/app/webrtc/statscollector.h @@ -31,8 +31,9 @@ #ifndef TALK_APP_WEBRTC_STATSCOLLECTOR_H_ #define TALK_APP_WEBRTC_STATSCOLLECTOR_H_ -#include #include +#include +#include #include "talk/app/webrtc/mediastreaminterface.h" #include "talk/app/webrtc/peerconnectioninterface.h" @@ -57,6 +58,13 @@ class StatsCollector { // to GetStats. void AddStream(MediaStreamInterface* stream); + // Adds a local audio track that is used for getting some voice statistics. + void AddLocalAudioTrack(AudioTrackInterface* audio_track, uint32 ssrc); + + // Removes a local audio tracks that is used for getting some voice + // statistics. + void RemoveLocalAudioTrack(AudioTrackInterface* audio_track, uint32 ssrc); + // Gather statistics from the session and store them for future use. void UpdateStats(PeerConnectionInterface::StatsOutputLevel level); @@ -95,6 +103,13 @@ class StatsCollector { WebRtcSession* session() { return session_; } webrtc::StatsReport* GetOrCreateReport(const std::string& type, const std::string& id); + webrtc::StatsReport* GetReport(const std::string& type, + const std::string& id); + + // Helper method to get stats from the local audio tracks. + void UpdateStatsFromExistingLocalAudioTracks(); + void UpdateReportFromAudioTrack(AudioTrackInterface* track, + StatsReport* report); // A map from the report id to the report. std::map reports_; @@ -103,6 +118,10 @@ class StatsCollector { double stats_gathering_started_; talk_base::Timing timing_; cricket::ProxyTransportMap proxy_to_transport_; + + typedef std::vector > + LocalAudioTrackVector; + LocalAudioTrackVector local_audio_tracks_; }; } // namespace webrtc diff --git a/talk/app/webrtc/statscollector_unittest.cc b/talk/app/webrtc/statscollector_unittest.cc index a7cda1635..83d214c46 100644 --- a/talk/app/webrtc/statscollector_unittest.cc +++ b/talk/app/webrtc/statscollector_unittest.cc @@ -29,6 +29,8 @@ #include "talk/app/webrtc/statscollector.h" #include "talk/app/webrtc/mediastream.h" +#include "talk/app/webrtc/mediastreaminterface.h" +#include "talk/app/webrtc/mediastreamtrack.h" #include "talk/app/webrtc/videotrack.h" #include "talk/base/base64.h" #include "talk/base/fakesslidentity.h" @@ -47,6 +49,8 @@ using testing::Return; using testing::ReturnNull; using testing::SetArgPointee; using webrtc::PeerConnectionInterface; +using webrtc::StatsReport; +using webrtc::StatsReports; namespace cricket { @@ -63,6 +67,7 @@ const char kNoReports[] = "NO REPORTS"; // Constant names for track identification. const char kTrackId[] = "somename"; +const char kAudioTrackId[] = "audio_track_id"; const uint32 kSsrcOfTrack = 1234; class MockWebRtcSession : public webrtc::WebRtcSession { @@ -71,6 +76,7 @@ class MockWebRtcSession : public webrtc::WebRtcSession { : WebRtcSession(channel_manager, talk_base::Thread::Current(), talk_base::Thread::Current(), NULL, NULL) { } + MOCK_METHOD0(voice_channel, cricket::VoiceChannel*()); MOCK_METHOD0(video_channel, cricket::VideoChannel*()); MOCK_METHOD2(GetTrackIdBySsrc, bool(uint32, std::string*)); MOCK_METHOD1(GetStats, bool(cricket::SessionStats*)); @@ -86,10 +92,60 @@ class MockVideoMediaChannel : public cricket::FakeVideoMediaChannel { MOCK_METHOD2(GetStats, bool(const StatsOptions&, cricket::VideoMediaInfo*)); }; -bool GetValue(const webrtc::StatsReport* report, +class MockVoiceMediaChannel : public cricket::FakeVoiceMediaChannel { + public: + MockVoiceMediaChannel() : cricket::FakeVoiceMediaChannel(NULL) { + } + MOCK_METHOD1(GetStats, bool(cricket::VoiceMediaInfo*)); +}; + +class FakeAudioProcessor : public webrtc::AudioProcessorInterface { + public: + FakeAudioProcessor() {} + ~FakeAudioProcessor() {} + + private: + virtual void GetStats( + AudioProcessorInterface::AudioProcessorStats* stats) OVERRIDE { + stats->typing_noise_detected = true; + stats->echo_return_loss = 2; + stats->echo_return_loss_enhancement = 3; + stats->echo_delay_median_ms = 4; + stats->aec_quality_min = 5.1f; + stats->echo_delay_std_ms = 6; + } +}; + +class FakeLocalAudioTrack + : public webrtc::MediaStreamTrack { + public: + explicit FakeLocalAudioTrack(const std::string& id) + : webrtc::MediaStreamTrack(id), + processor_(new talk_base::RefCountedObject()) {} + std::string kind() const OVERRIDE { + return "audio"; + } + virtual webrtc::AudioSourceInterface* GetSource() const OVERRIDE { + return NULL; + } + virtual void AddSink(webrtc::AudioTrackSinkInterface* sink) OVERRIDE {} + virtual void RemoveSink(webrtc::AudioTrackSinkInterface* sink) OVERRIDE {} + virtual bool GetSignalLevel(int* level) OVERRIDE { + *level = 1; + return true; + } + virtual webrtc::AudioProcessorInterface* GetAudioProcessor() OVERRIDE { + return processor_.get(); + } + + private: + talk_base::scoped_refptr processor_; +}; + +bool GetValue(const StatsReport* report, const std::string& name, std::string* value) { - webrtc::StatsReport::Values::const_iterator it = report->values.begin(); + StatsReport::Values::const_iterator it = report->values.begin(); for (; it != report->values.end(); ++it) { if (it->name == name) { *value = it->value; @@ -100,7 +156,7 @@ bool GetValue(const webrtc::StatsReport* report, } std::string ExtractStatsValue(const std::string& type, - const webrtc::StatsReports& reports, + const StatsReports& reports, const std::string name) { if (reports.empty()) { return kNoReports; @@ -119,8 +175,8 @@ std::string ExtractStatsValue(const std::string& type, // Finds the |n|-th report of type |type| in |reports|. // |n| starts from 1 for finding the first report. -const webrtc::StatsReport* FindNthReportByType( - const webrtc::StatsReports& reports, const std::string& type, int n) { +const StatsReport* FindNthReportByType( + const StatsReports& reports, const std::string& type, int n) { for (size_t i = 0; i < reports.size(); ++i) { if (reports[i].type == type) { n--; @@ -131,8 +187,8 @@ const webrtc::StatsReport* FindNthReportByType( return NULL; } -const webrtc::StatsReport* FindReportById(const webrtc::StatsReports& reports, - const std::string& id) { +const StatsReport* FindReportById(const StatsReports& reports, + const std::string& id) { for (size_t i = 0; i < reports.size(); ++i) { if (reports[i].id == id) { return &reports[i]; @@ -141,16 +197,16 @@ const webrtc::StatsReport* FindReportById(const webrtc::StatsReports& reports, return NULL; } -std::string ExtractSsrcStatsValue(webrtc::StatsReports reports, +std::string ExtractSsrcStatsValue(StatsReports reports, const std::string& name) { return ExtractStatsValue( - webrtc::StatsReport::kStatsReportTypeSsrc, reports, name); + StatsReport::kStatsReportTypeSsrc, reports, name); } -std::string ExtractBweStatsValue(webrtc::StatsReports reports, +std::string ExtractBweStatsValue(StatsReports reports, const std::string& name) { return ExtractStatsValue( - webrtc::StatsReport::kStatsReportTypeBwe, reports, name); + StatsReport::kStatsReportTypeBwe, reports, name); } std::string DerToPem(const std::string& der) { @@ -167,18 +223,18 @@ std::vector DersToPems( return pems; } -void CheckCertChainReports(const webrtc::StatsReports& reports, +void CheckCertChainReports(const StatsReports& reports, const std::vector& ders, const std::string& start_id) { std::string certificate_id = start_id; size_t i = 0; while (true) { - const webrtc::StatsReport* report = FindReportById(reports, certificate_id); + const StatsReport* report = FindReportById(reports, certificate_id); ASSERT_TRUE(report != NULL); std::string der_base64; EXPECT_TRUE(GetValue( - report, webrtc::StatsReport::kStatsValueNameDer, &der_base64)); + report, StatsReport::kStatsValueNameDer, &der_base64)); std::string der = talk_base::Base64::Decode(der_base64, talk_base::Base64::DO_STRICT); EXPECT_EQ(ders[i], der); @@ -186,7 +242,7 @@ void CheckCertChainReports(const webrtc::StatsReports& reports, std::string fingerprint_algorithm; EXPECT_TRUE(GetValue( report, - webrtc::StatsReport::kStatsValueNameFingerprintAlgorithm, + StatsReport::kStatsValueNameFingerprintAlgorithm, &fingerprint_algorithm)); // The digest algorithm for a FakeSSLCertificate is always SHA-1. std::string sha_1_str = talk_base::DIGEST_SHA_1; @@ -195,17 +251,68 @@ void CheckCertChainReports(const webrtc::StatsReports& reports, std::string dummy_fingerprint; // Value is not checked. EXPECT_TRUE(GetValue( report, - webrtc::StatsReport::kStatsValueNameFingerprint, + StatsReport::kStatsValueNameFingerprint, &dummy_fingerprint)); ++i; if (!GetValue( - report, webrtc::StatsReport::kStatsValueNameIssuerId, &certificate_id)) + report, StatsReport::kStatsValueNameIssuerId, &certificate_id)) break; } EXPECT_EQ(ders.size(), i); } +void VerifyVoiceSenderInfoReport(const StatsReport* report, + const cricket::VoiceSenderInfo& sinfo) { + std::string value_in_report; + EXPECT_TRUE(GetValue( + report, StatsReport::kStatsValueNameCodecName, &value_in_report)); + EXPECT_EQ(sinfo.codec_name, value_in_report); + EXPECT_TRUE(GetValue( + report, StatsReport::kStatsValueNameBytesSent, &value_in_report)); + EXPECT_EQ(talk_base::ToString(sinfo.bytes_sent), value_in_report); + EXPECT_TRUE(GetValue( + report, StatsReport::kStatsValueNamePacketsSent, &value_in_report)); + EXPECT_EQ(talk_base::ToString(sinfo.packets_sent), value_in_report); + EXPECT_TRUE(GetValue( + report, StatsReport::kStatsValueNameRtt, &value_in_report)); + EXPECT_EQ(talk_base::ToString(sinfo.rtt_ms), value_in_report); + EXPECT_TRUE(GetValue( + report, StatsReport::kStatsValueNameRtt, &value_in_report)); + EXPECT_EQ(talk_base::ToString(sinfo.rtt_ms), value_in_report); + EXPECT_TRUE(GetValue( + report, StatsReport::kStatsValueNameJitterReceived, &value_in_report)); + EXPECT_EQ(talk_base::ToString(sinfo.jitter_ms), value_in_report); + EXPECT_TRUE(GetValue( + report, StatsReport::kStatsValueNameEchoCancellationQualityMin, + &value_in_report)); + EXPECT_EQ(talk_base::ToString(sinfo.aec_quality_min), value_in_report); + EXPECT_TRUE(GetValue( + report, StatsReport::kStatsValueNameEchoDelayMedian, &value_in_report)); + EXPECT_EQ(talk_base::ToString(sinfo.echo_delay_median_ms), + value_in_report); + EXPECT_TRUE(GetValue( + report, StatsReport::kStatsValueNameEchoDelayStdDev, &value_in_report)); + EXPECT_EQ(talk_base::ToString(sinfo.echo_delay_std_ms), + value_in_report); + EXPECT_TRUE(GetValue( + report, StatsReport::kStatsValueNameEchoReturnLoss, &value_in_report)); + EXPECT_EQ(talk_base::ToString(sinfo.echo_return_loss), + value_in_report); + EXPECT_TRUE(GetValue( + report, StatsReport::kStatsValueNameEchoReturnLossEnhancement, + &value_in_report)); + EXPECT_EQ(talk_base::ToString(sinfo.echo_return_loss_enhancement), + value_in_report); + EXPECT_TRUE(GetValue( + report, StatsReport::kStatsValueNameAudioInputLevel, &value_in_report)); + EXPECT_EQ(talk_base::ToString(sinfo.audio_level), value_in_report); + EXPECT_TRUE(GetValue( + report, StatsReport::kStatsValueNameTypingNoiseState, &value_in_report)); + std::string typing_detected = sinfo.typing_noise_detected ? "true" : "false"; + EXPECT_EQ(typing_detected, value_in_report); +} + class StatsCollectorTest : public testing::Test { protected: StatsCollectorTest() @@ -245,12 +352,25 @@ class StatsCollectorTest : public testing::Test { Return(true))); } + // Adds a local audio track with a given SSRC into the stats. + void AddLocalAudioTrackStats() { + if (stream_ == NULL) + stream_ = webrtc::MediaStream::Create("streamlabel"); + + audio_track_ = + new talk_base::RefCountedObject(kAudioTrackId); + stream_->AddTrack(audio_track_); + EXPECT_CALL(session_, GetTrackIdBySsrc(kSsrcOfTrack, _)) + .WillRepeatedly(DoAll(SetArgPointee<1>(kAudioTrackId), + Return(true))); + } + void TestCertificateReports(const talk_base::FakeSSLCertificate& local_cert, const std::vector& local_ders, const talk_base::FakeSSLCertificate& remote_cert, const std::vector& remote_ders) { webrtc::StatsCollector stats; // Implementation under test. - webrtc::StatsReports reports; // returned values. + StatsReports reports; // returned values. stats.set_session(&session_); // Fake stats to process. @@ -289,22 +409,22 @@ class StatsCollectorTest : public testing::Test { EXPECT_CALL(session_, GetStats(_)) .WillOnce(DoAll(SetArgPointee<0>(session_stats), Return(true))); - EXPECT_CALL(session_, video_channel()) - .WillRepeatedly(ReturnNull()); + EXPECT_CALL(session_, video_channel()).WillRepeatedly(ReturnNull()); + EXPECT_CALL(session_, voice_channel()).WillRepeatedly(ReturnNull()); stats.UpdateStats(PeerConnectionInterface::kStatsOutputLevelStandard); stats.GetStats(NULL, &reports); - const webrtc::StatsReport* channel_report = FindNthReportByType( - reports, webrtc::StatsReport::kStatsReportTypeComponent, 1); + const StatsReport* channel_report = FindNthReportByType( + reports, StatsReport::kStatsReportTypeComponent, 1); EXPECT_TRUE(channel_report != NULL); // Check local certificate chain. std::string local_certificate_id = ExtractStatsValue( - webrtc::StatsReport::kStatsReportTypeComponent, + StatsReport::kStatsReportTypeComponent, reports, - webrtc::StatsReport::kStatsValueNameLocalCertificateId); + StatsReport::kStatsValueNameLocalCertificateId); if (local_ders.size() > 0) { EXPECT_NE(kNotFound, local_certificate_id); CheckCertChainReports(reports, local_ders, local_certificate_id); @@ -314,9 +434,9 @@ class StatsCollectorTest : public testing::Test { // Check remote certificate chain. std::string remote_certificate_id = ExtractStatsValue( - webrtc::StatsReport::kStatsReportTypeComponent, + StatsReport::kStatsReportTypeComponent, reports, - webrtc::StatsReport::kStatsValueNameRemoteCertificateId); + StatsReport::kStatsValueNameRemoteCertificateId); if (remote_ders.size() > 0) { EXPECT_NE(kNotFound, remote_certificate_id); CheckCertChainReports(reports, remote_ders, remote_certificate_id); @@ -331,6 +451,7 @@ class StatsCollectorTest : public testing::Test { cricket::SessionStats session_stats_; talk_base::scoped_refptr stream_; talk_base::scoped_refptr track_; + talk_base::scoped_refptr audio_track_; std::string track_id_; }; @@ -340,7 +461,7 @@ TEST_F(StatsCollectorTest, BytesCounterHandles64Bits) { MockVideoMediaChannel* media_channel = new MockVideoMediaChannel; cricket::VideoChannel video_channel(talk_base::Thread::Current(), media_engine_, media_channel, &session_, "", false, NULL); - webrtc::StatsReports reports; // returned values. + StatsReports reports; // returned values. cricket::VideoSenderInfo video_sender_info; cricket::VideoMediaInfo stats_read; // The number of bytes must be larger than 0xFFFFFFFF for this test. @@ -356,8 +477,8 @@ TEST_F(StatsCollectorTest, BytesCounterHandles64Bits) { video_sender_info.bytes_sent = kBytesSent; stats_read.senders.push_back(video_sender_info); - EXPECT_CALL(session_, video_channel()) - .WillRepeatedly(Return(&video_channel)); + EXPECT_CALL(session_, video_channel()).WillRepeatedly(Return(&video_channel)); + EXPECT_CALL(session_, voice_channel()).WillRepeatedly(ReturnNull()); EXPECT_CALL(*media_channel, GetStats(_, _)) .WillOnce(DoAll(SetArgPointee<1>(stats_read), Return(true))); @@ -373,7 +494,7 @@ TEST_F(StatsCollectorTest, BandwidthEstimationInfoIsReported) { MockVideoMediaChannel* media_channel = new MockVideoMediaChannel; cricket::VideoChannel video_channel(talk_base::Thread::Current(), media_engine_, media_channel, &session_, "", false, NULL); - webrtc::StatsReports reports; // returned values. + StatsReports reports; // returned values. cricket::VideoSenderInfo video_sender_info; cricket::VideoMediaInfo stats_read; // Set up an SSRC just to test that we get both kinds of stats back: SSRC and @@ -395,8 +516,8 @@ TEST_F(StatsCollectorTest, BandwidthEstimationInfoIsReported) { bwe.target_enc_bitrate = kTargetEncBitrate; stats_read.bw_estimations.push_back(bwe); - EXPECT_CALL(session_, video_channel()) - .WillRepeatedly(Return(&video_channel)); + EXPECT_CALL(session_, video_channel()).WillRepeatedly(Return(&video_channel)); + EXPECT_CALL(session_, voice_channel()).WillRepeatedly(ReturnNull()); EXPECT_CALL(*media_channel, GetStats(_, _)) .WillOnce(DoAll(SetArgPointee<1>(stats_read), Return(true))); @@ -413,14 +534,14 @@ TEST_F(StatsCollectorTest, BandwidthEstimationInfoIsReported) { // exists in the returned stats. TEST_F(StatsCollectorTest, SessionObjectExists) { webrtc::StatsCollector stats; // Implementation under test. - webrtc::StatsReports reports; // returned values. + StatsReports reports; // returned values. stats.set_session(&session_); - EXPECT_CALL(session_, video_channel()) - .WillRepeatedly(ReturnNull()); + EXPECT_CALL(session_, video_channel()).WillRepeatedly(ReturnNull()); + EXPECT_CALL(session_, voice_channel()).WillRepeatedly(ReturnNull()); stats.UpdateStats(PeerConnectionInterface::kStatsOutputLevelStandard); stats.GetStats(NULL, &reports); - const webrtc::StatsReport* session_report = FindNthReportByType( - reports, webrtc::StatsReport::kStatsReportTypeSession, 1); + const StatsReport* session_report = FindNthReportByType( + reports, StatsReport::kStatsReportTypeSession, 1); EXPECT_FALSE(session_report == NULL); } @@ -428,18 +549,18 @@ TEST_F(StatsCollectorTest, SessionObjectExists) { // in the returned stats. TEST_F(StatsCollectorTest, OnlyOneSessionObjectExists) { webrtc::StatsCollector stats; // Implementation under test. - webrtc::StatsReports reports; // returned values. + StatsReports reports; // returned values. stats.set_session(&session_); - EXPECT_CALL(session_, video_channel()) - .WillRepeatedly(ReturnNull()); + EXPECT_CALL(session_, video_channel()).WillRepeatedly(ReturnNull()); + EXPECT_CALL(session_, voice_channel()).WillRepeatedly(ReturnNull()); stats.UpdateStats(PeerConnectionInterface::kStatsOutputLevelStandard); stats.UpdateStats(PeerConnectionInterface::kStatsOutputLevelStandard); stats.GetStats(NULL, &reports); - const webrtc::StatsReport* session_report = FindNthReportByType( - reports, webrtc::StatsReport::kStatsReportTypeSession, 1); + const StatsReport* session_report = FindNthReportByType( + reports, StatsReport::kStatsReportTypeSession, 1); EXPECT_FALSE(session_report == NULL); session_report = FindNthReportByType( - reports, webrtc::StatsReport::kStatsReportTypeSession, 2); + reports, StatsReport::kStatsReportTypeSession, 2); EXPECT_EQ(NULL, session_report); } @@ -455,18 +576,18 @@ TEST_F(StatsCollectorTest, TrackObjectExistsWithoutUpdateStats) { stats.set_session(&session_); - webrtc::StatsReports reports; + StatsReports reports; // Verfies the existence of the track report. stats.GetStats(NULL, &reports); EXPECT_EQ((size_t)1, reports.size()); - EXPECT_EQ(std::string(webrtc::StatsReport::kStatsReportTypeTrack), + EXPECT_EQ(std::string(StatsReport::kStatsReportTypeTrack), reports[0].type); std::string trackValue = - ExtractStatsValue(webrtc::StatsReport::kStatsReportTypeTrack, + ExtractStatsValue(StatsReport::kStatsReportTypeTrack, reports, - webrtc::StatsReport::kStatsValueNameTrackId); + StatsReport::kStatsValueNameTrackId); EXPECT_EQ(kTrackId, trackValue); } @@ -482,7 +603,7 @@ TEST_F(StatsCollectorTest, TrackAndSsrcObjectExistAfterUpdateSsrcStats) { stats.set_session(&session_); - webrtc::StatsReports reports; + StatsReports reports; // Constructs an ssrc stats update. cricket::VideoSenderInfo video_sender_info; @@ -494,8 +615,8 @@ TEST_F(StatsCollectorTest, TrackAndSsrcObjectExistAfterUpdateSsrcStats) { video_sender_info.bytes_sent = kBytesSent; stats_read.senders.push_back(video_sender_info); - EXPECT_CALL(session_, video_channel()) - .WillRepeatedly(Return(&video_channel)); + EXPECT_CALL(session_, video_channel()).WillRepeatedly(Return(&video_channel)); + EXPECT_CALL(session_, voice_channel()).WillRepeatedly(ReturnNull()); EXPECT_CALL(*media_channel, GetStats(_, _)) .WillOnce(DoAll(SetArgPointee<1>(stats_read), Return(true))); @@ -505,8 +626,8 @@ TEST_F(StatsCollectorTest, TrackAndSsrcObjectExistAfterUpdateSsrcStats) { // |reports| should contain at least one session report, one track report, // and one ssrc report. EXPECT_LE((size_t)3, reports.size()); - const webrtc::StatsReport* track_report = FindNthReportByType( - reports, webrtc::StatsReport::kStatsReportTypeTrack, 1); + const StatsReport* track_report = FindNthReportByType( + reports, StatsReport::kStatsReportTypeTrack, 1); EXPECT_FALSE(track_report == NULL); stats.GetStats(track_, &reports); @@ -514,15 +635,15 @@ TEST_F(StatsCollectorTest, TrackAndSsrcObjectExistAfterUpdateSsrcStats) { // and one ssrc report. EXPECT_LE((size_t)3, reports.size()); track_report = FindNthReportByType( - reports, webrtc::StatsReport::kStatsReportTypeTrack, 1); + reports, StatsReport::kStatsReportTypeTrack, 1); EXPECT_FALSE(track_report == NULL); std::string ssrc_id = ExtractSsrcStatsValue( - reports, webrtc::StatsReport::kStatsValueNameSsrc); + reports, StatsReport::kStatsValueNameSsrc); EXPECT_EQ(talk_base::ToString(kSsrcOfTrack), ssrc_id); std::string track_id = ExtractSsrcStatsValue( - reports, webrtc::StatsReport::kStatsValueNameTrackId); + reports, StatsReport::kStatsValueNameTrackId); EXPECT_EQ(kTrackId, track_id); } @@ -540,7 +661,7 @@ TEST_F(StatsCollectorTest, TransportObjectLinkedFromSsrcObject) { stats.set_session(&session_); - webrtc::StatsReports reports; + StatsReports reports; // Constructs an ssrc stats update. cricket::VideoSenderInfo video_sender_info; @@ -552,26 +673,26 @@ TEST_F(StatsCollectorTest, TransportObjectLinkedFromSsrcObject) { video_sender_info.bytes_sent = kBytesSent; stats_read.senders.push_back(video_sender_info); - EXPECT_CALL(session_, video_channel()) - .WillRepeatedly(Return(&video_channel)); + EXPECT_CALL(session_, video_channel()).WillRepeatedly(Return(&video_channel)); + EXPECT_CALL(session_, voice_channel()).WillRepeatedly(ReturnNull()); EXPECT_CALL(*media_channel, GetStats(_, _)) .WillRepeatedly(DoAll(SetArgPointee<1>(stats_read), Return(true))); InitSessionStats(kVcName); EXPECT_CALL(session_, GetStats(_)) - .WillRepeatedly(DoAll(SetArgPointee<0>(session_stats_), - Return(true))); + .WillRepeatedly(DoAll(SetArgPointee<0>(session_stats_), + Return(true))); stats.UpdateStats(PeerConnectionInterface::kStatsOutputLevelStandard); stats.GetStats(NULL, &reports); std::string transport_id = ExtractStatsValue( - webrtc::StatsReport::kStatsReportTypeSsrc, + StatsReport::kStatsReportTypeSsrc, reports, - webrtc::StatsReport::kStatsValueNameTransportId); + StatsReport::kStatsValueNameTransportId); ASSERT_NE(kNotFound, transport_id); - const webrtc::StatsReport* transport_report = FindReportById(reports, - transport_id); + const StatsReport* transport_report = FindReportById(reports, + transport_id); ASSERT_FALSE(transport_report == NULL); } @@ -589,14 +710,14 @@ TEST_F(StatsCollectorTest, RemoteSsrcInfoIsAbsent) { stats.set_session(&session_); - EXPECT_CALL(session_, video_channel()) - .WillRepeatedly(ReturnNull()); + EXPECT_CALL(session_, video_channel()).WillRepeatedly(ReturnNull()); + EXPECT_CALL(session_, voice_channel()).WillRepeatedly(ReturnNull()); stats.UpdateStats(PeerConnectionInterface::kStatsOutputLevelStandard); - webrtc::StatsReports reports; + StatsReports reports; stats.GetStats(NULL, &reports); - const webrtc::StatsReport* remote_report = FindNthReportByType(reports, - webrtc::StatsReport::kStatsReportTypeRemoteSsrc, 1); + const StatsReport* remote_report = FindNthReportByType(reports, + StatsReport::kStatsReportTypeRemoteSsrc, 1); EXPECT_TRUE(remote_report == NULL); } @@ -614,13 +735,13 @@ TEST_F(StatsCollectorTest, RemoteSsrcInfoIsPresent) { stats.set_session(&session_); - webrtc::StatsReports reports; + StatsReports reports; // Instruct the session to return stats containing the transport channel. InitSessionStats(kVcName); EXPECT_CALL(session_, GetStats(_)) - .WillRepeatedly(DoAll(SetArgPointee<0>(session_stats_), - Return(true))); + .WillRepeatedly(DoAll(SetArgPointee<0>(session_stats_), + Return(true))); // Constructs an ssrc stats update. cricket::VideoMediaInfo stats_read; @@ -633,16 +754,16 @@ TEST_F(StatsCollectorTest, RemoteSsrcInfoIsPresent) { video_sender_info.remote_stats.push_back(remote_ssrc_stats); stats_read.senders.push_back(video_sender_info); - EXPECT_CALL(session_, video_channel()) - .WillRepeatedly(Return(&video_channel)); + EXPECT_CALL(session_, video_channel()).WillRepeatedly(Return(&video_channel)); + EXPECT_CALL(session_, voice_channel()).WillRepeatedly(ReturnNull()); EXPECT_CALL(*media_channel, GetStats(_, _)) .WillRepeatedly(DoAll(SetArgPointee<1>(stats_read), Return(true))); stats.UpdateStats(PeerConnectionInterface::kStatsOutputLevelStandard); stats.GetStats(NULL, &reports); - const webrtc::StatsReport* remote_report = FindNthReportByType(reports, - webrtc::StatsReport::kStatsReportTypeRemoteSsrc, 1); + const StatsReport* remote_report = FindNthReportByType(reports, + StatsReport::kStatsReportTypeRemoteSsrc, 1); EXPECT_FALSE(remote_report == NULL); EXPECT_NE(0, remote_report->timestamp); } @@ -689,7 +810,7 @@ TEST_F(StatsCollectorTest, ChainlessCertificateReportsCreated) { // transport is present. TEST_F(StatsCollectorTest, NoTransport) { webrtc::StatsCollector stats; // Implementation under test. - webrtc::StatsReports reports; // returned values. + StatsReports reports; // returned values. stats.set_session(&session_); // Fake stats to process. @@ -711,24 +832,24 @@ TEST_F(StatsCollectorTest, NoTransport) { .WillOnce(DoAll(SetArgPointee<0>(session_stats), Return(true))); - EXPECT_CALL(session_, video_channel()) - .WillRepeatedly(ReturnNull()); + EXPECT_CALL(session_, video_channel()).WillRepeatedly(ReturnNull()); + EXPECT_CALL(session_, voice_channel()).WillRepeatedly(ReturnNull()); stats.UpdateStats(PeerConnectionInterface::kStatsOutputLevelStandard); stats.GetStats(NULL, &reports); // Check that the local certificate is absent. std::string local_certificate_id = ExtractStatsValue( - webrtc::StatsReport::kStatsReportTypeComponent, + StatsReport::kStatsReportTypeComponent, reports, - webrtc::StatsReport::kStatsValueNameLocalCertificateId); + StatsReport::kStatsValueNameLocalCertificateId); ASSERT_EQ(kNotFound, local_certificate_id); // Check that the remote certificate is absent. std::string remote_certificate_id = ExtractStatsValue( - webrtc::StatsReport::kStatsReportTypeComponent, + StatsReport::kStatsReportTypeComponent, reports, - webrtc::StatsReport::kStatsValueNameRemoteCertificateId); + StatsReport::kStatsValueNameRemoteCertificateId); ASSERT_EQ(kNotFound, remote_certificate_id); } @@ -736,7 +857,7 @@ TEST_F(StatsCollectorTest, NoTransport) { // does not have any certificates. TEST_F(StatsCollectorTest, NoCertificates) { webrtc::StatsCollector stats; // Implementation under test. - webrtc::StatsReports reports; // returned values. + StatsReports reports; // returned values. stats.set_session(&session_); // Fake stats to process. @@ -764,24 +885,24 @@ TEST_F(StatsCollectorTest, NoCertificates) { EXPECT_CALL(session_, GetStats(_)) .WillOnce(DoAll(SetArgPointee<0>(session_stats), Return(true))); - EXPECT_CALL(session_, video_channel()) - .WillRepeatedly(ReturnNull()); + EXPECT_CALL(session_, video_channel()).WillRepeatedly(ReturnNull()); + EXPECT_CALL(session_, voice_channel()).WillRepeatedly(ReturnNull()); stats.UpdateStats(PeerConnectionInterface::kStatsOutputLevelStandard); stats.GetStats(NULL, &reports); // Check that the local certificate is absent. std::string local_certificate_id = ExtractStatsValue( - webrtc::StatsReport::kStatsReportTypeComponent, + StatsReport::kStatsReportTypeComponent, reports, - webrtc::StatsReport::kStatsValueNameLocalCertificateId); + StatsReport::kStatsValueNameLocalCertificateId); ASSERT_EQ(kNotFound, local_certificate_id); // Check that the remote certificate is absent. std::string remote_certificate_id = ExtractStatsValue( - webrtc::StatsReport::kStatsReportTypeComponent, + StatsReport::kStatsReportTypeComponent, reports, - webrtc::StatsReport::kStatsValueNameRemoteCertificateId); + StatsReport::kStatsValueNameRemoteCertificateId); ASSERT_EQ(kNotFound, remote_certificate_id); } @@ -810,7 +931,7 @@ TEST_F(StatsCollectorTest, StatsOutputLevelVerbose) { media_engine_, media_channel, &session_, "", false, NULL); stats.set_session(&session_); - webrtc::StatsReports reports; // returned values. + StatsReports reports; // returned values. cricket::VideoMediaInfo stats_read; cricket::BandwidthEstimationInfo bwe; bwe.total_received_propagation_delta_ms = 10; @@ -822,6 +943,7 @@ TEST_F(StatsCollectorTest, StatsOutputLevelVerbose) { EXPECT_CALL(session_, video_channel()) .WillRepeatedly(Return(&video_channel)); + EXPECT_CALL(session_, voice_channel()).WillRepeatedly(ReturnNull()); StatsOptions options; options.include_received_propagation_stats = true; @@ -844,4 +966,175 @@ TEST_F(StatsCollectorTest, StatsOutputLevelVerbose) { EXPECT_EQ("[1000, 2000]", result); } +// This test verifies that a local stats object can get statistics via +// AudioTrackInterface::GetStats() method. +TEST_F(StatsCollectorTest, GetStatsFromLocalAudioTrack) { + webrtc::StatsCollector stats; // Implementation under test. + MockVoiceMediaChannel* media_channel = new MockVoiceMediaChannel(); + // The content_name known by the voice channel. + const std::string kVcName("vcname"); + cricket::VoiceChannel voice_channel(talk_base::Thread::Current(), + media_engine_, media_channel, &session_, kVcName, false); + AddLocalAudioTrackStats(); + stats.AddStream(stream_); + stats.AddLocalAudioTrack(audio_track_.get(), kSsrcOfTrack); + + stats.set_session(&session_); + + // Instruct the session to return stats containing the transport channel. + InitSessionStats(kVcName); + EXPECT_CALL(session_, GetStats(_)) + .WillRepeatedly(DoAll(SetArgPointee<0>(session_stats_), + Return(true))); + + cricket::VoiceSenderInfo voice_sender_info; + // Contents won't be modified by the AudioTrackInterface::GetStats(). + voice_sender_info.add_ssrc(kSsrcOfTrack); + voice_sender_info.codec_name = "fake_codec"; + voice_sender_info.bytes_sent = 100; + voice_sender_info.packets_sent = 101; + voice_sender_info.rtt_ms = 102; + voice_sender_info.fraction_lost = 103; + voice_sender_info.jitter_ms = 104; + voice_sender_info.packets_lost = 105; + voice_sender_info.ext_seqnum = 106; + + // Contents will be modified by the AudioTrackInterface::GetStats(). + voice_sender_info.audio_level = 107; + voice_sender_info.echo_return_loss = 108;; + voice_sender_info.echo_return_loss_enhancement = 109; + voice_sender_info.echo_delay_median_ms = 110; + voice_sender_info.echo_delay_std_ms = 111; + voice_sender_info.aec_quality_min = 112.0f; + voice_sender_info.typing_noise_detected = false; + + // Constructs an ssrc stats update. + cricket::VoiceMediaInfo stats_read; + stats_read.senders.push_back(voice_sender_info); + + EXPECT_CALL(session_, voice_channel()).WillRepeatedly(Return(&voice_channel)); + EXPECT_CALL(session_, video_channel()).WillRepeatedly(ReturnNull()); + EXPECT_CALL(*media_channel, GetStats(_)) + .WillRepeatedly(DoAll(SetArgPointee<0>(stats_read), + Return(true))); + + StatsReports reports; // returned values. + stats.UpdateStats(PeerConnectionInterface::kStatsOutputLevelStandard); + stats.GetStats(NULL, &reports); + + // Verfy the existence of the track report. + const StatsReport* report = FindNthReportByType( + reports, StatsReport::kStatsReportTypeSsrc, 1); + EXPECT_FALSE(report == NULL); + std::string track_id = ExtractSsrcStatsValue( + reports, StatsReport::kStatsValueNameTrackId); + EXPECT_EQ(kAudioTrackId, track_id); + std::string ssrc_id = ExtractSsrcStatsValue( + reports, StatsReport::kStatsValueNameSsrc); + EXPECT_EQ(talk_base::ToString(kSsrcOfTrack), ssrc_id); + + // Verifies the values in the track report. + audio_track_->GetSignalLevel(&voice_sender_info.audio_level); + webrtc::AudioProcessorInterface::AudioProcessorStats audio_processor_stats; + audio_track_->GetAudioProcessor()->GetStats(&audio_processor_stats); + voice_sender_info.typing_noise_detected = + audio_processor_stats.typing_noise_detected; + voice_sender_info.echo_return_loss = audio_processor_stats.echo_return_loss; + voice_sender_info.echo_return_loss_enhancement = + audio_processor_stats.echo_return_loss_enhancement; + voice_sender_info.echo_delay_median_ms = + audio_processor_stats.echo_delay_median_ms; + voice_sender_info.aec_quality_min = audio_processor_stats.aec_quality_min; + voice_sender_info.echo_delay_std_ms = audio_processor_stats.echo_delay_std_ms; + VerifyVoiceSenderInfoReport(report, voice_sender_info); + + // Verify we get the same result by passing a track to GetStats(). + StatsReports track_reports; // returned values. + stats.GetStats(audio_track_.get(), &track_reports); + const StatsReport* track_report = FindNthReportByType( + track_reports, StatsReport::kStatsReportTypeSsrc, 1); + EXPECT_FALSE(track_report == NULL); + track_id = ExtractSsrcStatsValue(track_reports, + StatsReport::kStatsValueNameTrackId); + EXPECT_EQ(kAudioTrackId, track_id); + ssrc_id = ExtractSsrcStatsValue(track_reports, + StatsReport::kStatsValueNameSsrc); + EXPECT_EQ(talk_base::ToString(kSsrcOfTrack), ssrc_id); + VerifyVoiceSenderInfoReport(track_report, voice_sender_info); +} + +// This test verifies that a local stats object won't update its statistics +// after a RemoveLocalAudioTrack() call. +TEST_F(StatsCollectorTest, GetStatsAfterRemoveAudioStream) { + webrtc::StatsCollector stats; // Implementation under test. + MockVoiceMediaChannel* media_channel = new MockVoiceMediaChannel(); + // The content_name known by the voice channel. + const std::string kVcName("vcname"); + cricket::VoiceChannel voice_channel(talk_base::Thread::Current(), + media_engine_, media_channel, &session_, kVcName, false); + AddLocalAudioTrackStats(); + stats.AddStream(stream_); + stats.AddLocalAudioTrack(audio_track_.get(), kSsrcOfTrack); + + stats.set_session(&session_); + + // Instruct the session to return stats containing the transport channel. + InitSessionStats(kVcName); + EXPECT_CALL(session_, GetStats(_)) + .WillRepeatedly(DoAll(SetArgPointee<0>(session_stats_), + Return(true))); + + stats.RemoveLocalAudioTrack(audio_track_.get(), kSsrcOfTrack); + cricket::VoiceSenderInfo voice_sender_info; + // Contents won't be modified by the AudioTrackInterface::GetStats(). + voice_sender_info.add_ssrc(kSsrcOfTrack); + voice_sender_info.codec_name = "fake_codec"; + voice_sender_info.bytes_sent = 100; + voice_sender_info.packets_sent = 101; + voice_sender_info.rtt_ms = 102; + voice_sender_info.fraction_lost = 103; + voice_sender_info.jitter_ms = 104; + voice_sender_info.packets_lost = 105; + voice_sender_info.ext_seqnum = 106; + + // Contents will be modified by the AudioTrackInterface::GetStats(). + voice_sender_info.audio_level = 107; + voice_sender_info.echo_return_loss = 108;; + voice_sender_info.echo_return_loss_enhancement = 109; + voice_sender_info.echo_delay_median_ms = 110; + voice_sender_info.echo_delay_std_ms = 111; + voice_sender_info.aec_quality_min = 112; + voice_sender_info.typing_noise_detected = false; + + // Constructs an ssrc stats update. + cricket::VoiceMediaInfo stats_read; + stats_read.senders.push_back(voice_sender_info); + + EXPECT_CALL(session_, voice_channel()).WillRepeatedly(Return(&voice_channel)); + EXPECT_CALL(session_, video_channel()).WillRepeatedly(ReturnNull()); + EXPECT_CALL(*media_channel, GetStats(_)) + .WillRepeatedly(DoAll(SetArgPointee<0>(stats_read), + Return(true))); + + StatsReports reports; // returned values. + stats.UpdateStats(PeerConnectionInterface::kStatsOutputLevelStandard); + stats.GetStats(NULL, &reports); + + // The report will exist since we don't remove them in RemoveStream(). + const StatsReport* report = FindNthReportByType( + reports, StatsReport::kStatsReportTypeSsrc, 1); + EXPECT_FALSE(report == NULL); + std::string track_id = ExtractSsrcStatsValue( + reports, StatsReport::kStatsValueNameTrackId); + EXPECT_EQ(kAudioTrackId, track_id); + std::string ssrc_id = ExtractSsrcStatsValue( + reports, StatsReport::kStatsValueNameSsrc); + EXPECT_EQ(talk_base::ToString(kSsrcOfTrack), ssrc_id); + + // Verifies the values in the track report, no value will be changed by the + // AudioTrackInterface::GetSignalValue() and + // AudioProcessorInterface::AudioProcessorStats::GetStats(); + VerifyVoiceSenderInfoReport(report, voice_sender_info); +} + } // namespace diff --git a/talk/app/webrtc/statstypes.h b/talk/app/webrtc/statstypes.h index 2e7fb8127..c15dfaaed 100644 --- a/talk/app/webrtc/statstypes.h +++ b/talk/app/webrtc/statstypes.h @@ -57,6 +57,8 @@ class StatsReport { void AddValue(const std::string& name, const std::vector& value); void AddBoolean(const std::string& name, bool value); + void ReplaceValue(const std::string& name, const std::string& value); + double timestamp; // Time since 1970-01-01T00:00:00Z in milliseconds. typedef std::vector Values; Values values; diff --git a/talk/app/webrtc/test/fakemediastreamsignaling.h b/talk/app/webrtc/test/fakemediastreamsignaling.h index f0c19cf6d..c7b30aaa5 100644 --- a/talk/app/webrtc/test/fakemediastreamsignaling.h +++ b/talk/app/webrtc/test/fakemediastreamsignaling.h @@ -122,7 +122,8 @@ class FakeMediaStreamSignaling : public webrtc::MediaStreamSignaling, virtual void OnRemoveLocalAudioTrack( webrtc::MediaStreamInterface* stream, - webrtc::AudioTrackInterface* audio_track) { + webrtc::AudioTrackInterface* audio_track, + uint32 ssrc) { } virtual void OnRemoveLocalVideoTrack( webrtc::MediaStreamInterface* stream, diff --git a/talk/examples/call/call_main.cc b/talk/examples/call/call_main.cc index 2ee796b1c..25c33ff0e 100644 --- a/talk/examples/call/call_main.cc +++ b/talk/examples/call/call_main.cc @@ -45,9 +45,6 @@ #include "talk/examples/call/console.h" #include "talk/examples/call/mediaenginefactory.h" #include "talk/p2p/base/constants.h" -#ifdef ANDROID -#include "talk/media/other/androidmediaengine.h" -#endif #include "talk/session/media/mediasessionclient.h" #include "talk/session/media/srtpfilter.h" #include "talk/xmpp/xmppauth.h" @@ -185,7 +182,7 @@ static const int DEFAULT_PORT = 5222; static std::vector codecs; static const cricket::AudioCodec ISAC(103, "ISAC", 40000, 16000, 1, 0); -cricket::MediaEngine *AndroidMediaEngineFactory() { +cricket::MediaEngineInterface *CreateAndroidMediaEngine() { cricket::FakeMediaEngine *engine = new cricket::FakeMediaEngine(); codecs.push_back(ISAC); @@ -438,7 +435,7 @@ int main(int argc, char **argv) { } #ifdef ANDROID - InitAndroidMediaEngineFactory(AndroidMediaEngineFactory); + MediaEngineFactory::SetCreateFunction(&CreateAndroidMediaEngine); #endif #if WIN32 diff --git a/talk/media/base/mediaengine.cc b/talk/media/base/mediaengine.cc index 021cf81f1..289f2290a 100644 --- a/talk/media/base/mediaengine.cc +++ b/talk/media/base/mediaengine.cc @@ -42,6 +42,13 @@ const int MediaEngineInterface::kDefaultAudioDelayOffset = 0; #if defined(HAVE_WEBRTC_VIDEO) #include "talk/media/webrtc/webrtcvideoengine.h" #endif // HAVE_WEBRTC_VIDEO +#if defined(HAVE_LMI) +#include "talk/media/base/hybridvideoengine.h" +#include "talk/media/lmi/lmimediaengine.h" +#endif // HAVE_LMI +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif // HAVE_CONFIG namespace cricket { #if defined(HAVE_WEBRTC_VOICE) @@ -51,22 +58,59 @@ namespace cricket { #endif #if defined(HAVE_WEBRTC_VIDEO) +#if !defined(HAVE_LMI) template<> CompositeMediaEngine:: CompositeMediaEngine() { video_.SetVoiceEngine(&voice_); } #define VIDEO_ENG_NAME WebRtcVideoEngine +#else +// If we have both WebRtcVideoEngine and LmiVideoEngine, enable dual-stack. +// This small class here allows us to hook the WebRtcVideoChannel up to +// the capturer owned by the LMI engine, without infecting the rest of the +// HybridVideoEngine classes with this abstraction violation. +class WebRtcLmiHybridVideoEngine + : public HybridVideoEngine { + public: + void SetVoiceEngine(WebRtcVoiceEngine* engine) { + video1_.SetVoiceEngine(engine); + } +}; +template<> +CompositeMediaEngine:: + CompositeMediaEngine() { + video_.SetVoiceEngine(&voice_); +} +#define VIDEO_ENG_NAME WebRtcLmiHybridVideoEngine +#endif +#elif defined(HAVE_LMI) +#define VIDEO_ENG_NAME LmiVideoEngine +#else +#define VIDEO_ENG_NAME NullVideoEngine #endif +MediaEngineFactory::MediaEngineCreateFunction + MediaEngineFactory::create_function_ = NULL; +MediaEngineFactory::MediaEngineCreateFunction + MediaEngineFactory::SetCreateFunction(MediaEngineCreateFunction function) { + MediaEngineCreateFunction old_function = create_function_; + create_function_ = function; + return old_function; +}; + MediaEngineInterface* MediaEngineFactory::Create() { + if (create_function_) { + return create_function_(); + } else { #if defined(HAVE_LINPHONE) - return new LinphoneMediaEngine("", ""); + return new LinphoneMediaEngine("", ""); #elif defined(AUDIO_ENG_NAME) && defined(VIDEO_ENG_NAME) - return new CompositeMediaEngine(); + return new CompositeMediaEngine(); #else - return new NullMediaEngine(); + return new NullMediaEngine(); #endif + } } }; // namespace cricket diff --git a/talk/media/base/mediaengine.h b/talk/media/base/mediaengine.h index 93586bb5b..bccae9951 100644 --- a/talk/media/base/mediaengine.h +++ b/talk/media/base/mediaengine.h @@ -157,7 +157,18 @@ class MediaEngineInterface { #if !defined(DISABLE_MEDIA_ENGINE_FACTORY) class MediaEngineFactory { public: + typedef cricket::MediaEngineInterface* (*MediaEngineCreateFunction)(); + // Creates a media engine, using either the compiled system default or the + // creation function specified in SetCreateFunction, if specified. static MediaEngineInterface* Create(); + // Sets the function used when calling Create. If unset, the compiled system + // default will be used. Returns the old create function, or NULL if one + // wasn't set. Likewise, NULL can be used as the |function| parameter to + // reset to the default behavior. + static MediaEngineCreateFunction SetCreateFunction( + MediaEngineCreateFunction function); + private: + static MediaEngineCreateFunction create_function_; }; #endif diff --git a/talk/media/other/androidmediaengine.cc b/talk/media/other/androidmediaengine.cc new file mode 100644 index 000000000..e69de29bb diff --git a/talk/media/other/androidmediaengine.h b/talk/media/other/androidmediaengine.h new file mode 100644 index 000000000..e69de29bb