diff --git a/api/peerconnectioninterface.h b/api/peerconnectioninterface.h index eb6685a561..d3209f4d88 100644 --- a/api/peerconnectioninterface.h +++ b/api/peerconnectioninterface.h @@ -734,6 +734,10 @@ class PeerConnectionInterface : public rtc::RefCountInterface { // break third party projects. As soon as they have been updated this should // be changed to "= 0;". virtual void GetStats(RTCStatsCollectorCallback* callback) {} + // Clear cached stats in the rtcstatscollector. + // Exposed for testing while waiting for automatic cache clear to work. + // https://bugs.webrtc.org/8693 + virtual void ClearStatsCache() {} // Create a data channel with the provided config, or default config if none // is provided. Note that an offer/answer negotiation is still necessary diff --git a/pc/peerconnection.cc b/pc/peerconnection.cc index b99df99014..94f2db1e26 100644 --- a/pc/peerconnection.cc +++ b/pc/peerconnection.cc @@ -5518,4 +5518,10 @@ void PeerConnection::DestroyBaseChannel(cricket::BaseChannel* channel) { } } +void PeerConnection::ClearStatsCache() { + if (stats_collector_) { + stats_collector_->ClearCachedStatsReport(); + } +} + } // namespace webrtc diff --git a/pc/peerconnection.h b/pc/peerconnection.h index b9a86d8653..66061f10c0 100644 --- a/pc/peerconnection.h +++ b/pc/peerconnection.h @@ -137,6 +137,7 @@ class PeerConnection : public PeerConnectionInterface, webrtc::MediaStreamTrackInterface* track, StatsOutputLevel level) override; void GetStats(RTCStatsCollectorCallback* callback) override; + void ClearStatsCache() override; SignalingState signaling_state() override; diff --git a/pc/peerconnectioninterface_unittest.cc b/pc/peerconnectioninterface_unittest.cc index 6bff2766ee..56c12e9d79 100644 --- a/pc/peerconnectioninterface_unittest.cc +++ b/pc/peerconnectioninterface_unittest.cc @@ -846,6 +846,15 @@ class PeerConnectionInterfaceTest : public testing::Test { return observer->called(); } + // Call the standards-compliant GetStats function. + bool DoGetRTCStats() { + rtc::scoped_refptr callback( + new rtc::RefCountedObject()); + pc_->GetStats(callback); + EXPECT_TRUE_WAIT(callback->called(), kTimeout); + return callback->called(); + } + void InitiateCall() { CreatePeerConnectionWithoutDtls(); // Create a local stream with audio&video tracks. @@ -1466,7 +1475,6 @@ TEST_F(PeerConnectionInterfaceTest, AddTrackRemoveTrack) { // expecting a random stream ID to be generated. TEST_F(PeerConnectionInterfaceTest, AddTrackWithoutStream) { CreatePeerConnectionWithoutDtls(); - // Create a dummy stream, so tracks share a stream label. rtc::scoped_refptr audio_track( pc_factory_->CreateAudioTrack("audio_track", nullptr)); rtc::scoped_refptr video_track( @@ -1487,6 +1495,24 @@ TEST_F(PeerConnectionInterfaceTest, AddTrackWithoutStream) { EXPECT_NE(video_sender->stream_ids(), audio_sender->stream_ids()); } +// Test that we can call GetStats() after AddTrack but before connecting +// the PeerConnection to a peer. +TEST_F(PeerConnectionInterfaceTest, AddTrackBeforeConnecting) { + CreatePeerConnectionWithoutDtls(); + rtc::scoped_refptr audio_track( + pc_factory_->CreateAudioTrack("audio_track", nullptr)); + rtc::scoped_refptr video_track( + pc_factory_->CreateVideoTrack( + "video_track", pc_factory_->CreateVideoSource( + std::unique_ptr( + new cricket::FakeVideoCapturer())))); + auto audio_sender = + pc_->AddTrack(audio_track, std::vector()); + auto video_sender = + pc_->AddTrack(video_track, std::vector()); + EXPECT_TRUE(DoGetStats(nullptr)); +} + TEST_F(PeerConnectionInterfaceTest, CreateOfferReceiveAnswer) { InitiateCall(); WaitAndVerifyOnAddStream(kStreamLabel1); @@ -1735,6 +1761,19 @@ TEST_F(PeerConnectionInterfaceTest, GetStatsForInvalidTrack) { EXPECT_FALSE(DoGetStats(unknown_audio_track)); } +TEST_F(PeerConnectionInterfaceTest, GetRTCStatsBeforeAndAfterCalling) { + CreatePeerConnectionWithoutDtls(); + EXPECT_TRUE(DoGetRTCStats()); + // Clearing stats cache is needed now, but should be temporary. + // https://bugs.chromium.org/p/webrtc/issues/detail?id=8693 + pc_->ClearStatsCache(); + AddAudioVideoStream(kStreamLabel1, "audio_track", "video_track"); + EXPECT_TRUE(DoGetRTCStats()); + pc_->ClearStatsCache(); + CreateOfferReceiveAnswer(); + EXPECT_TRUE(DoGetRTCStats()); +} + // This test setup two RTP data channels in loop back. TEST_F(PeerConnectionInterfaceTest, TestDataChannel) { FakeConstraints constraints; diff --git a/pc/rtcstatscollector.cc b/pc/rtcstatscollector.cc index 24444f3fef..e777a2672c 100644 --- a/pc/rtcstatscollector.cc +++ b/pc/rtcstatscollector.cc @@ -207,7 +207,7 @@ void SetInboundRTPStreamStatsFromMediaReceiverInfo( RTCInboundRTPStreamStats* inbound_stats) { RTC_DCHECK(inbound_stats); inbound_stats->ssrc = media_receiver_info.ssrc(); - // TODO(hbos): Support the remote case. crbug.com/657855 + // TODO(hbos): Support the remote case. https://crbug.com/657855 inbound_stats->is_remote = false; inbound_stats->packets_received = static_cast(media_receiver_info.packets_rcvd); @@ -265,7 +265,7 @@ void SetOutboundRTPStreamStatsFromMediaSenderInfo( RTCOutboundRTPStreamStats* outbound_stats) { RTC_DCHECK(outbound_stats); outbound_stats->ssrc = media_sender_info.ssrc(); - // TODO(hbos): Support the remote case. crbug.com/657856 + // TODO(hbos): Support the remote case. https://crbug.com/657856 outbound_stats->is_remote = false; outbound_stats->packets_sent = static_cast(media_sender_info.packets_sent); @@ -460,7 +460,7 @@ ProduceMediaStreamTrackStatsFromVideoSenderInfo( video_track_stats->frame_height = static_cast( video_sender_info.send_frame_height); // TODO(hbos): Will reduce this by frames dropped due to congestion control - // when available. crbug.com/659137 + // when available. https://crbug.com/659137 video_track_stats->frames_sent = video_sender_info.frames_encoded; return video_track_stats; } @@ -492,7 +492,7 @@ ProduceMediaStreamTrackStatsFromVideoReceiverInfo( // TODO(hbos): When we support receiving simulcast, this should be the total // number of frames correctly decoded, independent of which SSRC it was // received from. Since we don't support that, this is correct and is the same - // value as "RTCInboundRTPStreamStats.framesDecoded". crbug.com/659137 + // value as "RTCInboundRTPStreamStats.framesDecoded". https://crbug.com/659137 video_track_stats->frames_decoded = video_receiver_info.frames_decoded; RTC_DCHECK_GE(video_receiver_info.frames_received, video_receiver_info.frames_rendered); @@ -501,20 +501,99 @@ ProduceMediaStreamTrackStatsFromVideoReceiverInfo( return video_track_stats; } -void ProduceMediaStreamAndTrackStats( +void ProduceSenderMediaTrackStats( + int64_t timestamp_us, + const TrackMediaInfoMap& track_media_info_map, + std::vector> senders, + RTCStatsReport* report) { + // This function iterates over the senders to generate outgoing track stats. + + // TODO(hbos): Return stats of detached tracks. We have to perform stats + // gathering at the time of detachment to get accurate stats and timestamps. + // https://crbug.com/659137 + for (auto const sender : senders) { + // Don't report on tracks before starting to send. + // https://bugs.webrtc.org/8673 + if (sender->ssrc() == 0) + continue; + if (sender->media_type() == cricket::MEDIA_TYPE_AUDIO) { + AudioTrackInterface* track = + static_cast(sender->track().get()); + if (!track) + continue; + auto voice_sender_info = + track_media_info_map.GetVoiceSenderInfoBySsrc(sender->ssrc()); + RTC_CHECK(voice_sender_info) + << "No voice sender info for sender with ssrc " << sender->ssrc(); + std::unique_ptr audio_track_stats = + ProduceMediaStreamTrackStatsFromVoiceSenderInfo(timestamp_us, *track, + *voice_sender_info); + report->AddStats(std::move(audio_track_stats)); + } else if (sender->media_type() == cricket::MEDIA_TYPE_VIDEO) { + VideoTrackInterface* track = + static_cast(sender->track().get()); + if (!track) + continue; + auto video_sender_info = + track_media_info_map.GetVideoSenderInfoBySsrc(sender->ssrc()); + RTC_CHECK(video_sender_info) + << "No video sender info for sender with ssrc " << sender->ssrc(); + std::unique_ptr video_track_stats = + ProduceMediaStreamTrackStatsFromVideoSenderInfo(timestamp_us, *track, + *video_sender_info); + report->AddStats(std::move(video_track_stats)); + } else { + RTC_NOTREACHED(); + } + } +} + +void ProduceReceiverMediaTrackStats( + int64_t timestamp_us, + const TrackMediaInfoMap& track_media_info_map, + std::vector> receivers, + RTCStatsReport* report) { + // This function iterates over the receivers to find the remote tracks. + for (auto const receiver : receivers) { + if (receiver->media_type() == cricket::MEDIA_TYPE_AUDIO) { + AudioTrackInterface* track = + static_cast(receiver->track().get()); + const cricket::VoiceReceiverInfo* voice_receiver_info = + track_media_info_map.GetVoiceReceiverInfo(*track); + if (!voice_receiver_info) { + continue; + } + std::unique_ptr audio_track_stats = + ProduceMediaStreamTrackStatsFromVoiceReceiverInfo( + timestamp_us, *track, *voice_receiver_info); + report->AddStats(std::move(audio_track_stats)); + } else if (receiver->media_type() == cricket::MEDIA_TYPE_VIDEO) { + VideoTrackInterface* track = + static_cast(receiver->track().get()); + const cricket::VideoReceiverInfo* video_receiver_info = + track_media_info_map.GetVideoReceiverInfo(*track); + if (!video_receiver_info) { + continue; + } + std::unique_ptr video_track_stats = + ProduceMediaStreamTrackStatsFromVideoReceiverInfo( + timestamp_us, *track, *video_receiver_info); + report->AddStats(std::move(video_track_stats)); + } else { + RTC_NOTREACHED(); + } + } +} + +void ProduceMediaStreamStats( int64_t timestamp_us, const TrackMediaInfoMap& track_media_info_map, rtc::scoped_refptr streams, bool is_local, RTCStatsReport* report) { - // TODO(hbos): When "AddTrack" is implemented we should iterate tracks to - // find which streams exist, not iterate streams to find tracks. - // crbug.com/659137 - // TODO(hbos): Return stats of detached tracks. We have to perform stats - // gathering at the time of detachment to get accurate stats and timestamps. - // crbug.com/659137 if (!streams) return; + // Collect info about streams and which tracks in them are attached to PC. for (size_t i = 0; i < streams->count(); ++i) { MediaStreamInterface* stream = streams->at(i); @@ -524,70 +603,64 @@ void ProduceMediaStreamAndTrackStats( stream->label(), timestamp_us)); stream_stats->stream_identifier = stream->label(); stream_stats->track_ids = std::vector(); - // The track stats are per-attachment to the connection. There can be one - // for receiving (remote) tracks and multiple attachments for sending - // (local) tracks. + // Record the IDs of tracks that are currently connected. + // Note: Iterating over streams may be iffy with AddTrack. + // TODO(hta): Revisit in conjunction with https://bugs.webrtc.org/8674 if (is_local) { - // Local Audio Tracks - for (const rtc::scoped_refptr& audio_track : - stream->GetAudioTracks()) { - const std::vector* voice_sender_infos = + for (auto audio_track : stream->GetAudioTracks()) { + auto sender_infos = track_media_info_map.GetVoiceSenderInfos(*audio_track); - if (!voice_sender_infos) { + // There is no map entry on unconnected tracks. + // https://bugs.webrtc.org/8673 + if (!sender_infos) continue; - } - for (const auto& voice_sender_info : *voice_sender_infos) { - std::unique_ptr audio_track_stats = - ProduceMediaStreamTrackStatsFromVoiceSenderInfo( - timestamp_us, *audio_track, *voice_sender_info); - stream_stats->track_ids->push_back(audio_track_stats->id()); - report->AddStats(std::move(audio_track_stats)); + for (const auto& sender_info : *sender_infos) { + // In the WebRTC implementation, SSRC 0 means unconnected, + // and should not occur in the map. + // https://bugs.webrtc.org/8694 + RTC_DCHECK_NE(0, sender_info->ssrc()); + stream_stats->track_ids->push_back( + RTCMediaStreamTrackStatsIDFromTrackKindIDAndSsrc( + is_local, MediaStreamTrackInterface::kAudioKind, + audio_track->id(), sender_info->ssrc())); } } - // Local Video Tracks - for (const rtc::scoped_refptr& video_track : - stream->GetVideoTracks()) { - const std::vector* video_sender_infos = + for (auto video_track : stream->GetVideoTracks()) { + auto sender_infos = track_media_info_map.GetVideoSenderInfos(*video_track); - if (!video_sender_infos) { + // There is no map entry on unconnected tracks. + // https://bugs.webrtc.org/8673 + if (!sender_infos) continue; - } - for (const auto& video_sender_info : *video_sender_infos) { - std::unique_ptr video_track_stats = - ProduceMediaStreamTrackStatsFromVideoSenderInfo( - timestamp_us, *video_track, *video_sender_info); - stream_stats->track_ids->push_back(video_track_stats->id()); - report->AddStats(std::move(video_track_stats)); + for (const auto& sender_info : *sender_infos) { + // SSRC must not be zero. https://bugs.webrtc.org/8694 + RTC_DCHECK_NE(0, sender_info->ssrc()); + stream_stats->track_ids->push_back( + RTCMediaStreamTrackStatsIDFromTrackKindIDAndSsrc( + is_local, MediaStreamTrackInterface::kVideoKind, + video_track->id(), sender_info->ssrc())); } } } else { - // Remote Audio Tracks - for (const rtc::scoped_refptr& audio_track : - stream->GetAudioTracks()) { - const cricket::VoiceReceiverInfo* voice_receiver_info = + for (auto audio_track : stream->GetAudioTracks()) { + auto receiver_info = track_media_info_map.GetVoiceReceiverInfo(*audio_track); - if (!voice_receiver_info) { - continue; + if (receiver_info) { + stream_stats->track_ids->push_back( + RTCMediaStreamTrackStatsIDFromTrackKindIDAndSsrc( + is_local, MediaStreamTrackInterface::kAudioKind, + audio_track->id(), receiver_info->ssrc())); } - std::unique_ptr audio_track_stats = - ProduceMediaStreamTrackStatsFromVoiceReceiverInfo( - timestamp_us, *audio_track, *voice_receiver_info); - stream_stats->track_ids->push_back(audio_track_stats->id()); - report->AddStats(std::move(audio_track_stats)); } - // Remote Video Tracks - for (const rtc::scoped_refptr& video_track : - stream->GetVideoTracks()) { - const cricket::VideoReceiverInfo* video_receiver_info = + for (auto video_track : stream->GetVideoTracks()) { + auto receiver_info = track_media_info_map.GetVideoReceiverInfo(*video_track); - if (!video_receiver_info) { - continue; + if (receiver_info) { + stream_stats->track_ids->push_back( + RTCMediaStreamTrackStatsIDFromTrackKindIDAndSsrc( + is_local, MediaStreamTrackInterface::kVideoKind, + video_track->id(), receiver_info->ssrc())); } - std::unique_ptr video_track_stats = - ProduceMediaStreamTrackStatsFromVideoReceiverInfo( - timestamp_us, *video_track, *video_receiver_info); - stream_stats->track_ids->push_back(video_track_stats->id()); - report->AddStats(std::move(video_track_stats)); } } report->AddStats(std::move(stream_stats)); @@ -897,7 +970,7 @@ void RTCStatsCollector::ProduceIceCandidateAndPairStats_n( // TODO(hbos): There could be other candidates that are not paired with // anything. We don't have a complete list. Local candidates come from // Port objects, and prflx candidates (both local and remote) are only - // stored in candidate pairs. crbug.com/632723 + // stored in candidate pairs. https://crbug.com/632723 candidate_pair_stats->local_candidate_id = ProduceIceCandidateStats( timestamp_us, info.local_candidate, true, transport_id, report); candidate_pair_stats->remote_candidate_id = ProduceIceCandidateStats( @@ -908,7 +981,7 @@ void RTCStatsCollector::ProduceIceCandidateAndPairStats_n( candidate_pair_stats->nominated = info.nominated; // TODO(hbos): This writable is different than the spec. It goes to // false after a certain amount of time without a response passes. - // crbug.com/633550 + // https://crbug.com/633550 candidate_pair_stats->writable = info.writable; candidate_pair_stats->bytes_sent = static_cast(info.sent_total_bytes); @@ -960,16 +1033,15 @@ void RTCStatsCollector::ProduceMediaStreamAndTrackStats_s( int64_t timestamp_us, RTCStatsReport* report) const { RTC_DCHECK(signaling_thread_->IsCurrent()); RTC_DCHECK(track_media_info_map_); - ProduceMediaStreamAndTrackStats(timestamp_us, - *track_media_info_map_, - pc_->local_streams(), - true, - report); - ProduceMediaStreamAndTrackStats(timestamp_us, - *track_media_info_map_, - pc_->remote_streams(), - false, - report); + // TODO(bugs.webrtc.org/8674): Use the stream list updated by AddTrack + ProduceMediaStreamStats(timestamp_us, *track_media_info_map_, + pc_->local_streams(), true, report); + ProduceMediaStreamStats(timestamp_us, *track_media_info_map_, + pc_->remote_streams(), false, report); + ProduceSenderMediaTrackStats(timestamp_us, *track_media_info_map_, + pc_->GetSenders(), report); + ProduceReceiverMediaTrackStats(timestamp_us, *track_media_info_map_, + pc_->GetReceivers(), report); } void RTCStatsCollector::ProducePeerConnectionStats_s( diff --git a/pc/rtcstatscollector_unittest.cc b/pc/rtcstatscollector_unittest.cc index 7e4ad7f969..2149695005 100644 --- a/pc/rtcstatscollector_unittest.cc +++ b/pc/rtcstatscollector_unittest.cc @@ -286,6 +286,7 @@ class RTCStatsCollectorTestHelper : public SetSessionDescriptionObserver { std::unique_ptr(media_engine_))), pc_(pc_factory_) { // Default return values for mocks. + EXPECT_CALL(pc_, GetCallStats()).WillRepeatedly(Return(Call::Stats())); EXPECT_CALL(pc_, local_streams()).WillRepeatedly(Return(nullptr)); EXPECT_CALL(pc_, remote_streams()).WillRepeatedly(Return(nullptr)); EXPECT_CALL(pc_, GetSenders()).WillRepeatedly(Return( @@ -320,25 +321,32 @@ class RTCStatsCollectorTestHelper : public SetSessionDescriptionObserver { void SetupLocalTrackAndSender(cricket::MediaType media_type, const std::string& track_id, - uint32_t ssrc) { + uint32_t ssrc, + bool add_stream) { rtc::scoped_refptr local_streams = StreamCollection::Create(); EXPECT_CALL(pc_, local_streams()) .WillRepeatedly(Return(local_streams)); - rtc::scoped_refptr local_stream = - MediaStream::Create("LocalStreamLabel"); - local_streams->AddStream(local_stream); + rtc::scoped_refptr local_stream; + if (add_stream) { + local_stream = MediaStream::Create("LocalStreamLabel"); + local_streams->AddStream(local_stream); + } rtc::scoped_refptr track; if (media_type == cricket::MEDIA_TYPE_AUDIO) { track = CreateFakeTrack(media_type, track_id, MediaStreamTrackInterface::kLive); - local_stream->AddTrack(static_cast(track.get())); + if (add_stream) { + local_stream->AddTrack(static_cast(track.get())); + } } else { track = CreateFakeTrack(media_type, track_id, MediaStreamTrackInterface::kLive); - local_stream->AddTrack(static_cast(track.get())); + if (add_stream) { + local_stream->AddTrack(static_cast(track.get())); + } } rtc::scoped_refptr sender = CreateMockSender(track, ssrc); @@ -1987,8 +1995,8 @@ TEST_F(RTCStatsCollectorTest, CollectRTCOutboundRTPStreamStats_Audio) { kDefaultRtcpMuxRequired, kDefaultSrtpRequired); voice_channel.set_transport_name_for_testing("TransportName"); - test_->SetupLocalTrackAndSender( - cricket::MEDIA_TYPE_AUDIO, "LocalAudioTrackID", 1); + test_->SetupLocalTrackAndSender(cricket::MEDIA_TYPE_AUDIO, + "LocalAudioTrackID", 1, true); cricket::VoiceMediaInfo voice_media_info; @@ -2064,8 +2072,8 @@ TEST_F(RTCStatsCollectorTest, CollectRTCOutboundRTPStreamStats_Video) { "VideoContentName", kDefaultRtcpMuxRequired, kDefaultSrtpRequired); video_channel.set_transport_name_for_testing("TransportName"); - test_->SetupLocalTrackAndSender( - cricket::MEDIA_TYPE_VIDEO, "LocalVideoTrackID", 1); + test_->SetupLocalTrackAndSender(cricket::MEDIA_TYPE_VIDEO, + "LocalVideoTrackID", 1, true); cricket::VideoMediaInfo video_media_info; @@ -2315,6 +2323,104 @@ TEST_F(RTCStatsCollectorTest, CollectRTCTransportStats) { report->Get(expected_rtcp_transport.id())->cast_to()); } +TEST_F(RTCStatsCollectorTest, CollectNoStreamRTCOutboundRTPStreamStats_Audio) { + // Emulates the case where AddTrack is used without an associated MediaStream + auto* voice_media_channel = new MockVoiceMediaChannel(); + cricket::VoiceChannel voice_channel( + test_->worker_thread(), test_->network_thread(), + test_->signaling_thread(), test_->media_engine(), + rtc::WrapUnique(voice_media_channel), "VoiceContentName", + kDefaultRtcpMuxRequired, kDefaultSrtpRequired); + voice_channel.set_transport_name_for_testing("TransportName"); + + test_->SetupLocalTrackAndSender(cricket::MEDIA_TYPE_AUDIO, + "LocalAudioTrackID", 1, false); + + cricket::VoiceMediaInfo voice_media_info; + + voice_media_info.senders.push_back(cricket::VoiceSenderInfo()); + voice_media_info.senders[0].local_stats.push_back(cricket::SsrcSenderInfo()); + voice_media_info.senders[0].local_stats[0].ssrc = 1; + voice_media_info.senders[0].packets_sent = 2; + voice_media_info.senders[0].bytes_sent = 3; + voice_media_info.senders[0].codec_payload_type = 42; + + RtpCodecParameters codec_parameters; + codec_parameters.payload_type = 42; + codec_parameters.kind = cricket::MEDIA_TYPE_AUDIO; + codec_parameters.name = "dummy"; + codec_parameters.clock_rate = 0; + voice_media_info.send_codecs.insert( + std::make_pair(codec_parameters.payload_type, codec_parameters)); + + EXPECT_CALL(*voice_media_channel, GetStats(_)) + .WillOnce(DoAll(SetArgPointee<0>(voice_media_info), Return(true))); + + SessionStats session_stats; + session_stats.transport_stats["TransportName"].transport_name = + "TransportName"; + + // Make sure the associated |RTCTransportStats| is created. + cricket::TransportChannelStats channel_stats; + channel_stats.component = cricket::ICE_CANDIDATE_COMPONENT_RTP; + session_stats.transport_stats["TransportName"].channel_stats.push_back( + channel_stats); + + EXPECT_CALL(test_->pc(), GetSessionStats(_)) + .WillRepeatedly(Invoke([&session_stats](const ChannelNamePairs&) { + return std::unique_ptr(new SessionStats(session_stats)); + })); + EXPECT_CALL(test_->pc(), voice_channel()) + .WillRepeatedly(Return(&voice_channel)); + + rtc::scoped_refptr report = GetStatsReport(); + + RTCOutboundRTPStreamStats expected_audio("RTCOutboundRTPAudioStream_1", + report->timestamp_us()); + expected_audio.ssrc = 1; + expected_audio.is_remote = false; + expected_audio.media_type = "audio"; + expected_audio.track_id = + "RTCMediaStreamTrack_local_audio_LocalAudioTrackID_1"; + expected_audio.transport_id = + "RTCTransport_TransportName_" + + rtc::ToString<>(cricket::ICE_CANDIDATE_COMPONENT_RTP); + expected_audio.codec_id = "RTCCodec_OutboundAudio_42"; + expected_audio.packets_sent = 2; + expected_audio.bytes_sent = 3; + + ASSERT_TRUE(report->Get(expected_audio.id())); + EXPECT_EQ( + report->Get(expected_audio.id())->cast_to(), + expected_audio); + + ASSERT_TRUE(report->Get(expected_audio.id())); + EXPECT_EQ( + report->Get(expected_audio.id())->cast_to(), + expected_audio); + EXPECT_TRUE(report->Get(*expected_audio.track_id)); + EXPECT_TRUE(report->Get(*expected_audio.transport_id)); + EXPECT_TRUE(report->Get(*expected_audio.codec_id)); +} + +// When the PC has not had SetLocalDescription done, tracks all have +// SSRC 0, meaning "unconnected". +// We do not report stats on those tracks. https://bugs.webrtc.org/8673 +TEST_F(RTCStatsCollectorTest, StatsNotReportedOnZeroSsrc) { + rtc::scoped_refptr track = + CreateFakeTrack(cricket::MEDIA_TYPE_AUDIO, "audioTrack", + MediaStreamTrackInterface::kLive); + rtc::scoped_refptr sender = CreateMockSender(track, 0); + EXPECT_CALL(test_->pc(), GetSenders()) + .WillRepeatedly( + Return(std::vector>( + {rtc::scoped_refptr(sender.get())}))); + rtc::scoped_refptr report = GetStatsReport(); + std::vector track_stats = + report->GetStatsOfType(); + EXPECT_EQ(0, track_stats.size()); +} + class RTCStatsCollectorTestWithFakeCollector : public testing::Test { public: RTCStatsCollectorTestWithFakeCollector() diff --git a/pc/trackmediainfomap.cc b/pc/trackmediainfomap.cc index 2a7fe9cf49..3fbe3faae7 100644 --- a/pc/trackmediainfomap.cc +++ b/pc/trackmediainfomap.cc @@ -135,6 +135,11 @@ TrackMediaInfoMap::TrackMediaInfoMap( audio_track_by_sender_info_[&sender_info] = associated_track; voice_infos_by_local_track_[associated_track].push_back(&sender_info); } + if (sender_info.ssrc() == 0) + continue; // Unconnected SSRC. bugs.webrtc.org/8673 + RTC_CHECK(voice_info_by_sender_ssrc_.count(sender_info.ssrc()) == 0) + << "Duplicate voice sender SSRC: " << sender_info.ssrc(); + voice_info_by_sender_ssrc_[sender_info.ssrc()] = &sender_info; } for (auto& receiver_info : voice_media_info_->receivers) { AudioTrackInterface* associated_track = @@ -150,6 +155,9 @@ TrackMediaInfoMap::TrackMediaInfoMap( audio_track_by_receiver_info_[&receiver_info] = unsignaled_audio_track; voice_info_by_remote_track_[unsignaled_audio_track] = &receiver_info; } + RTC_CHECK(voice_info_by_receiver_ssrc_.count(receiver_info.ssrc()) == 0) + << "Duplicate voice receiver SSRC: " << receiver_info.ssrc(); + voice_info_by_receiver_ssrc_[receiver_info.ssrc()] = &receiver_info; } } if (video_media_info_) { @@ -162,6 +170,11 @@ TrackMediaInfoMap::TrackMediaInfoMap( video_track_by_sender_info_[&sender_info] = associated_track; video_infos_by_local_track_[associated_track].push_back(&sender_info); } + if (sender_info.ssrc() == 0) + continue; // Unconnected SSRC. bugs.webrtc.org/8673 + RTC_DCHECK(video_info_by_sender_ssrc_.count(sender_info.ssrc()) == 0) + << "Duplicate video sender SSRC: " << sender_info.ssrc(); + video_info_by_sender_ssrc_[sender_info.ssrc()] = &sender_info; } for (auto& receiver_info : video_media_info_->receivers) { VideoTrackInterface* associated_track = @@ -177,6 +190,9 @@ TrackMediaInfoMap::TrackMediaInfoMap( video_track_by_receiver_info_[&receiver_info] = unsignaled_video_track; video_info_by_remote_track_[unsignaled_video_track] = &receiver_info; } + RTC_DCHECK(video_info_by_receiver_ssrc_.count(receiver_info.ssrc()) == 0) + << "Duplicate video receiver SSRC: " << receiver_info.ssrc(); + video_info_by_receiver_ssrc_[receiver_info.ssrc()] = &receiver_info; } } } @@ -203,6 +219,25 @@ const cricket::VideoReceiverInfo* TrackMediaInfoMap::GetVideoReceiverInfo( return FindValueOrNull(video_info_by_remote_track_, &remote_video_track); } +const cricket::VoiceSenderInfo* TrackMediaInfoMap::GetVoiceSenderInfoBySsrc( + uint32_t ssrc) const { + return FindValueOrNull(voice_info_by_sender_ssrc_, ssrc); +} +const cricket::VoiceReceiverInfo* TrackMediaInfoMap::GetVoiceReceiverInfoBySsrc( + uint32_t ssrc) const { + return FindValueOrNull(voice_info_by_receiver_ssrc_, ssrc); +} + +const cricket::VideoSenderInfo* TrackMediaInfoMap::GetVideoSenderInfoBySsrc( + uint32_t ssrc) const { + return FindValueOrNull(video_info_by_sender_ssrc_, ssrc); +} + +const cricket::VideoReceiverInfo* TrackMediaInfoMap::GetVideoReceiverInfoBySsrc( + uint32_t ssrc) const { + return FindValueOrNull(video_info_by_receiver_ssrc_, ssrc); +} + rtc::scoped_refptr TrackMediaInfoMap::GetAudioTrack( const cricket::VoiceSenderInfo& voice_sender_info) const { return FindValueOrNull(audio_track_by_sender_info_, &voice_sender_info); diff --git a/pc/trackmediainfomap.h b/pc/trackmediainfomap.h index acc0b14bd3..fd9a98e637 100644 --- a/pc/trackmediainfomap.h +++ b/pc/trackmediainfomap.h @@ -58,6 +58,13 @@ class TrackMediaInfoMap { const cricket::VideoReceiverInfo* GetVideoReceiverInfo( const VideoTrackInterface& remote_video_track) const; + const cricket::VoiceSenderInfo* GetVoiceSenderInfoBySsrc(uint32_t ssrc) const; + const cricket::VoiceReceiverInfo* GetVoiceReceiverInfoBySsrc( + uint32_t ssrc) const; + const cricket::VideoSenderInfo* GetVideoSenderInfoBySsrc(uint32_t ssrc) const; + const cricket::VideoReceiverInfo* GetVideoReceiverInfoBySsrc( + uint32_t ssrc) const; + rtc::scoped_refptr GetAudioTrack( const cricket::VoiceSenderInfo& voice_sender_info) const; rtc::scoped_refptr GetAudioTrack( @@ -95,6 +102,11 @@ class TrackMediaInfoMap { std::map> video_track_by_receiver_info_; + // These maps map SSRCs to the corresponding voice or video info objects. + std::map voice_info_by_sender_ssrc_; + std::map voice_info_by_receiver_ssrc_; + std::map video_info_by_sender_ssrc_; + std::map video_info_by_receiver_ssrc_; }; } // namespace webrtc diff --git a/pc/trackmediainfomap_unittest.cc b/pc/trackmediainfomap_unittest.cc index 65e75c46b6..c05abe17a0 100644 --- a/pc/trackmediainfomap_unittest.cc +++ b/pc/trackmediainfomap_unittest.cc @@ -386,6 +386,20 @@ TEST_F(TrackMediaInfoMapTest, SingleSenderReceiverPerTrackWithSsrcNotUnique) { remote_video_track_.get()); } +TEST_F(TrackMediaInfoMapTest, SsrcLookupFunction) { + AddRtpSenderWithSsrcs({1}, local_audio_track_); + AddRtpReceiverWithSsrcs({2}, remote_audio_track_); + AddRtpSenderWithSsrcs({3}, local_video_track_); + AddRtpReceiverWithSsrcs({4}, remote_video_track_); + CreateMap(); + EXPECT_TRUE(map_->GetVoiceSenderInfoBySsrc(1)); + EXPECT_TRUE(map_->GetVoiceReceiverInfoBySsrc(2)); + EXPECT_TRUE(map_->GetVideoSenderInfoBySsrc(3)); + EXPECT_TRUE(map_->GetVideoReceiverInfoBySsrc(4)); + EXPECT_FALSE(map_->GetVoiceSenderInfoBySsrc(2)); + EXPECT_FALSE(map_->GetVoiceSenderInfoBySsrc(1024)); +} + // Death tests. // Disabled on Android because death tests misbehave on Android, see // base/test/gtest_util.h.