diff --git a/media/engine/webrtc_video_engine.cc b/media/engine/webrtc_video_engine.cc index 5ab7a015d0..cfb15b0576 100644 --- a/media/engine/webrtc_video_engine.cc +++ b/media/engine/webrtc_video_engine.cc @@ -416,6 +416,28 @@ MergeInfoAboutOutboundRtpSubstreams( return rtp_substreams; } +bool IsActiveFromEncodings( + absl::optional ssrc, + const std::vector& encodings) { + if (ssrc.has_value()) { + // Report the `active` value of a specific ssrc, or false if an encoding + // with this ssrc does not exist. + auto encoding_it = std::find_if( + encodings.begin(), encodings.end(), + [ssrc = ssrc.value()](const webrtc::RtpEncodingParameters& encoding) { + return encoding.ssrc.has_value() && encoding.ssrc.value() == ssrc; + }); + return encoding_it != encodings.end() ? encoding_it->active : false; + } + // If `ssrc` is not specified then any encoding being active counts as active. + for (const auto& encoding : encodings) { + if (encoding.active) { + return true; + } + } + return false; +} + } // namespace // This constant is really an on/off, lower-level configurable NACK history @@ -2627,20 +2649,16 @@ WebRtcVideoChannel::WebRtcVideoSendStream::GetPerLayerVideoSenderInfos( common_info.aggregated_huge_frames_sent = stats.huge_frames_sent; common_info.power_efficient_encoder = stats.power_efficient_encoder; - // If we don't have any substreams, get the remaining metrics from `stats`. - // Otherwise, these values are obtained from `sub_stream` below. + // The normal case is that substreams are present, handled below. But if + // substreams are missing (can happen before negotiated/connected where we + // have no stats yet) a single outbound-rtp is created representing any and + // all layers. if (stats.substreams.empty()) { for (uint32_t ssrc : parameters_.config.rtp.ssrcs) { common_info.add_ssrc(ssrc); - auto encoding_it = std::find_if( - rtp_parameters_.encodings.begin(), rtp_parameters_.encodings.end(), - [&ssrc](const webrtc::RtpEncodingParameters& parameters) { - return parameters.ssrc && parameters.ssrc == ssrc; - }); - if (encoding_it != rtp_parameters_.encodings.end()) { - common_info.active = encoding_it->active; - } } + common_info.active = + IsActiveFromEncodings(absl::nullopt, rtp_parameters_.encodings); common_info.framerate_sent = stats.encode_frame_rate; common_info.frames_encoded = stats.frames_encoded; common_info.total_encode_time_ms = stats.total_encode_time_ms; @@ -2651,21 +2669,24 @@ WebRtcVideoChannel::WebRtcVideoSendStream::GetPerLayerVideoSenderInfos( return infos; } } + // Merge `stats.substreams`, which may contain additional SSRCs for RTX or + // Flexfec, with media SSRCs. This results in a set of substreams that match + // with the outbound-rtp stats objects. auto outbound_rtp_substreams = MergeInfoAboutOutboundRtpSubstreams(stats.substreams); + // If SVC is used, one stream is configured but multiple encodings exist. This + // is not spec-compliant, but it is how we've implemented SVC so this affects + // how the RTP stream's "active" value is determined. + bool is_svc = (parameters_.encoder_config.number_of_streams == 1 && + rtp_parameters_.encodings.size() > 1); for (const auto& pair : outbound_rtp_substreams) { auto info = common_info; - info.add_ssrc(pair.first); - info.rid = parameters_.config.rtp.GetRidForSsrc(pair.first); - // Search the associated encoding by SSRC. - auto encoding_it = std::find_if( - rtp_parameters_.encodings.begin(), rtp_parameters_.encodings.end(), - [&pair](const webrtc::RtpEncodingParameters& parameters) { - return parameters.ssrc && pair.first == *parameters.ssrc; - }); - if (encoding_it != rtp_parameters_.encodings.end()) { - info.active = encoding_it->active; - } + uint32_t ssrc = pair.first; + info.add_ssrc(ssrc); + info.rid = parameters_.config.rtp.GetRidForSsrc(ssrc); + info.active = IsActiveFromEncodings( + !is_svc ? absl::optional(ssrc) : absl::nullopt, + rtp_parameters_.encodings); auto stream_stats = pair.second; RTC_DCHECK_EQ(stream_stats.type, webrtc::VideoSendStream::StreamStats::StreamType::kMedia); diff --git a/media/engine/webrtc_video_engine_unittest.cc b/media/engine/webrtc_video_engine_unittest.cc index 87d0f723bf..053fd173be 100644 --- a/media/engine/webrtc_video_engine_unittest.cc +++ b/media/engine/webrtc_video_engine_unittest.cc @@ -5824,6 +5824,91 @@ TEST_F(WebRtcVideoChannelTest, GetPerLayerStatsReportForSubStreams) { EXPECT_EQ(sender.rid, absl::nullopt); } +TEST_F(WebRtcVideoChannelTest, + OutboundRtpIsActiveComesFromMatchingEncodingInSimulcast) { + constexpr uint32_t kSsrc1 = 123u; + constexpr uint32_t kSsrc2 = 456u; + + // Create simulcast stream from both SSRCs. + // `kSsrc1` is the "main" ssrc used for getting parameters. + FakeVideoSendStream* stream = + AddSendStream(cricket::CreateSimStreamParams("cname", {kSsrc1, kSsrc2})); + + webrtc::RtpParameters parameters = channel_->GetRtpSendParameters(kSsrc1); + ASSERT_EQ(2u, parameters.encodings.size()); + parameters.encodings[0].active = false; + parameters.encodings[1].active = true; + channel_->SetRtpSendParameters(kSsrc1, parameters); + + // Fill in dummy stats. + auto stats = GetInitialisedStats(); + stats.substreams[kSsrc1]; + stats.substreams[kSsrc2]; + stream->SetStats(stats); + + // GetStats() and ensure `active` matches `encodings` for each SSRC. + cricket::VideoMediaInfo video_media_info; + ASSERT_TRUE(channel_->GetStats(&video_media_info)); + ASSERT_EQ(video_media_info.senders.size(), 2u); + ASSERT_TRUE(video_media_info.senders[0].active.has_value()); + EXPECT_FALSE(video_media_info.senders[0].active.value()); + ASSERT_TRUE(video_media_info.senders[1].active.has_value()); + EXPECT_TRUE(video_media_info.senders[1].active.value()); +} + +TEST_F(WebRtcVideoChannelTest, OutboundRtpIsActiveComesFromAnyEncodingInSvc) { + cricket::VideoSendParameters send_parameters; + send_parameters.codecs.push_back(GetEngineCodec("VP9")); + ASSERT_TRUE(channel_->SetSendParameters(send_parameters)); + + constexpr uint32_t kSsrc1 = 123u; + constexpr uint32_t kSsrc2 = 456u; + constexpr uint32_t kSsrc3 = 789u; + + // Configuring SVC is done the same way that simulcast is configured, the only + // difference is that the VP9 codec is used. This triggers special hacks that + // we depend on because we don't have a proper SVC API yet. + FakeVideoSendStream* stream = AddSendStream( + cricket::CreateSimStreamParams("cname", {kSsrc1, kSsrc2, kSsrc3})); + // Expect that we got SVC. + EXPECT_EQ(stream->GetEncoderConfig().number_of_streams, 1u); + webrtc::VideoCodecVP9 vp9_settings; + ASSERT_TRUE(stream->GetVp9Settings(&vp9_settings)); + EXPECT_EQ(vp9_settings.numberOfSpatialLayers, 3u); + + webrtc::RtpParameters parameters = channel_->GetRtpSendParameters(kSsrc1); + ASSERT_EQ(3u, parameters.encodings.size()); + parameters.encodings[0].active = false; + parameters.encodings[1].active = true; + parameters.encodings[2].active = false; + channel_->SetRtpSendParameters(kSsrc1, parameters); + + // Fill in dummy stats. + auto stats = GetInitialisedStats(); + stats.substreams[kSsrc1]; + stream->SetStats(stats); + + // GetStats() and ensure `active` is true if ANY encoding is active. + cricket::VideoMediaInfo video_media_info; + ASSERT_TRUE(channel_->GetStats(&video_media_info)); + ASSERT_EQ(video_media_info.senders.size(), 1u); + // Middle layer is active. + ASSERT_TRUE(video_media_info.senders[0].active.has_value()); + EXPECT_TRUE(video_media_info.senders[0].active.value()); + + parameters = channel_->GetRtpSendParameters(kSsrc1); + ASSERT_EQ(3u, parameters.encodings.size()); + parameters.encodings[0].active = false; + parameters.encodings[1].active = false; + parameters.encodings[2].active = false; + channel_->SetRtpSendParameters(kSsrc1, parameters); + ASSERT_TRUE(channel_->GetStats(&video_media_info)); + ASSERT_EQ(video_media_info.senders.size(), 1u); + // No layer is active. + ASSERT_TRUE(video_media_info.senders[0].active.has_value()); + EXPECT_FALSE(video_media_info.senders[0].active.value()); +} + TEST_F(WebRtcVideoChannelTest, MediaSubstreamMissingProducesEmpyStats) { FakeVideoSendStream* stream = AddSendStream();