From 646fda0212b17f55b73e0f52f455fc48a019558a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Bostr=C3=B6m?= Date: Wed, 22 May 2019 15:49:42 +0200 Subject: [PATCH] Implement RTCMediaSourceStats and friends in standard getStats(). MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This implements RTCAudioSourceStats and RTCVideoSourceStats, both inheriting from abstract dictionary RTCMediaSourceStats: https://w3c.github.io/webrtc-stats/#dom-rtcmediasourcestats All members are implemented except for the total "frames" counter: - trackIdentifier - kind - width - height - framesPerSecond This means to make googFrameWidthInput, googFrameHeightInput and googFrameRateInput obsolete. Implemented using the same code path as the goog stats, there are some minor bugs that should be fixed in the future, but not this CL: 1. We create media-source objects on a per-track attachment basis. If the same track is attached multiple times this results in multiple media-source objects, but the spec says it should be on a per-source basis. 2. framesPerSecond is only calculated after connecting (when we have a sender with SSRC), but if collected on a per-source basis the source should be able to tell us the FPS whether or not we are sending it. Bug: webrtc:10453 Change-Id: I23705a79f15075dca2536275934af1904a7f0d39 Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/137804 Commit-Queue: Henrik Boström Reviewed-by: Harald Alvestrand Cr-Commit-Position: refs/heads/master@{#28028} --- api/stats/rtc_stats.h | 18 ++ api/stats/rtcstats_objects.h | 46 ++++ pc/rtc_stats_collector.cc | 97 +++++++- pc/rtc_stats_collector.h | 4 + pc/rtc_stats_collector_unittest.cc | 365 ++++++++++++++++++++++++----- pc/rtc_stats_integrationtest.cc | 72 +++++- pc/rtc_stats_traversal.cc | 11 +- stats/rtcstats_objects.cc | 73 ++++++ 8 files changed, 623 insertions(+), 63 deletions(-) diff --git a/api/stats/rtc_stats.h b/api/stats/rtc_stats.h index bb23490ce4..4361163576 100644 --- a/api/stats/rtc_stats.h +++ b/api/stats/rtc_stats.h @@ -179,6 +179,24 @@ class RTC_EXPORT RTCStats { return local_var_members_vec; \ } +// A version of WEBRTC_RTCSTATS_IMPL() where "..." is omitted, used to avoid a +// compile error on windows. This is used if the stats dictionary does not +// declare any members of its own (but perhaps its parent dictionary does). +#define WEBRTC_RTCSTATS_IMPL_NO_MEMBERS(this_class, parent_class, type_str) \ + const char this_class::kType[] = type_str; \ + \ + std::unique_ptr this_class::copy() const { \ + return std::unique_ptr(new this_class(*this)); \ + } \ + \ + const char* this_class::type() const { return this_class::kType; } \ + \ + std::vector \ + this_class::MembersOfThisObjectAndAncestors( \ + size_t local_var_additional_capacity) const { \ + return parent_class::MembersOfThisObjectAndAncestors(0); \ + } + // Non-standard stats members can be exposed to the JavaScript API in Chrome // e.g. through origin trials. The group ID can be used by the blink layer to // determine if a stats member should be exposed or not. Multiple non-standard diff --git a/api/stats/rtcstats_objects.h b/api/stats/rtcstats_objects.h index 2e6cd3b9bb..cbcc8fa37d 100644 --- a/api/stats/rtcstats_objects.h +++ b/api/stats/rtcstats_objects.h @@ -279,6 +279,7 @@ class RTC_EXPORT RTCMediaStreamTrackStats final : public RTCStats { ~RTCMediaStreamTrackStats() override; RTCStatsMember track_identifier; + RTCStatsMember media_source_id; RTCStatsMember remote_source; RTCStatsMember ended; // TODO(hbos): |RTCStatsCollector| does not return stats for detached tracks. @@ -450,6 +451,7 @@ class RTC_EXPORT RTCOutboundRTPStreamStats final : public RTCRTPStreamStats { RTCOutboundRTPStreamStats(const RTCOutboundRTPStreamStats& other); ~RTCOutboundRTPStreamStats() override; + RTCStatsMember media_source_id; RTCStatsMember packets_sent; RTCStatsMember retransmitted_packets_sent; RTCStatsMember bytes_sent; @@ -466,6 +468,50 @@ class RTC_EXPORT RTCOutboundRTPStreamStats final : public RTCRTPStreamStats { RTCStatsMember content_type; }; +// https://w3c.github.io/webrtc-stats/#dom-rtcmediasourcestats +class RTC_EXPORT RTCMediaSourceStats : public RTCStats { + public: + WEBRTC_RTCSTATS_DECL(); + + RTCMediaSourceStats(const RTCMediaSourceStats& other); + ~RTCMediaSourceStats() override; + + RTCStatsMember track_identifier; + RTCStatsMember kind; + + protected: + RTCMediaSourceStats(const std::string& id, int64_t timestamp_us); + RTCMediaSourceStats(std::string&& id, int64_t timestamp_us); +}; + +// https://w3c.github.io/webrtc-stats/#dom-rtcaudiosourcestats +class RTC_EXPORT RTCAudioSourceStats final : public RTCMediaSourceStats { + public: + WEBRTC_RTCSTATS_DECL(); + + RTCAudioSourceStats(const std::string& id, int64_t timestamp_us); + RTCAudioSourceStats(std::string&& id, int64_t timestamp_us); + RTCAudioSourceStats(const RTCAudioSourceStats& other); + ~RTCAudioSourceStats() override; +}; + +// https://w3c.github.io/webrtc-stats/#dom-rtcvideosourcestats +class RTC_EXPORT RTCVideoSourceStats final : public RTCMediaSourceStats { + public: + WEBRTC_RTCSTATS_DECL(); + + RTCVideoSourceStats(const std::string& id, int64_t timestamp_us); + RTCVideoSourceStats(std::string&& id, int64_t timestamp_us); + RTCVideoSourceStats(const RTCVideoSourceStats& other); + ~RTCVideoSourceStats() override; + + RTCStatsMember width; + RTCStatsMember height; + // TODO(hbos): Implement this metric. + RTCStatsMember frames; + RTCStatsMember frames_per_second; +}; + // https://w3c.github.io/webrtc-stats/#transportstats-dict* class RTC_EXPORT RTCTransportStats final : public RTCStats { public: diff --git a/pc/rtc_stats_collector.cc b/pc/rtc_stats_collector.cc index 3e11a92061..9f82abe663 100644 --- a/pc/rtc_stats_collector.cc +++ b/pc/rtc_stats_collector.cc @@ -34,6 +34,7 @@ namespace webrtc { namespace { +// TODO(https://crbug.com/webrtc/10656): Consider making IDs less predictable. std::string RTCCertificateIDFromFingerprint(const std::string& fingerprint) { return "RTCCertificate_" + fingerprint; } @@ -91,6 +92,16 @@ std::string RTCOutboundRTPStreamStatsIDFromSSRC(bool audio, uint32_t ssrc) { return sb.str(); } +std::string RTCMediaSourceStatsIDFromKindAndAttachment( + cricket::MediaType media_type, + int attachment_id) { + char buf[1024]; + rtc::SimpleStringBuilder sb(buf); + sb << "RTC" << (media_type == cricket::MEDIA_TYPE_AUDIO ? "Audio" : "Video") + << "Source_" << attachment_id; + return sb.str(); +} + const char* CandidateTypeToRTCIceCandidateType(const std::string& type) { if (type == cricket::LOCAL_PORT_TYPE) return RTCIceCandidateType::kHost; @@ -439,6 +450,9 @@ ProduceMediaStreamTrackStatsFromVoiceSenderInfo( timestamp_us, RTCMediaStreamTrackKind::kAudio)); SetMediaStreamTrackStatsFromMediaStreamTrackInterface( audio_track, audio_track_stats.get()); + audio_track_stats->media_source_id = + RTCMediaSourceStatsIDFromKindAndAttachment(cricket::MEDIA_TYPE_AUDIO, + attachment_id); audio_track_stats->remote_source = false; audio_track_stats->detached = false; if (voice_sender_info.audio_level >= 0) { @@ -524,11 +538,13 @@ ProduceMediaStreamTrackStatsFromVideoSenderInfo( std::unique_ptr video_track_stats( new RTCMediaStreamTrackStats( RTCMediaStreamTrackStatsIDFromDirectionAndAttachment(kSender, - attachment_id), timestamp_us, RTCMediaStreamTrackKind::kVideo)); SetMediaStreamTrackStatsFromMediaStreamTrackInterface( video_track, video_track_stats.get()); + video_track_stats->media_source_id = + RTCMediaSourceStatsIDFromKindAndAttachment(cricket::MEDIA_TYPE_VIDEO, + attachment_id); video_track_stats->remote_source = false; video_track_stats->detached = false; video_track_stats->frame_width = static_cast( @@ -930,6 +946,7 @@ void RTCStatsCollector::ProducePartialResultsOnSignalingThreadImpl( ProduceDataChannelStats_s(timestamp_us, partial_report); ProduceMediaStreamStats_s(timestamp_us, partial_report); ProduceMediaStreamTrackStats_s(timestamp_us, partial_report); + ProduceMediaSourceStats_s(timestamp_us, partial_report); ProducePeerConnectionStats_s(timestamp_us, partial_report); } @@ -1265,6 +1282,64 @@ void RTCStatsCollector::ProduceMediaStreamTrackStats_s( } } +void RTCStatsCollector::ProduceMediaSourceStats_s( + int64_t timestamp_us, + RTCStatsReport* report) const { + RTC_DCHECK(signaling_thread_->IsCurrent()); + for (const RtpTransceiverStatsInfo& transceiver_stats_info : + transceiver_stats_infos_) { + const auto& track_media_info_map = + transceiver_stats_info.track_media_info_map; + for (const auto& sender : transceiver_stats_info.transceiver->senders()) { + const auto& sender_internal = sender->internal(); + const auto& track = sender_internal->track(); + if (!track) + continue; + // TODO(hbos): The same track could be attached to multiple senders which + // should result in multiple senders referencing the same media source + // stats. When all media source related metrics are moved to the track's + // source (e.g. input frame rate is moved from cricket::VideoSenderInfo to + // VideoTrackSourceInterface::Stats), don't create separate media source + // stats objects on a per-attachment basis. + std::unique_ptr media_source_stats; + if (track->kind() == MediaStreamTrackInterface::kAudioKind) { + media_source_stats = absl::make_unique( + RTCMediaSourceStatsIDFromKindAndAttachment( + cricket::MEDIA_TYPE_AUDIO, sender_internal->AttachmentId()), + timestamp_us); + } else { + RTC_DCHECK_EQ(MediaStreamTrackInterface::kVideoKind, track->kind()); + auto video_source_stats = absl::make_unique( + RTCMediaSourceStatsIDFromKindAndAttachment( + cricket::MEDIA_TYPE_VIDEO, sender_internal->AttachmentId()), + timestamp_us); + auto* video_track = static_cast(track.get()); + auto* video_source = video_track->GetSource(); + VideoTrackSourceInterface::Stats source_stats; + if (video_source && video_source->GetStats(&source_stats)) { + video_source_stats->width = source_stats.input_width; + video_source_stats->height = source_stats.input_height; + } + // TODO(hbos): Source stats should not depend on whether or not we are + // connected/have an SSRC assigned. Related to + // https://crbug.com/webrtc/8694 (using ssrc 0 to indicate "none"). + if (sender_internal->ssrc() != 0) { + auto* sender_info = track_media_info_map->GetVideoSenderInfoBySsrc( + sender_internal->ssrc()); + if (sender_info) { + video_source_stats->frames_per_second = + sender_info->framerate_input; + } + } + media_source_stats = std::move(video_source_stats); + } + media_source_stats->track_identifier = track->id(); + media_source_stats->kind = track->kind(); + report->AddStats(std::move(media_source_stats)); + } + } +} + void RTCStatsCollector::ProducePeerConnectionStats_s( int64_t timestamp_us, RTCStatsReport* report) const { RTC_DCHECK(signaling_thread_->IsCurrent()); @@ -1340,10 +1415,14 @@ void RTCStatsCollector::ProduceAudioRTPStreamStats_n( rtc::scoped_refptr audio_track = track_media_info_map.GetAudioTrack(voice_sender_info); if (audio_track) { + int attachment_id = + track_media_info_map.GetAttachmentIdByTrack(audio_track).value(); outbound_audio->track_id = - RTCMediaStreamTrackStatsIDFromDirectionAndAttachment( - kSender, - track_media_info_map.GetAttachmentIdByTrack(audio_track).value()); + RTCMediaStreamTrackStatsIDFromDirectionAndAttachment(kSender, + attachment_id); + outbound_audio->media_source_id = + RTCMediaSourceStatsIDFromKindAndAttachment(cricket::MEDIA_TYPE_AUDIO, + attachment_id); } outbound_audio->transport_id = transport_id; report->AddStats(std::move(outbound_audio)); @@ -1397,10 +1476,14 @@ void RTCStatsCollector::ProduceVideoRTPStreamStats_n( rtc::scoped_refptr video_track = track_media_info_map.GetVideoTrack(video_sender_info); if (video_track) { + int attachment_id = + track_media_info_map.GetAttachmentIdByTrack(video_track).value(); outbound_video->track_id = - RTCMediaStreamTrackStatsIDFromDirectionAndAttachment( - kSender, - track_media_info_map.GetAttachmentIdByTrack(video_track).value()); + RTCMediaStreamTrackStatsIDFromDirectionAndAttachment(kSender, + attachment_id); + outbound_video->media_source_id = + RTCMediaSourceStatsIDFromKindAndAttachment(cricket::MEDIA_TYPE_VIDEO, + attachment_id); } outbound_video->transport_id = transport_id; report->AddStats(std::move(outbound_video)); diff --git a/pc/rtc_stats_collector.h b/pc/rtc_stats_collector.h index 4837fc0abe..82501db038 100644 --- a/pc/rtc_stats_collector.h +++ b/pc/rtc_stats_collector.h @@ -182,6 +182,10 @@ class RTCStatsCollector : public virtual rtc::RefCountInterface, // Produces |RTCMediaStreamTrackStats|. void ProduceMediaStreamTrackStats_s(int64_t timestamp_us, RTCStatsReport* report) const; + // Produces RTCMediaSourceStats, including RTCAudioSourceStats and + // RTCVideoSourceStats. + void ProduceMediaSourceStats_s(int64_t timestamp_us, + RTCStatsReport* report) const; // Produces |RTCPeerConnectionStats|. void ProducePeerConnectionStats_s(int64_t timestamp_us, RTCStatsReport* report) const; diff --git a/pc/rtc_stats_collector_unittest.cc b/pc/rtc_stats_collector_unittest.cc index 78530dfd63..6cd63196a7 100644 --- a/pc/rtc_stats_collector_unittest.cc +++ b/pc/rtc_stats_collector_unittest.cc @@ -89,6 +89,14 @@ void PrintTo(const RTCOutboundRTPStreamStats& stats, ::std::ostream* os) { *os << stats.ToJson(); } +void PrintTo(const RTCAudioSourceStats& stats, ::std::ostream* os) { + *os << stats.ToJson(); +} + +void PrintTo(const RTCVideoSourceStats& stats, ::std::ostream* os) { + *os << stats.ToJson(); +} + void PrintTo(const RTCTransportStats& stats, ::std::ostream* os) { *os << stats.ToJson(); } @@ -195,19 +203,62 @@ class FakeAudioTrackForStats : public MediaStreamTrack { } }; +class FakeVideoTrackSourceForStats : public VideoTrackSourceInterface { + public: + static rtc::scoped_refptr Create( + int input_width, + int input_height) { + return rtc::scoped_refptr( + new rtc::RefCountedObject(input_width, + input_height)); + } + + FakeVideoTrackSourceForStats(int input_width, int input_height) + : input_width_(input_width), input_height_(input_height) {} + ~FakeVideoTrackSourceForStats() override {} + + // VideoTrackSourceInterface + bool is_screencast() const override { return false; } + absl::optional needs_denoising() const override { return false; } + bool GetStats(VideoTrackSourceInterface::Stats* stats) override { + stats->input_width = input_width_; + stats->input_height = input_height_; + return true; + } + // MediaSourceInterface (part of VideoTrackSourceInterface) + MediaSourceInterface::SourceState state() const override { + return MediaSourceInterface::SourceState::kLive; + } + bool remote() const override { return false; } + // NotifierInterface (part of MediaSourceInterface) + void RegisterObserver(ObserverInterface* observer) override {} + void UnregisterObserver(ObserverInterface* observer) override {} + // rtc::VideoSourceInterface (part of VideoTrackSourceInterface) + void AddOrUpdateSink(rtc::VideoSinkInterface* sink, + const rtc::VideoSinkWants& wants) override {} + void RemoveSink(rtc::VideoSinkInterface* sink) override {} + + private: + int input_width_; + int input_height_; +}; + class FakeVideoTrackForStats : public MediaStreamTrack { public: static rtc::scoped_refptr Create( const std::string& id, - MediaStreamTrackInterface::TrackState state) { + MediaStreamTrackInterface::TrackState state, + rtc::scoped_refptr source) { rtc::scoped_refptr video_track( - new rtc::RefCountedObject(id)); + new rtc::RefCountedObject(id, + std::move(source))); video_track->set_state(state); return video_track; } - explicit FakeVideoTrackForStats(const std::string& id) - : MediaStreamTrack(id) {} + FakeVideoTrackForStats(const std::string& id, + rtc::scoped_refptr source) + : MediaStreamTrack(id), source_(source) {} std::string kind() const override { return MediaStreamTrackInterface::kVideoKind; @@ -217,7 +268,12 @@ class FakeVideoTrackForStats : public MediaStreamTrack { const rtc::VideoSinkWants& wants) override {} void RemoveSink(rtc::VideoSinkInterface* sink) override {} - VideoTrackSourceInterface* GetSource() const override { return nullptr; } + VideoTrackSourceInterface* GetSource() const override { + return source_.get(); + } + + private: + rtc::scoped_refptr source_; }; rtc::scoped_refptr CreateFakeTrack( @@ -228,24 +284,26 @@ rtc::scoped_refptr CreateFakeTrack( return FakeAudioTrackForStats::Create(track_id, track_state); } else { RTC_DCHECK_EQ(media_type, cricket::MEDIA_TYPE_VIDEO); - return FakeVideoTrackForStats::Create(track_id, track_state); + return FakeVideoTrackForStats::Create(track_id, track_state, nullptr); } } rtc::scoped_refptr CreateMockSender( - const rtc::scoped_refptr& track, + cricket::MediaType media_type, + rtc::scoped_refptr track, uint32_t ssrc, int attachment_id, std::vector local_stream_ids) { + RTC_DCHECK(!track || + (track->kind() == MediaStreamTrackInterface::kAudioKind && + media_type == cricket::MEDIA_TYPE_AUDIO) || + (track->kind() == MediaStreamTrackInterface::kVideoKind && + media_type == cricket::MEDIA_TYPE_VIDEO)); rtc::scoped_refptr sender( new rtc::RefCountedObject()); EXPECT_CALL(*sender, track()).WillRepeatedly(Return(track)); EXPECT_CALL(*sender, ssrc()).WillRepeatedly(Return(ssrc)); - EXPECT_CALL(*sender, media_type()) - .WillRepeatedly( - Return(track->kind() == MediaStreamTrackInterface::kAudioKind - ? cricket::MEDIA_TYPE_AUDIO - : cricket::MEDIA_TYPE_VIDEO)); + EXPECT_CALL(*sender, media_type()).WillRepeatedly(Return(media_type)); EXPECT_CALL(*sender, GetParameters()).WillRepeatedly(Invoke([ssrc]() { RtpParameters params; params.encodings.push_back(RtpEncodingParameters()); @@ -325,7 +383,8 @@ class RTCStatsCollectorWrapper { cricket::MediaType media_type, const std::string& track_id, uint32_t ssrc, - bool add_stream) { + bool add_stream, + int attachment_id) { rtc::scoped_refptr local_stream; if (add_stream) { local_stream = MediaStream::Create("LocalStreamId"); @@ -348,7 +407,7 @@ class RTCStatsCollectorWrapper { } rtc::scoped_refptr sender = - CreateMockSender(track, ssrc, 50, {}); + CreateMockSender(media_type, track, ssrc, attachment_id, {}); pc_->AddSender(sender); return sender; } @@ -388,6 +447,7 @@ class RTCStatsCollectorWrapper { // |[Voice/Video][Sender/Receiver]Info| and their SSRCs. Local tracks can be // associated with multiple |[Voice/Video]SenderInfo|s, remote tracks can only // be associated with one |[Voice/Video]ReceiverInfo|. + // Senders get assigned attachment ID "ssrc + 10". void CreateMockRtpSendersReceiversAndChannels( std::initializer_list< std::pair> @@ -407,7 +467,6 @@ class RTCStatsCollectorWrapper { cricket::VideoMediaInfo video_media_info; // Local audio tracks and voice sender infos - int attachment_id = 147; for (auto& pair : local_audio_track_info_pairs) { MediaStreamTrackInterface* local_audio_track = pair.first; const cricket::VoiceSenderInfo& voice_sender_info = pair.second; @@ -416,14 +475,14 @@ class RTCStatsCollectorWrapper { voice_media_info.senders.push_back(voice_sender_info); rtc::scoped_refptr rtp_sender = CreateMockSender( + cricket::MEDIA_TYPE_AUDIO, rtc::scoped_refptr(local_audio_track), - voice_sender_info.local_stats[0].ssrc, attachment_id++, - local_stream_ids); + voice_sender_info.local_stats[0].ssrc, + voice_sender_info.local_stats[0].ssrc + 10, local_stream_ids); pc_->AddSender(rtp_sender); } // Remote audio tracks and voice receiver infos - attachment_id = 181; for (auto& pair : remote_audio_track_info_pairs) { MediaStreamTrackInterface* remote_audio_track = pair.first; const cricket::VoiceReceiverInfo& voice_receiver_info = pair.second; @@ -434,14 +493,14 @@ class RTCStatsCollectorWrapper { rtc::scoped_refptr rtp_receiver = CreateMockReceiver( rtc::scoped_refptr(remote_audio_track), - voice_receiver_info.local_stats[0].ssrc, attachment_id++); + voice_receiver_info.local_stats[0].ssrc, + voice_receiver_info.local_stats[0].ssrc + 10); EXPECT_CALL(*rtp_receiver, streams()) .WillRepeatedly(Return(remote_streams)); pc_->AddReceiver(rtp_receiver); } // Local video tracks and video sender infos - attachment_id = 151; for (auto& pair : local_video_track_info_pairs) { MediaStreamTrackInterface* local_video_track = pair.first; const cricket::VideoSenderInfo& video_sender_info = pair.second; @@ -450,14 +509,14 @@ class RTCStatsCollectorWrapper { video_media_info.senders.push_back(video_sender_info); rtc::scoped_refptr rtp_sender = CreateMockSender( + cricket::MEDIA_TYPE_VIDEO, rtc::scoped_refptr(local_video_track), - video_sender_info.local_stats[0].ssrc, attachment_id++, - local_stream_ids); + video_sender_info.local_stats[0].ssrc, + video_sender_info.local_stats[0].ssrc + 10, local_stream_ids); pc_->AddSender(rtp_sender); } // Remote video tracks and video receiver infos - attachment_id = 191; for (auto& pair : remote_video_track_info_pairs) { MediaStreamTrackInterface* remote_video_track = pair.first; const cricket::VideoReceiverInfo& video_receiver_info = pair.second; @@ -468,7 +527,8 @@ class RTCStatsCollectorWrapper { rtc::scoped_refptr rtp_receiver = CreateMockReceiver( rtc::scoped_refptr(remote_video_track), - video_receiver_info.local_stats[0].ssrc, attachment_id++); + video_receiver_info.local_stats[0].ssrc, + video_receiver_info.local_stats[0].ssrc + 10); EXPECT_CALL(*rtp_receiver, streams()) .WillRepeatedly(Return(remote_streams)); pc_->AddReceiver(rtp_receiver); @@ -536,6 +596,7 @@ class RTCStatsCollectorTest : public ::testing::Test { std::string receiver_track_id; std::string remote_stream_id; std::string peer_connection_id; + std::string media_source_id; }; // Sets up the example stats graph (see ASCII art below) used for testing the @@ -583,7 +644,7 @@ class RTCStatsCollectorTest : public ::testing::Test { video_media_channel->SetStats(video_media_info); // track (sender) graph.sender = stats_->SetupLocalTrackAndSender( - cricket::MEDIA_TYPE_VIDEO, "LocalVideoTrackID", 3, false); + cricket::MEDIA_TYPE_VIDEO, "LocalVideoTrackID", 3, false, 50); graph.sender_track_id = "RTCMediaStreamTrack_sender_" + rtc::ToString(graph.sender->AttachmentId()); // track (receiver) and stream (remote stream) @@ -594,20 +655,25 @@ class RTCStatsCollectorTest : public ::testing::Test { graph.remote_stream_id = "RTCMediaStream_RemoteStreamId"; // peer-connection graph.peer_connection_id = "RTCPeerConnection"; + // media-source (kind: video) + graph.media_source_id = + "RTCVideoSource_" + rtc::ToString(graph.sender->AttachmentId()); // Expected stats graph: // - // track (sender) stream (remote stream) ---> track (receiver) - // ^ ^ - // | | - // outbound-rtp inbound-rtp ---------------+ - // | | | | - // v v v v - // codec (send) transport codec (recv) peer-connection + // +--- track (sender) stream (remote stream) ---> track (receiver) + // | ^ ^ + // | | | + // | +--------- outbound-rtp inbound-rtp ---------------+ + // | | | | | | + // | | v v v v + // | | codec (send) transport codec (recv) peer-connection + // v v + // media-source // Verify the stats graph is set up correctly. graph.full_report = stats_->GetStatsReport(); - EXPECT_EQ(graph.full_report->size(), 9u); + EXPECT_EQ(graph.full_report->size(), 10u); EXPECT_TRUE(graph.full_report->Get(graph.send_codec_id)); EXPECT_TRUE(graph.full_report->Get(graph.recv_codec_id)); EXPECT_TRUE(graph.full_report->Get(graph.outbound_rtp_id)); @@ -617,8 +683,13 @@ class RTCStatsCollectorTest : public ::testing::Test { EXPECT_TRUE(graph.full_report->Get(graph.receiver_track_id)); EXPECT_TRUE(graph.full_report->Get(graph.remote_stream_id)); EXPECT_TRUE(graph.full_report->Get(graph.peer_connection_id)); + EXPECT_TRUE(graph.full_report->Get(graph.media_source_id)); + const auto& sender_track = graph.full_report->Get(graph.sender_track_id) + ->cast_to(); + EXPECT_EQ(*sender_track.media_source_id, graph.media_source_id); const auto& outbound_rtp = graph.full_report->Get(graph.outbound_rtp_id) ->cast_to(); + EXPECT_EQ(*outbound_rtp.media_source_id, graph.media_source_id); EXPECT_EQ(*outbound_rtp.codec_id, graph.send_codec_id); EXPECT_EQ(*outbound_rtp.track_id, graph.sender_track_id); EXPECT_EQ(*outbound_rtp.transport_id, graph.transport_id); @@ -1386,6 +1457,8 @@ TEST_F(RTCStatsCollectorTest, IdForType(report), report->timestamp_us(), RTCMediaStreamTrackKind::kAudio); expected_local_audio_track_ssrc1.track_identifier = local_audio_track->id(); + expected_local_audio_track_ssrc1.media_source_id = + "RTCAudioSource_11"; // Attachment ID = SSRC + 10 expected_local_audio_track_ssrc1.remote_source = false; expected_local_audio_track_ssrc1.ended = true; expected_local_audio_track_ssrc1.detached = false; @@ -1457,6 +1530,8 @@ TEST_F(RTCStatsCollectorTest, IdForType(report), report->timestamp_us(), RTCMediaStreamTrackKind::kAudio); expected_remote_audio_track.track_identifier = remote_audio_track->id(); + // |expected_remote_audio_track.media_source_id| should be undefined + // because the track is remote. expected_remote_audio_track.remote_source = true; expected_remote_audio_track.ended = false; expected_remote_audio_track.detached = false; @@ -1530,6 +1605,8 @@ TEST_F(RTCStatsCollectorTest, stats_of_track_type[0]->id(), report->timestamp_us(), RTCMediaStreamTrackKind::kVideo); expected_local_video_track_ssrc1.track_identifier = local_video_track->id(); + expected_local_video_track_ssrc1.media_source_id = + "RTCVideoSource_11"; // Attachment ID = SSRC + 10 expected_local_video_track_ssrc1.remote_source = false; expected_local_video_track_ssrc1.ended = false; expected_local_video_track_ssrc1.detached = false; @@ -1601,6 +1678,8 @@ TEST_F(RTCStatsCollectorTest, RTCMediaStreamTrackKind::kVideo); expected_remote_video_track_ssrc3.track_identifier = remote_video_track_ssrc3->id(); + // |expected_remote_video_track_ssrc3.media_source_id| should be undefined + // because the track is remote. expected_remote_video_track_ssrc3.remote_source = true; expected_remote_video_track_ssrc3.ended = true; expected_remote_video_track_ssrc3.detached = false; @@ -1801,12 +1880,14 @@ TEST_F(RTCStatsCollectorTest, CollectRTCOutboundRTPStreamStats_Audio) { auto* voice_media_channel = pc_->AddVoiceChannel("AudioMid", "TransportName"); voice_media_channel->SetStats(voice_media_info); stats_->SetupLocalTrackAndSender(cricket::MEDIA_TYPE_AUDIO, - "LocalAudioTrackID", 1, true); + "LocalAudioTrackID", 1, true, + /*attachment_id=*/50); rtc::scoped_refptr report = stats_->GetStatsReport(); RTCOutboundRTPStreamStats expected_audio("RTCOutboundRTPAudioStream_1", report->timestamp_us()); + expected_audio.media_source_id = "RTCAudioSource_50"; expected_audio.ssrc = 1; expected_audio.is_remote = false; expected_audio.media_type = "audio"; @@ -1865,7 +1946,8 @@ TEST_F(RTCStatsCollectorTest, CollectRTCOutboundRTPStreamStats_Video) { auto* video_media_channel = pc_->AddVideoChannel("VideoMid", "TransportName"); video_media_channel->SetStats(video_media_info); stats_->SetupLocalTrackAndSender(cricket::MEDIA_TYPE_VIDEO, - "LocalVideoTrackID", 1, true); + "LocalVideoTrackID", 1, true, + /*attachment_id=*/50); rtc::scoped_refptr report = stats_->GetStatsReport(); @@ -1876,6 +1958,7 @@ TEST_F(RTCStatsCollectorTest, CollectRTCOutboundRTPStreamStats_Video) { RTCOutboundRTPStreamStats expected_video(stats_of_my_type[0]->id(), report->timestamp_us()); + expected_video.media_source_id = "RTCVideoSource_50"; expected_video.ssrc = 1; expected_video.is_remote = false; expected_video.media_type = "video"; @@ -2081,12 +2164,14 @@ TEST_F(RTCStatsCollectorTest, CollectNoStreamRTCOutboundRTPStreamStats_Audio) { auto* voice_media_channel = pc_->AddVoiceChannel("AudioMid", "TransportName"); voice_media_channel->SetStats(voice_media_info); stats_->SetupLocalTrackAndSender(cricket::MEDIA_TYPE_AUDIO, - "LocalAudioTrackID", 1, false); + "LocalAudioTrackID", 1, false, + /*attachment_id=*/50); rtc::scoped_refptr report = stats_->GetStatsReport(); RTCOutboundRTPStreamStats expected_audio("RTCOutboundRTPAudioStream_1", report->timestamp_us()); + expected_audio.media_source_id = "RTCAudioSource_50"; expected_audio.ssrc = 1; expected_audio.is_remote = false; expected_audio.media_type = "audio"; @@ -2108,22 +2193,194 @@ TEST_F(RTCStatsCollectorTest, CollectNoStreamRTCOutboundRTPStreamStats_Audio) { EXPECT_TRUE(report->Get(*expected_audio.codec_id)); } +TEST_F(RTCStatsCollectorTest, RTCAudioSourceStatsCollectedForSenderWithTrack) { + const uint32_t kSsrc = 4; + const int kAttachmentId = 42; + + 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 = kSsrc; + auto* voice_media_channel = pc_->AddVoiceChannel("AudioMid", "TransportName"); + voice_media_channel->SetStats(voice_media_info); + stats_->SetupLocalTrackAndSender(cricket::MEDIA_TYPE_AUDIO, + "LocalAudioTrackID", kSsrc, false, + kAttachmentId); + + rtc::scoped_refptr report = stats_->GetStatsReport(); + + RTCAudioSourceStats expected_audio("RTCAudioSource_42", + report->timestamp_us()); + expected_audio.track_identifier = "LocalAudioTrackID"; + expected_audio.kind = "audio"; + + ASSERT_TRUE(report->Get(expected_audio.id())); + EXPECT_EQ(report->Get(expected_audio.id())->cast_to(), + expected_audio); +} + +TEST_F(RTCStatsCollectorTest, RTCVideoSourceStatsCollectedForSenderWithTrack) { + const uint32_t kSsrc = 4; + const int kAttachmentId = 42; + const int kVideoSourceWidth = 12; + const int kVideoSourceHeight = 34; + + cricket::VideoMediaInfo video_media_info; + video_media_info.senders.push_back(cricket::VideoSenderInfo()); + video_media_info.senders[0].local_stats.push_back(cricket::SsrcSenderInfo()); + video_media_info.senders[0].local_stats[0].ssrc = kSsrc; + video_media_info.senders[0].framerate_input = 29; + auto* video_media_channel = pc_->AddVideoChannel("VideoMid", "TransportName"); + video_media_channel->SetStats(video_media_info); + + auto video_source = FakeVideoTrackSourceForStats::Create(kVideoSourceWidth, + kVideoSourceHeight); + auto video_track = FakeVideoTrackForStats::Create( + "LocalVideoTrackID", MediaStreamTrackInterface::kLive, video_source); + rtc::scoped_refptr sender = CreateMockSender( + cricket::MEDIA_TYPE_VIDEO, video_track, kSsrc, kAttachmentId, {}); + pc_->AddSender(sender); + + rtc::scoped_refptr report = stats_->GetStatsReport(); + + RTCVideoSourceStats expected_video("RTCVideoSource_42", + report->timestamp_us()); + expected_video.track_identifier = "LocalVideoTrackID"; + expected_video.kind = "video"; + expected_video.width = kVideoSourceWidth; + expected_video.height = kVideoSourceHeight; + // |expected_video.frames| is expected to be undefined because it is not set. + // TODO(hbos): When implemented, set its expected value here. + expected_video.frames_per_second = 29; + + ASSERT_TRUE(report->Get(expected_video.id())); + EXPECT_EQ(report->Get(expected_video.id())->cast_to(), + expected_video); +} + +// This test exercises the current behavior and code path, but the correct +// behavior is to report frame rate even if we have no SSRC. +// TODO(hbos): When we know the frame rate even if we have no SSRC, update the +// expectations of this test. +TEST_F(RTCStatsCollectorTest, + RTCVideoSourceStatsMissingFrameRateWhenSenderHasNoSsrc) { + // TODO(https://crbug.com/webrtc/8694): When 0 is no longer a magic value for + // "none", update this test. + const uint32_t kNoSsrc = 0; + const int kAttachmentId = 42; + const int kVideoSourceWidth = 12; + const int kVideoSourceHeight = 34; + + cricket::VideoMediaInfo video_media_info; + video_media_info.senders.push_back(cricket::VideoSenderInfo()); + video_media_info.senders[0].local_stats.push_back(cricket::SsrcSenderInfo()); + video_media_info.senders[0].framerate_input = 29; + auto* video_media_channel = pc_->AddVideoChannel("VideoMid", "TransportName"); + video_media_channel->SetStats(video_media_info); + + auto video_source = FakeVideoTrackSourceForStats::Create(kVideoSourceWidth, + kVideoSourceHeight); + auto video_track = FakeVideoTrackForStats::Create( + "LocalVideoTrackID", MediaStreamTrackInterface::kLive, video_source); + rtc::scoped_refptr sender = CreateMockSender( + cricket::MEDIA_TYPE_VIDEO, video_track, kNoSsrc, kAttachmentId, {}); + pc_->AddSender(sender); + + rtc::scoped_refptr report = stats_->GetStatsReport(); + ASSERT_TRUE(report->Get("RTCVideoSource_42")); + auto video_stats = + report->Get("RTCVideoSource_42")->cast_to(); + EXPECT_FALSE(video_stats.frames_per_second.is_defined()); +} + +// The track not having a source is not expected to be true in practise, but +// this is true in some tests relying on fakes. This test covers that code path. +TEST_F(RTCStatsCollectorTest, + RTCVideoSourceStatsMissingResolutionWhenTrackHasNoSource) { + const uint32_t kSsrc = 4; + const int kAttachmentId = 42; + + cricket::VideoMediaInfo video_media_info; + video_media_info.senders.push_back(cricket::VideoSenderInfo()); + video_media_info.senders[0].local_stats.push_back(cricket::SsrcSenderInfo()); + video_media_info.senders[0].local_stats[0].ssrc = kSsrc; + video_media_info.senders[0].framerate_input = 29; + auto* video_media_channel = pc_->AddVideoChannel("VideoMid", "TransportName"); + video_media_channel->SetStats(video_media_info); + + auto video_track = FakeVideoTrackForStats::Create( + "LocalVideoTrackID", MediaStreamTrackInterface::kLive, + /*source=*/nullptr); + rtc::scoped_refptr sender = CreateMockSender( + cricket::MEDIA_TYPE_VIDEO, video_track, kSsrc, kAttachmentId, {}); + pc_->AddSender(sender); + + rtc::scoped_refptr report = stats_->GetStatsReport(); + ASSERT_TRUE(report->Get("RTCVideoSource_42")); + auto video_stats = + report->Get("RTCVideoSource_42")->cast_to(); + EXPECT_FALSE(video_stats.width.is_defined()); + EXPECT_FALSE(video_stats.height.is_defined()); +} + +TEST_F(RTCStatsCollectorTest, + RTCAudioSourceStatsNotCollectedForSenderWithoutTrack) { + const uint32_t kSsrc = 4; + const int kAttachmentId = 42; + + 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 = kSsrc; + auto* voice_media_channel = pc_->AddVoiceChannel("AudioMid", "TransportName"); + voice_media_channel->SetStats(voice_media_info); + rtc::scoped_refptr sender = CreateMockSender( + cricket::MEDIA_TYPE_AUDIO, /*track=*/nullptr, kSsrc, kAttachmentId, {}); + pc_->AddSender(sender); + + rtc::scoped_refptr report = stats_->GetStatsReport(); + EXPECT_FALSE(report->Get("RTCAudioSource_42")); +} + +TEST_F(RTCStatsCollectorTest, + RTCVideoSourceStatsNotCollectedForSenderWithoutTrack) { + const uint32_t kSsrc = 4; + const int kAttachmentId = 42; + + cricket::VideoMediaInfo video_media_info; + video_media_info.senders.push_back(cricket::VideoSenderInfo()); + video_media_info.senders[0].local_stats.push_back(cricket::SsrcSenderInfo()); + video_media_info.senders[0].local_stats[0].ssrc = kSsrc; + video_media_info.senders[0].framerate_input = 29; + auto* video_media_channel = pc_->AddVideoChannel("VideoMid", "TransportName"); + video_media_channel->SetStats(video_media_info); + + rtc::scoped_refptr sender = CreateMockSender( + cricket::MEDIA_TYPE_VIDEO, /*track=*/nullptr, kSsrc, kAttachmentId, {}); + pc_->AddSender(sender); + + rtc::scoped_refptr report = stats_->GetStatsReport(); + EXPECT_FALSE(report->Get("RTCVideoSource_42")); +} + TEST_F(RTCStatsCollectorTest, GetStatsWithSenderSelector) { ExampleStatsGraph graph = SetupExampleStatsGraphForSelectorTests(); // Expected stats graph when filtered by sender: // - // track (sender) - // ^ - // | - // outbound-rtp - // | | - // v v - // codec (send) transport + // +--- track (sender) + // | ^ + // | | + // | +--------- outbound-rtp + // | | | | + // | | v v + // | | codec (send) transport + // v v + // media-source rtc::scoped_refptr sender_report = stats_->GetStatsReportWithSenderSelector(graph.sender); EXPECT_TRUE(sender_report); EXPECT_EQ(sender_report->timestamp_us(), graph.full_report->timestamp_us()); - EXPECT_EQ(sender_report->size(), 4u); + EXPECT_EQ(sender_report->size(), 5u); EXPECT_TRUE(sender_report->Get(graph.send_codec_id)); EXPECT_FALSE(sender_report->Get(graph.recv_codec_id)); EXPECT_TRUE(sender_report->Get(graph.outbound_rtp_id)); @@ -2133,19 +2390,20 @@ TEST_F(RTCStatsCollectorTest, GetStatsWithSenderSelector) { EXPECT_FALSE(sender_report->Get(graph.receiver_track_id)); EXPECT_FALSE(sender_report->Get(graph.remote_stream_id)); EXPECT_FALSE(sender_report->Get(graph.peer_connection_id)); + EXPECT_TRUE(sender_report->Get(graph.media_source_id)); } TEST_F(RTCStatsCollectorTest, GetStatsWithReceiverSelector) { ExampleStatsGraph graph = SetupExampleStatsGraphForSelectorTests(); // Expected stats graph when filtered by receiver: // - // track (receiver) - // ^ - // | - // inbound-rtp ---------------+ - // | | - // v v - // transport codec (recv) + // track (receiver) + // ^ + // | + // inbound-rtp ---------------+ + // | | + // v v + // transport codec (recv) rtc::scoped_refptr receiver_report = stats_->GetStatsReportWithReceiverSelector(graph.receiver); EXPECT_TRUE(receiver_report); @@ -2160,6 +2418,7 @@ TEST_F(RTCStatsCollectorTest, GetStatsWithReceiverSelector) { EXPECT_TRUE(receiver_report->Get(graph.receiver_track_id)); EXPECT_FALSE(receiver_report->Get(graph.remote_stream_id)); EXPECT_FALSE(receiver_report->Get(graph.peer_connection_id)); + EXPECT_FALSE(receiver_report->Get(graph.media_source_id)); } TEST_F(RTCStatsCollectorTest, GetStatsWithNullSenderSelector) { @@ -2188,7 +2447,7 @@ TEST_F(RTCStatsCollectorTest, StatsReportedOnZeroSsrc) { CreateFakeTrack(cricket::MEDIA_TYPE_AUDIO, "audioTrack", MediaStreamTrackInterface::kLive); rtc::scoped_refptr sender = - CreateMockSender(track, 0, 49, {}); + CreateMockSender(cricket::MEDIA_TYPE_AUDIO, track, 0, 49, {}); pc_->AddSender(sender); rtc::scoped_refptr report = stats_->GetStatsReport(); @@ -2207,7 +2466,7 @@ TEST_F(RTCStatsCollectorTest, DoNotCrashOnSsrcChange) { CreateFakeTrack(cricket::MEDIA_TYPE_AUDIO, "audioTrack", MediaStreamTrackInterface::kLive); rtc::scoped_refptr sender = - CreateMockSender(track, 4711, 49, {}); + CreateMockSender(cricket::MEDIA_TYPE_AUDIO, track, 4711, 49, {}); pc_->AddSender(sender); // We do not generate any matching voice_sender_info stats. diff --git a/pc/rtc_stats_integrationtest.cc b/pc/rtc_stats_integrationtest.cc index 438b47b160..10b17e5986 100644 --- a/pc/rtc_stats_integrationtest.cc +++ b/pc/rtc_stats_integrationtest.cc @@ -317,8 +317,8 @@ class RTCStatsVerifier { EXPECT_TRUE(valid_reference) << stats_->type() << "." << member.name() << " is not a reference to an " - << "existing dictionary of type " << expected_type << " (" - << member.ValueToString() << ")."; + << "existing dictionary of type " << expected_type << " (value: " + << (member.is_defined() ? member.ValueToString() : "null") << ")."; MarkMemberTested(member, valid_reference); } @@ -394,6 +394,18 @@ class RTCStatsReportVerifier { } else if (stats.type() == RTCOutboundRTPStreamStats::kType) { verify_successful &= VerifyRTCOutboundRTPStreamStats( stats.cast_to()); + } else if (stats.type() == RTCAudioSourceStats::kType) { + // RTCAudioSourceStats::kType and RTCVideoSourceStats::kType both have + // the value "media-source", but they are distinguishable with pointer + // equality (==). In JavaScript they would be distinguished with |kind|. + verify_successful &= + VerifyRTCAudioSourceStats(stats.cast_to()); + } else if (stats.type() == RTCVideoSourceStats::kType) { + // RTCAudioSourceStats::kType and RTCVideoSourceStats::kType both have + // the value "media-source", but they are distinguishable with pointer + // equality (==). In JavaScript they would be distinguished with |kind|. + verify_successful &= + VerifyRTCVideoSourceStats(stats.cast_to()); } else if (stats.type() == RTCTransportStats::kType) { verify_successful &= VerifyRTCTransportStats(stats.cast_to()); @@ -542,6 +554,15 @@ class RTCStatsReportVerifier { verifier.TestMemberIsDefined(media_stream_track.kind); // Video or audio media stream track? if (*media_stream_track.kind == RTCMediaStreamTrackKind::kVideo) { + // The type of the referenced media source depends on kind. + if (media_stream_track.remote_source.is_defined() && + !*media_stream_track.remote_source) { + verifier.TestMemberIsIDReference(media_stream_track.media_source_id, + RTCVideoSourceStats::kType); + } else { + // Remote tracks don't have media source stats. + verifier.TestMemberIsUndefined(media_stream_track.media_source_id); + } // Video-only members verifier.TestMemberIsNonNegative( media_stream_track.frame_width); @@ -600,6 +621,15 @@ class RTCStatsReportVerifier { verifier.TestMemberIsUndefined(media_stream_track.total_samples_duration); } else { RTC_DCHECK_EQ(*media_stream_track.kind, RTCMediaStreamTrackKind::kAudio); + // The type of the referenced media source depends on kind. + if (media_stream_track.remote_source.is_defined() && + !*media_stream_track.remote_source) { + verifier.TestMemberIsIDReference(media_stream_track.media_source_id, + RTCAudioSourceStats::kType); + } else { + // Remote tracks don't have media source stats. + verifier.TestMemberIsUndefined(media_stream_track.media_source_id); + } // Video-only members should be undefined verifier.TestMemberIsUndefined(media_stream_track.frame_width); verifier.TestMemberIsUndefined(media_stream_track.frame_height); @@ -777,8 +807,12 @@ class RTCStatsReportVerifier { VerifyRTCRTPStreamStats(outbound_stream, &verifier); if (outbound_stream.media_type.is_defined() && *outbound_stream.media_type == "video") { + verifier.TestMemberIsIDReference(outbound_stream.media_source_id, + RTCVideoSourceStats::kType); verifier.TestMemberIsNonNegative(outbound_stream.qp_sum); } else { + verifier.TestMemberIsIDReference(outbound_stream.media_source_id, + RTCAudioSourceStats::kType); verifier.TestMemberIsUndefined(outbound_stream.qp_sum); } verifier.TestMemberIsNonNegative(outbound_stream.packets_sent); @@ -812,6 +846,40 @@ class RTCStatsReportVerifier { return verifier.ExpectAllMembersSuccessfullyTested(); } + void VerifyRTCMediaSourceStats(const RTCMediaSourceStats& media_source, + RTCStatsVerifier* verifier) { + verifier->TestMemberIsDefined(media_source.track_identifier); + verifier->TestMemberIsDefined(media_source.kind); + if (media_source.kind.is_defined()) { + EXPECT_TRUE((*media_source.kind == "audio" && + media_source.type() == RTCAudioSourceStats::kType) || + (*media_source.kind == "video" && + media_source.type() == RTCVideoSourceStats::kType)); + } + } + + bool VerifyRTCAudioSourceStats(const RTCAudioSourceStats& audio_source) { + RTCStatsVerifier verifier(report_, &audio_source); + VerifyRTCMediaSourceStats(audio_source, &verifier); + return verifier.ExpectAllMembersSuccessfullyTested(); + } + + bool VerifyRTCVideoSourceStats(const RTCVideoSourceStats& video_source) { + RTCStatsVerifier verifier(report_, &video_source); + VerifyRTCMediaSourceStats(video_source, &verifier); + // TODO(hbos): This integration test uses fakes that doesn't support + // VideoTrackSourceInterface::Stats. When this is fixed we should + // TestMemberIsNonNegative() for |width| and |height| instead to + // reflect real code. + verifier.TestMemberIsUndefined(video_source.width); + verifier.TestMemberIsUndefined(video_source.height); + // TODO(hbos): When |frames| is implemented test that this member should be + // expected to be non-negative. + verifier.TestMemberIsUndefined(video_source.frames); + verifier.TestMemberIsNonNegative(video_source.frames_per_second); + return verifier.ExpectAllMembersSuccessfullyTested(); + } + bool VerifyRTCTransportStats(const RTCTransportStats& transport) { RTCStatsVerifier verifier(report_, &transport); verifier.TestMemberIsNonNegative(transport.bytes_sent); diff --git a/pc/rtc_stats_traversal.cc b/pc/rtc_stats_traversal.cc index e7af0e10e9..16a6c9d668 100644 --- a/pc/rtc_stats_traversal.cc +++ b/pc/rtc_stats_traversal.cc @@ -94,7 +94,8 @@ std::vector GetStatsReferencedIds(const RTCStats& stats) { const auto& stream = static_cast(stats); AddIdsIfDefined(stream.track_ids, &neighbor_ids); } else if (type == RTCMediaStreamTrackStats::kType) { - // RTCMediaStreamTrackStats does not have any neighbor references. + const auto& track = static_cast(stats); + AddIdIfDefined(track.media_source_id, &neighbor_ids); } else if (type == RTCPeerConnectionStats::kType) { // RTCPeerConnectionStats does not have any neighbor references. } else if (type == RTCInboundRTPStreamStats::kType || @@ -104,6 +105,14 @@ std::vector GetStatsReferencedIds(const RTCStats& stats) { AddIdIfDefined(rtp.track_id, &neighbor_ids); AddIdIfDefined(rtp.transport_id, &neighbor_ids); AddIdIfDefined(rtp.codec_id, &neighbor_ids); + if (type == RTCOutboundRTPStreamStats::kType) { + const auto& outbound_rtp = + static_cast(stats); + AddIdIfDefined(outbound_rtp.media_source_id, &neighbor_ids); + } + } else if (type == RTCAudioSourceStats::kType || + type == RTCVideoSourceStats::kType) { + // RTC[Audio/Video]SourceStats does not have any neighbor references. } else if (type == RTCTransportStats::kType) { // RTCTransportStats does not have any neighbor references. const auto& transport = static_cast(stats); diff --git a/stats/rtcstats_objects.cc b/stats/rtcstats_objects.cc index e6c96e0092..f97e23fb04 100644 --- a/stats/rtcstats_objects.cc +++ b/stats/rtcstats_objects.cc @@ -361,6 +361,7 @@ RTCMediaStreamStats::~RTCMediaStreamStats() {} // clang-format off WEBRTC_RTCSTATS_IMPL(RTCMediaStreamTrackStats, RTCStats, "track", &track_identifier, + &media_source_id, &remote_source, &ended, &detached, @@ -409,6 +410,7 @@ RTCMediaStreamTrackStats::RTCMediaStreamTrackStats(std::string&& id, const char* kind) : RTCStats(std::move(id), timestamp_us), track_identifier("trackIdentifier"), + media_source_id("mediaSourceId"), remote_source("remoteSource"), ended("ended"), detached("detached"), @@ -463,6 +465,7 @@ RTCMediaStreamTrackStats::RTCMediaStreamTrackStats( const RTCMediaStreamTrackStats& other) : RTCStats(other.id(), other.timestamp_us()), track_identifier(other.track_identifier), + media_source_id(other.media_source_id), remote_source(other.remote_source), ended(other.ended), detached(other.detached), @@ -668,6 +671,7 @@ RTCInboundRTPStreamStats::~RTCInboundRTPStreamStats() {} // clang-format off WEBRTC_RTCSTATS_IMPL( RTCOutboundRTPStreamStats, RTCRTPStreamStats, "outbound-rtp", + &media_source_id, &packets_sent, &retransmitted_packets_sent, &bytes_sent, @@ -687,6 +691,7 @@ RTCOutboundRTPStreamStats::RTCOutboundRTPStreamStats(const std::string& id, RTCOutboundRTPStreamStats::RTCOutboundRTPStreamStats(std::string&& id, int64_t timestamp_us) : RTCRTPStreamStats(std::move(id), timestamp_us), + media_source_id("mediaSourceId"), packets_sent("packetsSent"), retransmitted_packets_sent("retransmittedPacketsSent"), bytes_sent("bytesSent"), @@ -701,6 +706,7 @@ RTCOutboundRTPStreamStats::RTCOutboundRTPStreamStats(std::string&& id, RTCOutboundRTPStreamStats::RTCOutboundRTPStreamStats( const RTCOutboundRTPStreamStats& other) : RTCRTPStreamStats(other), + media_source_id(other.media_source_id), packets_sent(other.packets_sent), retransmitted_packets_sent(other.retransmitted_packets_sent), bytes_sent(other.bytes_sent), @@ -714,6 +720,73 @@ RTCOutboundRTPStreamStats::RTCOutboundRTPStreamStats( RTCOutboundRTPStreamStats::~RTCOutboundRTPStreamStats() {} +// clang-format off +WEBRTC_RTCSTATS_IMPL(RTCMediaSourceStats, RTCStats, "parent-media-source", + &track_identifier, + &kind) +// clang-format on + +RTCMediaSourceStats::RTCMediaSourceStats(const std::string& id, + int64_t timestamp_us) + : RTCMediaSourceStats(std::string(id), timestamp_us) {} + +RTCMediaSourceStats::RTCMediaSourceStats(std::string&& id, int64_t timestamp_us) + : RTCStats(std::move(id), timestamp_us), + track_identifier("trackIdentifier"), + kind("kind") {} + +RTCMediaSourceStats::RTCMediaSourceStats(const RTCMediaSourceStats& other) + : RTCStats(other.id(), other.timestamp_us()), + track_identifier(other.track_identifier), + kind(other.kind) {} + +RTCMediaSourceStats::~RTCMediaSourceStats() {} + +// clang-format off +WEBRTC_RTCSTATS_IMPL_NO_MEMBERS( + RTCAudioSourceStats, RTCMediaSourceStats, "media-source") +// clang-format on + +RTCAudioSourceStats::RTCAudioSourceStats(const std::string& id, + int64_t timestamp_us) + : RTCAudioSourceStats(std::string(id), timestamp_us) {} + +RTCAudioSourceStats::RTCAudioSourceStats(std::string&& id, int64_t timestamp_us) + : RTCMediaSourceStats(std::move(id), timestamp_us) {} + +RTCAudioSourceStats::RTCAudioSourceStats(const RTCAudioSourceStats& other) + : RTCMediaSourceStats(other) {} + +RTCAudioSourceStats::~RTCAudioSourceStats() {} + +// clang-format off +WEBRTC_RTCSTATS_IMPL(RTCVideoSourceStats, RTCMediaSourceStats, "media-source", + &width, + &height, + &frames, + &frames_per_second) +// clang-format on + +RTCVideoSourceStats::RTCVideoSourceStats(const std::string& id, + int64_t timestamp_us) + : RTCVideoSourceStats(std::string(id), timestamp_us) {} + +RTCVideoSourceStats::RTCVideoSourceStats(std::string&& id, int64_t timestamp_us) + : RTCMediaSourceStats(std::move(id), timestamp_us), + width("width"), + height("height"), + frames("frames"), + frames_per_second("framesPerSecond") {} + +RTCVideoSourceStats::RTCVideoSourceStats(const RTCVideoSourceStats& other) + : RTCMediaSourceStats(other), + width(other.width), + height(other.height), + frames(other.frames), + frames_per_second(other.frames_per_second) {} + +RTCVideoSourceStats::~RTCVideoSourceStats() {} + // clang-format off WEBRTC_RTCSTATS_IMPL(RTCTransportStats, RTCStats, "transport", &bytes_sent,