| /* |
| * Copyright (c) 2014 The WebRTC project authors. All Rights Reserved. |
| * |
| * Use of this source code is governed by a BSD-style license |
| * that can be found in the LICENSE file in the root of the source |
| * tree. An additional intellectual property rights grant can be found |
| * in the file PATENTS. All contributing project authors may |
| * be found in the AUTHORS file in the root of the source tree. |
| */ |
| |
| #include "media/engine/webrtc_video_engine.h" |
| |
| #include <stdio.h> |
| |
| #include <algorithm> |
| #include <cstdint> |
| #include <initializer_list> |
| #include <optional> |
| #include <set> |
| #include <string> |
| #include <type_traits> |
| #include <utility> |
| |
| #include "absl/algorithm/container.h" |
| #include "absl/container/inlined_vector.h" |
| #include "absl/functional/bind_front.h" |
| #include "absl/strings/match.h" |
| #include "api/make_ref_counted.h" |
| #include "api/media_stream_interface.h" |
| #include "api/media_types.h" |
| #include "api/priority.h" |
| #include "api/rtc_error.h" |
| #include "api/rtp_parameters.h" |
| #include "api/rtp_transceiver_direction.h" |
| #include "api/units/time_delta.h" |
| #include "api/units/timestamp.h" |
| #include "api/video/resolution.h" |
| #include "api/video/video_codec_type.h" |
| #include "api/video_codecs/scalability_mode.h" |
| #include "api/video_codecs/sdp_video_format.h" |
| #include "api/video_codecs/video_codec.h" |
| #include "api/video_codecs/video_decoder_factory.h" |
| #include "api/video_codecs/video_encoder.h" |
| #include "api/video_codecs/video_encoder_factory.h" |
| #include "call/call.h" |
| #include "call/packet_receiver.h" |
| #include "call/receive_stream.h" |
| #include "call/rtp_config.h" |
| #include "call/rtp_transport_controller_send_interface.h" |
| #include "call/video_send_stream.h" |
| #include "common_video/frame_counts.h" |
| #include "common_video/include/quality_limitation_reason.h" |
| #include "media/base/codec.h" |
| #include "media/base/codec_comparators.h" |
| #include "media/base/media_channel.h" |
| #include "media/base/media_constants.h" |
| #include "media/base/rid_description.h" |
| #include "media/base/rtp_utils.h" |
| #include "media/engine/webrtc_media_engine.h" |
| #include "modules/rtp_rtcp/include/receive_statistics.h" |
| #include "modules/rtp_rtcp/include/report_block_data.h" |
| #include "modules/rtp_rtcp/include/rtcp_statistics.h" |
| #include "modules/rtp_rtcp/include/rtp_rtcp_defines.h" |
| #include "modules/rtp_rtcp/source/rtp_util.h" |
| #include "modules/video_coding/svc/scalability_mode_util.h" |
| #include "rtc_base/checks.h" |
| #include "rtc_base/dscp.h" |
| #include "rtc_base/experiments/field_trial_parser.h" |
| #include "rtc_base/logging.h" |
| #include "rtc_base/socket.h" |
| #include "rtc_base/strings/string_builder.h" |
| #include "rtc_base/time_utils.h" |
| #include "rtc_base/trace_event.h" |
| |
| namespace cricket { |
| |
| namespace { |
| |
| using ::webrtc::ParseRtpPayloadType; |
| using ::webrtc::ParseRtpSsrc; |
| |
| constexpr int64_t kUnsignaledSsrcCooldownMs = rtc::kNumMillisecsPerSec / 2; |
| |
| // TODO(bugs.webrtc.org/13166): Remove AV1X when backwards compatibility is not |
| // needed. |
| constexpr char kAv1xCodecName[] = "AV1X"; |
| |
| // This constant is really an on/off, lower-level configurable NACK history |
| // duration hasn't been implemented. |
| const int kNackHistoryMs = 1000; |
| |
| const int kDefaultRtcpReceiverReportSsrc = 1; |
| |
| // Minimum time interval for logging stats. |
| const int64_t kStatsLogIntervalMs = 10000; |
| |
| const char* StreamTypeToString( |
| webrtc::VideoSendStream::StreamStats::StreamType type) { |
| switch (type) { |
| case webrtc::VideoSendStream::StreamStats::StreamType::kMedia: |
| return "kMedia"; |
| case webrtc::VideoSendStream::StreamStats::StreamType::kRtx: |
| return "kRtx"; |
| case webrtc::VideoSendStream::StreamStats::StreamType::kFlexfec: |
| return "kFlexfec"; |
| } |
| return nullptr; |
| } |
| |
| bool IsEnabled(const webrtc::FieldTrialsView& trials, absl::string_view name) { |
| return absl::StartsWith(trials.Lookup(name), "Enabled"); |
| } |
| |
| bool IsDisabled(const webrtc::FieldTrialsView& trials, absl::string_view name) { |
| return absl::StartsWith(trials.Lookup(name), "Disabled"); |
| } |
| |
| void AddDefaultFeedbackParams(Codec* codec, |
| const webrtc::FieldTrialsView& trials) { |
| // Don't add any feedback params for RED and ULPFEC. |
| if (codec->name == kRedCodecName || codec->name == kUlpfecCodecName) |
| return; |
| codec->AddFeedbackParam(FeedbackParam(kRtcpFbParamRemb, kParamValueEmpty)); |
| codec->AddFeedbackParam( |
| FeedbackParam(kRtcpFbParamTransportCc, kParamValueEmpty)); |
| // Don't add any more feedback params for FLEXFEC. |
| if (codec->name == kFlexfecCodecName) |
| return; |
| codec->AddFeedbackParam(FeedbackParam(kRtcpFbParamCcm, kRtcpFbCcmParamFir)); |
| codec->AddFeedbackParam(FeedbackParam(kRtcpFbParamNack, kParamValueEmpty)); |
| codec->AddFeedbackParam(FeedbackParam(kRtcpFbParamNack, kRtcpFbNackParamPli)); |
| if (codec->name == kVp8CodecName && |
| IsEnabled(trials, "WebRTC-RtcpLossNotification")) { |
| codec->AddFeedbackParam(FeedbackParam(kRtcpFbParamLntf, kParamValueEmpty)); |
| } |
| } |
| |
| // Helper function to determine whether a codec should use the [35, 63] range. |
| // Should be used when adding new codecs (or variants). |
| bool IsCodecValidForLowerRange(const Codec& codec) { |
| if (absl::EqualsIgnoreCase(codec.name, kFlexfecCodecName) || |
| absl::EqualsIgnoreCase(codec.name, kAv1CodecName) || |
| absl::EqualsIgnoreCase(codec.name, kAv1xCodecName)) { |
| return true; |
| } else if (absl::EqualsIgnoreCase(codec.name, kH264CodecName)) { |
| std::string profile_level_id; |
| std::string packetization_mode; |
| |
| if (codec.GetParam(kH264FmtpProfileLevelId, &profile_level_id)) { |
| if (absl::StartsWithIgnoreCase(profile_level_id, "4d00")) { |
| if (codec.GetParam(kH264FmtpPacketizationMode, &packetization_mode)) { |
| return packetization_mode == "0"; |
| } |
| } |
| // H264 with YUV444. |
| return absl::StartsWithIgnoreCase(profile_level_id, "f400"); |
| } |
| } else if (absl::EqualsIgnoreCase(codec.name, kVp9CodecName)) { |
| std::string profile_id; |
| |
| if (codec.GetParam(kVP9ProfileId, &profile_id)) { |
| if (profile_id.compare("1") == 0 || profile_id.compare("3") == 0) { |
| return true; |
| } |
| } |
| } |
| return false; |
| } |
| |
| // Get the default set of supported codecs. |
| // is_decoder_factory is needed to keep track of the implict assumption that any |
| // H264 decoder also supports constrained base line profile. |
| // Also, is_decoder_factory is used to decide whether FlexFEC video format |
| // should be advertised as supported. |
| template <class T> |
| std::vector<webrtc::SdpVideoFormat> GetDefaultSupportedFormats( |
| const T* factory, |
| bool is_decoder_factory, |
| const webrtc::FieldTrialsView& trials) { |
| if (!factory) { |
| return {}; |
| } |
| |
| std::vector<webrtc::SdpVideoFormat> supported_formats = |
| factory->GetSupportedFormats(); |
| if (is_decoder_factory) { |
| AddH264ConstrainedBaselineProfileToSupportedFormats(&supported_formats); |
| } |
| |
| if (supported_formats.empty()) |
| return supported_formats; |
| |
| supported_formats.push_back(webrtc::SdpVideoFormat(kRedCodecName)); |
| supported_formats.push_back(webrtc::SdpVideoFormat(kUlpfecCodecName)); |
| // flexfec-03 is always supported as receive codec and as send codec |
| // only if WebRTC-FlexFEC-03-Advertised is enabled |
| if (is_decoder_factory || IsEnabled(trials, "WebRTC-FlexFEC-03-Advertised")) { |
| webrtc::SdpVideoFormat flexfec_format(kFlexfecCodecName); |
| // This value is currently arbitrarily set to 10 seconds. (The unit |
| // is microseconds.) This parameter MUST be present in the SDP, but |
| // we never use the actual value anywhere in our code however. |
| // TODO(brandtr): Consider honouring this value in the sender and receiver. |
| flexfec_format.parameters = {{kFlexfecFmtpRepairWindow, "10000000"}}; |
| supported_formats.push_back(flexfec_format); |
| } |
| return supported_formats; |
| } |
| |
| // This function will assign dynamic payload types (in the range [96, 127] |
| // and then [35, 63]) to the input codecs, and also add ULPFEC, RED, FlexFEC, |
| // and associated RTX codecs for recognized codecs (VP8, VP9, H264, and RED). |
| // It will also add default feedback params to the codecs. |
| std::vector<Codec> AssignPayloadTypesAndAddRtx( |
| const std::vector<webrtc::SdpVideoFormat>& supported_formats, |
| bool include_rtx, |
| const webrtc::FieldTrialsView& trials) { |
| // Due to interoperability issues with old Chrome/WebRTC versions that |
| // ignore the [35, 63] range prefer the lower range for new codecs. |
| static const int kFirstDynamicPayloadTypeLowerRange = 35; |
| static const int kLastDynamicPayloadTypeLowerRange = 63; |
| |
| static const int kFirstDynamicPayloadTypeUpperRange = 96; |
| static const int kLastDynamicPayloadTypeUpperRange = 127; |
| int payload_type_upper = kFirstDynamicPayloadTypeUpperRange; |
| int payload_type_lower = kFirstDynamicPayloadTypeLowerRange; |
| |
| std::vector<Codec> output_codecs; |
| for (const webrtc::SdpVideoFormat& format : supported_formats) { |
| Codec codec = cricket::CreateVideoCodec(format); |
| bool isFecCodec = absl::EqualsIgnoreCase(codec.name, kUlpfecCodecName) || |
| absl::EqualsIgnoreCase(codec.name, kFlexfecCodecName); |
| |
| // Check if we ran out of payload types. |
| if (payload_type_lower > kLastDynamicPayloadTypeLowerRange) { |
| // TODO(https://bugs.chromium.org/p/webrtc/issues/detail?id=12248): |
| // return an error. |
| RTC_LOG(LS_ERROR) << "Out of dynamic payload types [35,63] after " |
| "fallback from [96, 127], skipping the rest."; |
| RTC_DCHECK_EQ(payload_type_upper, kLastDynamicPayloadTypeUpperRange); |
| break; |
| } |
| |
| // Lower range gets used for "new" codecs or when running out of payload |
| // types in the upper range. |
| if (IsCodecValidForLowerRange(codec) || |
| payload_type_upper >= kLastDynamicPayloadTypeUpperRange) { |
| codec.id = payload_type_lower++; |
| } else { |
| codec.id = payload_type_upper++; |
| } |
| AddDefaultFeedbackParams(&codec, trials); |
| output_codecs.push_back(codec); |
| |
| // Add associated RTX codec for non-FEC codecs. |
| if (include_rtx) { |
| if (!isFecCodec) { |
| // Check if we ran out of payload types. |
| if (payload_type_lower > kLastDynamicPayloadTypeLowerRange) { |
| // TODO(https://bugs.chromium.org/p/webrtc/issues/detail?id=12248): |
| // return an error. |
| RTC_LOG(LS_ERROR) << "Out of dynamic payload types [35,63] after " |
| "fallback from [96, 127], skipping the rest."; |
| RTC_DCHECK_EQ(payload_type_upper, kLastDynamicPayloadTypeUpperRange); |
| break; |
| } |
| if (IsCodecValidForLowerRange(codec) || |
| payload_type_upper >= kLastDynamicPayloadTypeUpperRange) { |
| output_codecs.push_back( |
| cricket::CreateVideoRtxCodec(payload_type_lower++, codec.id)); |
| } else { |
| output_codecs.push_back( |
| cricket::CreateVideoRtxCodec(payload_type_upper++, codec.id)); |
| } |
| } |
| } |
| } |
| return output_codecs; |
| } |
| |
| // TODO(kron): Perhaps it is better to move the implicit knowledge to the place |
| // where codecs are negotiated. |
| template <class T> |
| std::vector<Codec> GetPayloadTypesAndDefaultCodecs( |
| const T* factory, |
| bool is_decoder_factory, |
| bool include_rtx, |
| const webrtc::FieldTrialsView& trials) { |
| auto supported_formats = |
| GetDefaultSupportedFormats(factory, is_decoder_factory, trials); |
| |
| return AssignPayloadTypesAndAddRtx(supported_formats, include_rtx, trials); |
| } |
| |
| static std::string CodecVectorToString(const std::vector<Codec>& codecs) { |
| rtc::StringBuilder out; |
| out << "{"; |
| for (size_t i = 0; i < codecs.size(); ++i) { |
| out << codecs[i].ToString(); |
| if (i != codecs.size() - 1) { |
| out << ", "; |
| } |
| } |
| out << "}"; |
| return out.Release(); |
| } |
| |
| static bool ValidateCodecFormats(const std::vector<Codec>& codecs) { |
| bool has_video = false; |
| for (size_t i = 0; i < codecs.size(); ++i) { |
| if (!codecs[i].ValidateCodecFormat()) { |
| return false; |
| } |
| if (codecs[i].IsMediaCodec()) { |
| has_video = true; |
| } |
| } |
| if (!has_video) { |
| RTC_LOG(LS_ERROR) << "Setting codecs without a video codec is invalid: " |
| << CodecVectorToString(codecs); |
| return false; |
| } |
| return true; |
| } |
| |
| static bool ValidateStreamParams(const StreamParams& sp) { |
| if (sp.ssrcs.empty()) { |
| RTC_LOG(LS_ERROR) << "No SSRCs in stream parameters: " << sp.ToString(); |
| return false; |
| } |
| |
| // Validate that a primary SSRC can only have one ssrc-group per semantics. |
| std::map<uint32_t, std::set<std::string>> primary_ssrc_to_semantics; |
| for (const auto& group : sp.ssrc_groups) { |
| auto result = primary_ssrc_to_semantics.try_emplace( |
| group.ssrcs[0], std::set<std::string>({group.semantics})); |
| if (!result.second) { |
| // A duplicate SSRC was found, check for duplicate semantics. |
| auto semantics_it = result.first->second.insert(group.semantics); |
| if (!semantics_it.second) { |
| RTC_LOG(LS_ERROR) << "Duplicate ssrc-group '" << group.semantics |
| << " for primary SSRC " << group.ssrcs[0] << " " |
| << sp.ToString(); |
| return false; |
| } |
| } |
| } |
| |
| std::vector<uint32_t> primary_ssrcs; |
| sp.GetPrimarySsrcs(&primary_ssrcs); |
| for (const auto& semantic : |
| {kFidSsrcGroupSemantics, kFecFrSsrcGroupSemantics}) { |
| if (!sp.has_ssrc_group(semantic)) { |
| continue; |
| } |
| std::vector<uint32_t> secondary_ssrcs; |
| sp.GetSecondarySsrcs(semantic, primary_ssrcs, &secondary_ssrcs); |
| for (uint32_t secondary_ssrc : secondary_ssrcs) { |
| bool secondary_ssrc_present = false; |
| for (uint32_t sp_ssrc : sp.ssrcs) { |
| if (sp_ssrc == secondary_ssrc) { |
| secondary_ssrc_present = true; |
| break; |
| } |
| } |
| if (!secondary_ssrc_present) { |
| RTC_LOG(LS_ERROR) << "SSRC '" << secondary_ssrc |
| << "' missing from StreamParams ssrcs with semantics " |
| << semantic << ": " << sp.ToString(); |
| return false; |
| } |
| } |
| if (!secondary_ssrcs.empty() && |
| primary_ssrcs.size() != secondary_ssrcs.size()) { |
| RTC_LOG(LS_ERROR) |
| << semantic |
| << " secondary SSRCs exist, but don't cover all SSRCs (unsupported): " |
| << sp.ToString(); |
| return false; |
| } |
| } |
| for (const auto& group : sp.ssrc_groups) { |
| if (!(group.semantics == kFidSsrcGroupSemantics || |
| group.semantics == kSimSsrcGroupSemantics || |
| group.semantics == kFecFrSsrcGroupSemantics)) { |
| continue; |
| } |
| for (uint32_t group_ssrc : group.ssrcs) { |
| auto it = absl::c_find_if(sp.ssrcs, [&group_ssrc](uint32_t ssrc) { |
| return ssrc == group_ssrc; |
| }); |
| if (it == sp.ssrcs.end()) { |
| RTC_LOG(LS_ERROR) << "SSRC '" << group_ssrc |
| << "' missing from StreamParams ssrcs with semantics " |
| << group.semantics << ": " << sp.ToString(); |
| return false; |
| } |
| } |
| } |
| return true; |
| } |
| |
| // Returns true if the given codec is disallowed from doing simulcast. |
| bool IsCodecDisabledForSimulcast(bool legacy_scalability_mode, |
| webrtc::VideoCodecType codec_type) { |
| if (legacy_scalability_mode && (codec_type == webrtc::kVideoCodecVP9 || |
| codec_type == webrtc::kVideoCodecAV1)) { |
| return true; |
| } |
| |
| return false; |
| } |
| |
| bool IsLayerActive(const webrtc::RtpEncodingParameters& layer) { |
| return layer.active && |
| (!layer.max_bitrate_bps || *layer.max_bitrate_bps > 0) && |
| (!layer.max_framerate || *layer.max_framerate > 0); |
| } |
| |
| int NumActiveStreams(const webrtc::RtpParameters& rtp_parameters) { |
| int res = 0; |
| for (size_t i = 0; i < rtp_parameters.encodings.size(); ++i) { |
| if (rtp_parameters.encodings[i].active) { |
| ++res; |
| } |
| } |
| return res; |
| } |
| |
| std::optional<int> NumSpatialLayersFromEncoding( |
| const webrtc::RtpParameters& rtp_parameters, |
| size_t idx) { |
| if (idx >= rtp_parameters.encodings.size()) |
| return std::nullopt; |
| |
| std::optional<webrtc::ScalabilityMode> scalability_mode = |
| webrtc::ScalabilityModeFromString( |
| rtp_parameters.encodings[idx].scalability_mode.value_or("")); |
| return scalability_mode |
| ? std::optional<int>( |
| ScalabilityModeToNumSpatialLayers(*scalability_mode)) |
| : std::nullopt; |
| } |
| |
| std::map<uint32_t, webrtc::VideoSendStream::StreamStats> |
| MergeInfoAboutOutboundRtpSubstreams( |
| const std::map<uint32_t, webrtc::VideoSendStream::StreamStats>& |
| substreams) { |
| std::map<uint32_t, webrtc::VideoSendStream::StreamStats> rtp_substreams; |
| // Add substreams for all RTP media streams. |
| for (const auto& pair : substreams) { |
| uint32_t ssrc = pair.first; |
| const webrtc::VideoSendStream::StreamStats& substream = pair.second; |
| switch (substream.type) { |
| case webrtc::VideoSendStream::StreamStats::StreamType::kMedia: |
| break; |
| case webrtc::VideoSendStream::StreamStats::StreamType::kRtx: |
| case webrtc::VideoSendStream::StreamStats::StreamType::kFlexfec: |
| continue; |
| } |
| rtp_substreams.insert(std::make_pair(ssrc, substream)); |
| } |
| // Complement the kMedia substream stats with the associated kRtx and kFlexfec |
| // substream stats. |
| for (const auto& pair : substreams) { |
| switch (pair.second.type) { |
| case webrtc::VideoSendStream::StreamStats::StreamType::kMedia: |
| continue; |
| case webrtc::VideoSendStream::StreamStats::StreamType::kRtx: |
| case webrtc::VideoSendStream::StreamStats::StreamType::kFlexfec: |
| break; |
| } |
| // The associated substream is an RTX or FlexFEC substream that is |
| // referencing an RTP media substream. |
| const webrtc::VideoSendStream::StreamStats& associated_substream = |
| pair.second; |
| RTC_DCHECK(associated_substream.referenced_media_ssrc.has_value()); |
| uint32_t media_ssrc = associated_substream.referenced_media_ssrc.value(); |
| if (substreams.find(media_ssrc) == substreams.end()) { |
| RTC_LOG(LS_WARNING) << "Substream [ssrc: " << pair.first << ", type: " |
| << StreamTypeToString(associated_substream.type) |
| << "] is associated with a media ssrc (" << media_ssrc |
| << ") that does not have StreamStats. Ignoring its " |
| << "RTP stats."; |
| continue; |
| } |
| webrtc::VideoSendStream::StreamStats& rtp_substream = |
| rtp_substreams[media_ssrc]; |
| |
| // We only merge `rtp_stats`. All other metrics are not applicable for RTX |
| // and FlexFEC. |
| // TODO(hbos): kRtx and kFlexfec stats should use a separate struct to make |
| // it clear what is or is not applicable. |
| rtp_substream.rtp_stats.Add(associated_substream.rtp_stats); |
| } |
| return rtp_substreams; |
| } |
| |
| bool IsActiveFromEncodings( |
| std::optional<uint32_t> ssrc, |
| const std::vector<webrtc::RtpEncodingParameters>& 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; |
| } |
| |
| bool IsScalabilityModeSupportedByCodec( |
| const Codec& codec, |
| const std::string& scalability_mode, |
| const webrtc::VideoSendStream::Config& config) { |
| return config.encoder_settings.encoder_factory |
| ->QueryCodecSupport(webrtc::SdpVideoFormat(codec.name, codec.params), |
| scalability_mode) |
| .is_supported; |
| } |
| |
| // Fallback to default value if the scalability mode is unset or unsupported by |
| // the codec. |
| void FallbackToDefaultScalabilityModeIfNotSupported( |
| const Codec& codec, |
| const webrtc::VideoSendStream::Config& config, |
| std::vector<webrtc::RtpEncodingParameters>& encodings) { |
| if (!absl::c_any_of(encodings, |
| [](const webrtc::RtpEncodingParameters& encoding) { |
| return encoding.scalability_mode && |
| !encoding.scalability_mode->empty(); |
| })) { |
| // Fallback is only enabled if the scalability mode is configured for any of |
| // the encodings for now. |
| return; |
| } |
| if (config.encoder_settings.encoder_factory == nullptr) { |
| return; |
| } |
| for (auto& encoding : encodings) { |
| RTC_LOG(LS_INFO) << "Encoding scalability_mode: " |
| << encoding.scalability_mode.value_or("-"); |
| if (!encoding.active && !encoding.scalability_mode.has_value()) { |
| // Inactive encodings should not fallback since apps may only specify the |
| // scalability mode of the first encoding when the others are inactive. |
| continue; |
| } |
| |
| if (!encoding.scalability_mode.has_value() || |
| !IsScalabilityModeSupportedByCodec(codec, *encoding.scalability_mode, |
| config)) { |
| encoding.scalability_mode = |
| (encoding.scalability_mode != |
| std::string(webrtc::kDefaultScalabilityModeStr) && |
| IsScalabilityModeSupportedByCodec( |
| codec, webrtc::kDefaultScalabilityModeStr, config)) |
| ? webrtc::kDefaultScalabilityModeStr |
| : webrtc::kNoLayeringScalabilityModeStr; |
| RTC_LOG(LS_INFO) << " -> " << *encoding.scalability_mode; |
| } |
| } |
| } |
| |
| // Generate the list of codec parameters to pass down based on the negotiated |
| // "codecs". Note that VideoCodecSettings correspond to concrete codecs like |
| // VP8, VP9, H264 while VideoCodecs correspond also to "virtual" codecs like |
| // RTX, ULPFEC, FLEXFEC. |
| std::vector<VideoCodecSettings> MapCodecs(const std::vector<Codec>& codecs) { |
| if (codecs.empty()) { |
| return {}; |
| } |
| |
| std::vector<VideoCodecSettings> video_codecs; |
| std::map<int, Codec::ResiliencyType> payload_codec_type; |
| // `rtx_mapping` maps video payload type to rtx payload type. |
| std::map<int, int> rtx_mapping; |
| std::map<int, int> rtx_time_mapping; |
| |
| webrtc::UlpfecConfig ulpfec_config; |
| std::optional<int> flexfec_payload_type; |
| |
| for (const Codec& in_codec : codecs) { |
| const int payload_type = in_codec.id; |
| |
| if (payload_codec_type.find(payload_type) != payload_codec_type.end()) { |
| RTC_LOG(LS_ERROR) << "Payload type already registered: " |
| << in_codec.ToString(); |
| return {}; |
| } |
| payload_codec_type[payload_type] = in_codec.GetResiliencyType(); |
| |
| switch (in_codec.GetResiliencyType()) { |
| case Codec::ResiliencyType::kRed: { |
| if (ulpfec_config.red_payload_type != -1) { |
| RTC_LOG(LS_ERROR) |
| << "Duplicate RED codec: ignoring PT=" << payload_type |
| << " in favor of PT=" << ulpfec_config.red_payload_type |
| << " which was specified first."; |
| break; |
| } |
| ulpfec_config.red_payload_type = payload_type; |
| break; |
| } |
| |
| case Codec::ResiliencyType::kUlpfec: { |
| if (ulpfec_config.ulpfec_payload_type != -1) { |
| RTC_LOG(LS_ERROR) |
| << "Duplicate ULPFEC codec: ignoring PT=" << payload_type |
| << " in favor of PT=" << ulpfec_config.ulpfec_payload_type |
| << " which was specified first."; |
| break; |
| } |
| ulpfec_config.ulpfec_payload_type = payload_type; |
| break; |
| } |
| |
| case Codec::ResiliencyType::kFlexfec: { |
| if (flexfec_payload_type) { |
| RTC_LOG(LS_ERROR) |
| << "Duplicate FLEXFEC codec: ignoring PT=" << payload_type |
| << " in favor of PT=" << *flexfec_payload_type |
| << " which was specified first."; |
| break; |
| } |
| flexfec_payload_type = payload_type; |
| break; |
| } |
| |
| case Codec::ResiliencyType::kRtx: { |
| int associated_payload_type; |
| if (!in_codec.GetParam(kCodecParamAssociatedPayloadType, |
| &associated_payload_type) || |
| !IsValidRtpPayloadType(associated_payload_type)) { |
| RTC_LOG(LS_ERROR) |
| << "RTX codec with invalid or no associated payload type: " |
| << in_codec.ToString(); |
| return {}; |
| } |
| int rtx_time; |
| if (in_codec.GetParam(kCodecParamRtxTime, &rtx_time) && rtx_time > 0) { |
| rtx_time_mapping[associated_payload_type] = rtx_time; |
| } |
| rtx_mapping[associated_payload_type] = payload_type; |
| break; |
| } |
| |
| case Codec::ResiliencyType::kNone: { |
| video_codecs.emplace_back(in_codec); |
| break; |
| } |
| } |
| } |
| |
| // One of these codecs should have been a video codec. Only having FEC |
| // parameters into this code is a logic error. |
| RTC_DCHECK(!video_codecs.empty()); |
| |
| for (const auto& entry : rtx_mapping) { |
| const int associated_payload_type = entry.first; |
| const int rtx_payload_type = entry.second; |
| auto it = payload_codec_type.find(associated_payload_type); |
| if (it == payload_codec_type.end()) { |
| RTC_LOG(LS_ERROR) << "RTX codec (PT=" << rtx_payload_type |
| << ") mapped to PT=" << associated_payload_type |
| << " which is not in the codec list."; |
| return {}; |
| } |
| const Codec::ResiliencyType associated_codec_type = it->second; |
| if (associated_codec_type != Codec::ResiliencyType::kNone && |
| associated_codec_type != Codec::ResiliencyType::kRed) { |
| RTC_LOG(LS_ERROR) |
| << "RTX PT=" << rtx_payload_type |
| << " not mapped to regular video codec or RED codec (PT=" |
| << associated_payload_type << ")."; |
| return {}; |
| } |
| |
| if (associated_payload_type == ulpfec_config.red_payload_type) { |
| ulpfec_config.red_rtx_payload_type = rtx_payload_type; |
| } |
| } |
| |
| for (VideoCodecSettings& codec_settings : video_codecs) { |
| const int payload_type = codec_settings.codec.id; |
| codec_settings.ulpfec = ulpfec_config; |
| codec_settings.flexfec_payload_type = flexfec_payload_type.value_or(-1); |
| auto it = rtx_mapping.find(payload_type); |
| if (it != rtx_mapping.end()) { |
| const int rtx_payload_type = it->second; |
| codec_settings.rtx_payload_type = rtx_payload_type; |
| |
| auto rtx_time_it = rtx_time_mapping.find(payload_type); |
| if (rtx_time_it != rtx_time_mapping.end()) { |
| const int rtx_time = rtx_time_it->second; |
| if (rtx_time < kNackHistoryMs) { |
| codec_settings.rtx_time = rtx_time; |
| } else { |
| codec_settings.rtx_time = kNackHistoryMs; |
| } |
| } |
| } |
| } |
| |
| return video_codecs; |
| } |
| |
| bool NonFlexfecReceiveCodecsHaveChanged(std::vector<VideoCodecSettings> before, |
| std::vector<VideoCodecSettings> after) { |
| // The receive codec order doesn't matter, so we sort the codecs before |
| // comparing. This is necessary because currently the |
| // only way to change the send codec is to munge SDP, which causes |
| // the receive codec list to change order, which causes the streams |
| // to be recreates which causes a "blink" of black video. In order |
| // to support munging the SDP in this way without recreating receive |
| // streams, we ignore the order of the received codecs so that |
| // changing the order doesn't cause this "blink". |
| auto comparison = [](const VideoCodecSettings& codec1, |
| const VideoCodecSettings& codec2) { |
| return codec1.codec.id > codec2.codec.id; |
| }; |
| absl::c_sort(before, comparison); |
| absl::c_sort(after, comparison); |
| |
| // Changes in FlexFEC payload type are handled separately in |
| // WebRtcVideoReceiveChannel::GetChangedReceiverParameters, so disregard |
| // FlexFEC in the comparison here. |
| return !absl::c_equal(before, after, |
| VideoCodecSettings::EqualsDisregardingFlexfec); |
| } |
| |
| std::string CodecSettingsVectorToString( |
| const std::vector<VideoCodecSettings>& codecs) { |
| rtc::StringBuilder out; |
| out << "{"; |
| for (size_t i = 0; i < codecs.size(); ++i) { |
| out << codecs[i].codec.ToString(); |
| if (i != codecs.size() - 1) { |
| out << ", "; |
| } |
| } |
| out << "}"; |
| return out.Release(); |
| } |
| |
| void ExtractCodecInformation( |
| rtc::ArrayView<const VideoCodecSettings> recv_codecs, |
| std::map<int, int>& rtx_associated_payload_types, |
| std::set<int>& raw_payload_types, |
| std::vector<webrtc::VideoReceiveStreamInterface::Decoder>& decoders) { |
| RTC_DCHECK(!recv_codecs.empty()); |
| RTC_DCHECK(rtx_associated_payload_types.empty()); |
| RTC_DCHECK(raw_payload_types.empty()); |
| RTC_DCHECK(decoders.empty()); |
| |
| for (const VideoCodecSettings& recv_codec : recv_codecs) { |
| decoders.emplace_back( |
| webrtc::SdpVideoFormat(recv_codec.codec.name, recv_codec.codec.params), |
| recv_codec.codec.id); |
| rtx_associated_payload_types.emplace(recv_codec.rtx_payload_type, |
| recv_codec.codec.id); |
| if (recv_codec.codec.packetization == kPacketizationParamRaw) { |
| raw_payload_types.insert(recv_codec.codec.id); |
| } |
| } |
| } |
| |
| int ParseReceiveBufferSize(const webrtc::FieldTrialsView& trials) { |
| webrtc::FieldTrialParameter<int> size_bytes("size_bytes", |
| kVideoRtpRecvBufferSize); |
| webrtc::ParseFieldTrial({&size_bytes}, |
| trials.Lookup("WebRTC-ReceiveBufferSize")); |
| if (size_bytes.Get() < 10'000 || size_bytes.Get() > 10'000'000) { |
| RTC_LOG(LS_WARNING) << "WebRTC-ReceiveBufferSize out of bounds: " |
| << size_bytes.Get(); |
| return kVideoRtpRecvBufferSize; |
| } |
| return size_bytes.Get(); |
| } |
| |
| } // namespace |
| // --------------- WebRtcVideoEngine --------------------------- |
| |
| WebRtcVideoEngine::WebRtcVideoEngine( |
| std::unique_ptr<webrtc::VideoEncoderFactory> video_encoder_factory, |
| std::unique_ptr<webrtc::VideoDecoderFactory> video_decoder_factory, |
| const webrtc::FieldTrialsView& trials) |
| : decoder_factory_(std::move(video_decoder_factory)), |
| encoder_factory_(std::move(video_encoder_factory)), |
| trials_(trials) { |
| RTC_DLOG(LS_INFO) << "WebRtcVideoEngine::WebRtcVideoEngine()"; |
| } |
| |
| WebRtcVideoEngine::~WebRtcVideoEngine() { |
| RTC_DLOG(LS_INFO) << "WebRtcVideoEngine::~WebRtcVideoEngine"; |
| } |
| |
| std::unique_ptr<VideoMediaSendChannelInterface> |
| WebRtcVideoEngine::CreateSendChannel( |
| webrtc::Call* call, |
| const MediaConfig& config, |
| const VideoOptions& options, |
| const webrtc::CryptoOptions& crypto_options, |
| webrtc::VideoBitrateAllocatorFactory* video_bitrate_allocator_factory) { |
| return std::make_unique<WebRtcVideoSendChannel>( |
| call, config, options, crypto_options, encoder_factory_.get(), |
| decoder_factory_.get(), video_bitrate_allocator_factory); |
| } |
| std::unique_ptr<VideoMediaReceiveChannelInterface> |
| WebRtcVideoEngine::CreateReceiveChannel( |
| webrtc::Call* call, |
| const MediaConfig& config, |
| const VideoOptions& options, |
| const webrtc::CryptoOptions& crypto_options) { |
| return std::make_unique<WebRtcVideoReceiveChannel>( |
| call, config, options, crypto_options, decoder_factory_.get()); |
| } |
| |
| std::vector<Codec> WebRtcVideoEngine::send_codecs(bool include_rtx) const { |
| return GetPayloadTypesAndDefaultCodecs(encoder_factory_.get(), |
| /*is_decoder_factory=*/false, |
| include_rtx, trials_); |
| } |
| |
| std::vector<Codec> WebRtcVideoEngine::recv_codecs(bool include_rtx) const { |
| return GetPayloadTypesAndDefaultCodecs(decoder_factory_.get(), |
| /*is_decoder_factory=*/true, |
| include_rtx, trials_); |
| } |
| |
| std::vector<webrtc::RtpHeaderExtensionCapability> |
| WebRtcVideoEngine::GetRtpHeaderExtensions() const { |
| std::vector<webrtc::RtpHeaderExtensionCapability> result; |
| // id is *not* incremented for non-default extensions, UsedIds needs to |
| // resolve conflicts. |
| int id = 1; |
| for (const auto& uri : |
| {webrtc::RtpExtension::kTimestampOffsetUri, |
| webrtc::RtpExtension::kAbsSendTimeUri, |
| webrtc::RtpExtension::kVideoRotationUri, |
| webrtc::RtpExtension::kTransportSequenceNumberUri, |
| webrtc::RtpExtension::kPlayoutDelayUri, |
| webrtc::RtpExtension::kVideoContentTypeUri, |
| webrtc::RtpExtension::kVideoTimingUri, |
| webrtc::RtpExtension::kColorSpaceUri, webrtc::RtpExtension::kMidUri, |
| webrtc::RtpExtension::kRidUri, webrtc::RtpExtension::kRepairedRidUri}) { |
| result.emplace_back(uri, id++, webrtc::RtpTransceiverDirection::kSendRecv); |
| } |
| result.emplace_back(webrtc::RtpExtension::kCorruptionDetectionUri, id++, |
| /*preferred_encrypt=*/true, |
| webrtc::RtpTransceiverDirection::kStopped); |
| for (const auto& uri : {webrtc::RtpExtension::kAbsoluteCaptureTimeUri}) { |
| result.emplace_back(uri, id, webrtc::RtpTransceiverDirection::kStopped); |
| } |
| result.emplace_back(webrtc::RtpExtension::kGenericFrameDescriptorUri00, id, |
| IsEnabled(trials_, "WebRTC-GenericDescriptorAdvertised") |
| ? webrtc::RtpTransceiverDirection::kSendRecv |
| : webrtc::RtpTransceiverDirection::kStopped); |
| result.emplace_back( |
| webrtc::RtpExtension::kDependencyDescriptorUri, id, |
| IsEnabled(trials_, "WebRTC-DependencyDescriptorAdvertised") |
| ? webrtc::RtpTransceiverDirection::kSendRecv |
| : webrtc::RtpTransceiverDirection::kStopped); |
| result.emplace_back( |
| webrtc::RtpExtension::kVideoLayersAllocationUri, id, |
| IsEnabled(trials_, "WebRTC-VideoLayersAllocationAdvertised") |
| ? webrtc::RtpTransceiverDirection::kSendRecv |
| : webrtc::RtpTransceiverDirection::kStopped); |
| |
| // VideoFrameTrackingId is a test-only extension. |
| if (IsEnabled(trials_, "WebRTC-VideoFrameTrackingIdAdvertised")) { |
| result.emplace_back(webrtc::RtpExtension::kVideoFrameTrackingIdUri, id, |
| webrtc::RtpTransceiverDirection::kSendRecv); |
| } |
| return result; |
| } |
| |
| // Free function, exported for testing |
| std::map<uint32_t, webrtc::VideoSendStream::StreamStats> |
| MergeInfoAboutOutboundRtpSubstreamsForTesting( |
| const std::map<uint32_t, webrtc::VideoSendStream::StreamStats>& |
| substreams) { |
| return MergeInfoAboutOutboundRtpSubstreams(substreams); |
| } |
| |
| // --------------- WebRtcVideoSendChannel ---------------------- |
| WebRtcVideoSendChannel::WebRtcVideoSendChannel( |
| webrtc::Call* call, |
| const MediaConfig& config, |
| const VideoOptions& options, |
| const webrtc::CryptoOptions& crypto_options, |
| webrtc::VideoEncoderFactory* encoder_factory, |
| webrtc::VideoDecoderFactory* decoder_factory, |
| webrtc::VideoBitrateAllocatorFactory* bitrate_allocator_factory) |
| : MediaChannelUtil(call->network_thread(), config.enable_dscp), |
| worker_thread_(call->worker_thread()), |
| sending_(false), |
| receiving_(false), |
| call_(call), |
| default_sink_(nullptr), |
| video_config_(config.video), |
| encoder_factory_(encoder_factory), |
| decoder_factory_(decoder_factory), |
| bitrate_allocator_factory_(bitrate_allocator_factory), |
| default_send_options_(options), |
| last_send_stats_log_ms_(-1), |
| last_receive_stats_log_ms_(-1), |
| discard_unknown_ssrc_packets_( |
| IsEnabled(call_->trials(), |
| "WebRTC-Video-DiscardPacketsWithUnknownSsrc")), |
| crypto_options_(crypto_options) { |
| RTC_DCHECK_RUN_ON(&thread_checker_); |
| rtcp_receiver_report_ssrc_ = kDefaultRtcpReceiverReportSsrc; |
| recv_codecs_ = MapCodecs(GetPayloadTypesAndDefaultCodecs( |
| decoder_factory_, /*is_decoder_factory=*/true, |
| /*include_rtx=*/true, call_->trials())); |
| recv_flexfec_payload_type_ = |
| recv_codecs_.empty() ? 0 : recv_codecs_.front().flexfec_payload_type; |
| } |
| |
| WebRtcVideoSendChannel::~WebRtcVideoSendChannel() { |
| for (auto& kv : send_streams_) |
| delete kv.second; |
| } |
| |
| rtc::scoped_refptr<webrtc::VideoEncoderConfig::EncoderSpecificSettings> |
| WebRtcVideoSendChannel::WebRtcVideoSendStream::ConfigureVideoEncoderSettings( |
| const Codec& codec) { |
| RTC_DCHECK_RUN_ON(&thread_checker_); |
| bool is_screencast = parameters_.options.is_screencast.value_or(false); |
| // No automatic resizing when using simulcast or screencast, or when |
| // disabled by field trial flag. |
| bool automatic_resize = !disable_automatic_resize_ && !is_screencast && |
| (parameters_.config.rtp.ssrcs.size() == 1 || |
| NumActiveStreams(rtp_parameters_) == 1); |
| |
| bool denoising; |
| bool codec_default_denoising = false; |
| if (is_screencast) { |
| denoising = false; |
| } else { |
| // Use codec default if video_noise_reduction is unset. |
| codec_default_denoising = !parameters_.options.video_noise_reduction; |
| denoising = parameters_.options.video_noise_reduction.value_or(false); |
| } |
| |
| if (absl::EqualsIgnoreCase(codec.name, kH264CodecName)) { |
| return nullptr; |
| } |
| if (absl::EqualsIgnoreCase(codec.name, kVp8CodecName)) { |
| webrtc::VideoCodecVP8 vp8_settings = |
| webrtc::VideoEncoder::GetDefaultVp8Settings(); |
| vp8_settings.automaticResizeOn = automatic_resize; |
| // VP8 denoising is enabled by default. |
| vp8_settings.denoisingOn = codec_default_denoising ? true : denoising; |
| return rtc::make_ref_counted< |
| webrtc::VideoEncoderConfig::Vp8EncoderSpecificSettings>(vp8_settings); |
| } |
| if (absl::EqualsIgnoreCase(codec.name, kVp9CodecName)) { |
| webrtc::VideoCodecVP9 vp9_settings = |
| webrtc::VideoEncoder::GetDefaultVp9Settings(); |
| |
| vp9_settings.numberOfSpatialLayers = std::min<unsigned char>( |
| parameters_.config.rtp.ssrcs.size(), kConferenceMaxNumSpatialLayers); |
| vp9_settings.numberOfTemporalLayers = |
| std::min<unsigned char>(parameters_.config.rtp.ssrcs.size() > 1 |
| ? kConferenceDefaultNumTemporalLayers |
| : 1, |
| kConferenceMaxNumTemporalLayers); |
| |
| // VP9 denoising is disabled by default. |
| vp9_settings.denoisingOn = codec_default_denoising ? true : denoising; |
| // Disable automatic resize if more than one spatial layer is requested. |
| bool vp9_automatic_resize = automatic_resize; |
| std::optional<int> num_spatial_layers = |
| NumSpatialLayersFromEncoding(rtp_parameters_, /*idx=*/0); |
| if (num_spatial_layers && *num_spatial_layers > 1) { |
| vp9_automatic_resize = false; |
| } |
| vp9_settings.automaticResizeOn = vp9_automatic_resize; |
| if (!is_screencast) { |
| webrtc::FieldTrialFlag interlayer_pred_experiment_enabled("Enabled"); |
| webrtc::FieldTrialEnum<webrtc::InterLayerPredMode> inter_layer_pred_mode( |
| "inter_layer_pred_mode", webrtc::InterLayerPredMode::kOnKeyPic, |
| {{"off", webrtc::InterLayerPredMode::kOff}, |
| {"on", webrtc::InterLayerPredMode::kOn}, |
| {"onkeypic", webrtc::InterLayerPredMode::kOnKeyPic}}); |
| webrtc::ParseFieldTrial( |
| {&interlayer_pred_experiment_enabled, &inter_layer_pred_mode}, |
| call_->trials().Lookup("WebRTC-Vp9InterLayerPred")); |
| if (interlayer_pred_experiment_enabled) { |
| vp9_settings.interLayerPred = inter_layer_pred_mode; |
| } else { |
| // Limit inter-layer prediction to key pictures by default. |
| vp9_settings.interLayerPred = webrtc::InterLayerPredMode::kOnKeyPic; |
| } |
| |
| // TODO(webrtc:329396373): Remove after flexible mode is fully deployed. |
| vp9_settings.flexibleMode = |
| !IsDisabled(call_->trials(), "WebRTC-Video-Vp9FlexibleMode"); |
| } else { |
| // Multiple spatial layers vp9 screenshare needs flexible mode. |
| vp9_settings.flexibleMode = vp9_settings.numberOfSpatialLayers > 1; |
| vp9_settings.interLayerPred = webrtc::InterLayerPredMode::kOn; |
| } |
| return rtc::make_ref_counted< |
| webrtc::VideoEncoderConfig::Vp9EncoderSpecificSettings>(vp9_settings); |
| } |
| if (absl::EqualsIgnoreCase(codec.name, kAv1CodecName)) { |
| webrtc::VideoCodecAV1 av1_settings = {.automatic_resize_on = |
| automatic_resize}; |
| if (NumSpatialLayersFromEncoding(rtp_parameters_, /*idx=*/0) > 1) { |
| av1_settings.automatic_resize_on = false; |
| } |
| return rtc::make_ref_counted< |
| webrtc::VideoEncoderConfig::Av1EncoderSpecificSettings>(av1_settings); |
| } |
| return nullptr; |
| } |
| std::vector<VideoCodecSettings> WebRtcVideoSendChannel::SelectSendVideoCodecs( |
| const std::vector<VideoCodecSettings>& remote_mapped_codecs) const { |
| std::vector<webrtc::SdpVideoFormat> sdp_formats = |
| encoder_factory_ ? encoder_factory_->GetImplementations() |
| : std::vector<webrtc::SdpVideoFormat>(); |
| |
| // The returned vector holds the VideoCodecSettings in term of preference. |
| // They are orderd by receive codec preference first and local implementation |
| // preference second. |
| std::vector<VideoCodecSettings> encoders; |
| for (const VideoCodecSettings& remote_codec : remote_mapped_codecs) { |
| for (auto format_it = sdp_formats.begin(); |
| format_it != sdp_formats.end();) { |
| // For H264, we will limit the encode level to the remote offered level |
| // regardless if level asymmetry is allowed or not. This is strictly not |
| // following the spec in https://tools.ietf.org/html/rfc6184#section-8.2.2 |
| // since we should limit the encode level to the lower of local and remote |
| // level when level asymmetry is not allowed. |
| // For H.265, the level asymmetry is implicitly allowed. We need to make |
| // sure the encode level is set to the remote offered level. |
| if (format_it->IsSameCodec( |
| {remote_codec.codec.name, remote_codec.codec.params})) { |
| encoders.push_back(remote_codec); |
| |
| // To allow the VideoEncoderFactory to keep information about which |
| // implementation to instantitate when CreateEncoder is called the two |
| // parmeter sets are merged. |
| encoders.back().codec.params.insert(format_it->parameters.begin(), |
| format_it->parameters.end()); |
| |
| format_it = sdp_formats.erase(format_it); |
| } else { |
| ++format_it; |
| } |
| } |
| } |
| |
| return encoders; |
| } |
| |
| bool WebRtcVideoSendChannel::GetChangedSenderParameters( |
| const VideoSenderParameters& params, |
| ChangedSenderParameters* changed_params) const { |
| if (!ValidateCodecFormats(params.codecs) || |
| !ValidateRtpExtensions(params.extensions, send_rtp_extensions_)) { |
| return false; |
| } |
| |
| std::vector<VideoCodecSettings> mapped_codecs = MapCodecs(params.codecs); |
| if (mapped_codecs.empty()) { |
| // This suggests a failure in MapCodecs, e.g. inconsistent RTX codecs. |
| return false; |
| } |
| |
| std::vector<VideoCodecSettings> negotiated_codecs = |
| SelectSendVideoCodecs(mapped_codecs); |
| |
| if (params.is_stream_active && negotiated_codecs.empty()) { |
| // This is not a failure but will lead to the answer being rejected. |
| RTC_LOG(LS_ERROR) << "No video codecs in common."; |
| return true; |
| } |
| |
| // Never enable sending FlexFEC, unless we are in the experiment. |
| if (!IsEnabled(call_->trials(), "WebRTC-FlexFEC-03")) { |
| for (VideoCodecSettings& codec : negotiated_codecs) |
| codec.flexfec_payload_type = -1; |
| } |
| |
| std::optional<VideoCodecSettings> force_codec; |
| if (!send_streams_.empty()) { |
| // Since we do not support mixed-codec simulcast yet, |
| // all send streams must have the same codec value. |
| auto rtp_parameters = send_streams_.begin()->second->GetRtpParameters(); |
| if (rtp_parameters.encodings[0].codec) { |
| auto matched_codec = |
| absl::c_find_if(negotiated_codecs, [&](auto negotiated_codec) { |
| return IsSameRtpCodec(negotiated_codec.codec, |
| *rtp_parameters.encodings[0].codec); |
| }); |
| if (matched_codec != negotiated_codecs.end()) { |
| force_codec = *matched_codec; |
| } else { |
| // The requested codec has been negotiated away, we clear it from the |
| // parameters. |
| for (auto& encoding : rtp_parameters.encodings) { |
| encoding.codec.reset(); |
| } |
| send_streams_.begin()->second->SetRtpParameters(rtp_parameters, |
| nullptr); |
| } |
| } |
| } |
| |
| if (negotiated_codecs_ != negotiated_codecs) { |
| if (negotiated_codecs.empty()) { |
| changed_params->send_codec = std::nullopt; |
| } else if (force_codec) { |
| changed_params->send_codec = force_codec; |
| } else if (send_codec() != negotiated_codecs.front()) { |
| changed_params->send_codec = negotiated_codecs.front(); |
| } |
| changed_params->negotiated_codecs = negotiated_codecs; |
| } |
| |
| // For mixed-codec simulcast |
| std::vector<VideoCodecSettings> send_codecs; |
| if (!send_streams_.empty() && !negotiated_codecs.empty()) { |
| bool needs_update = false; |
| auto rtp_parameters = send_streams_.begin()->second->GetRtpParameters(); |
| for (auto& encoding : rtp_parameters.encodings) { |
| if (encoding.codec) { |
| auto matched_codec = |
| absl::c_find_if(negotiated_codecs, [&](auto negotiated_codec) { |
| return IsSameRtpCodec(negotiated_codec.codec, *encoding.codec); |
| }); |
| if (matched_codec != negotiated_codecs.end()) { |
| send_codecs.push_back(*matched_codec); |
| } else { |
| // The requested codec has been negotiated away, we clear it from the |
| // parameters. |
| encoding.codec.reset(); |
| needs_update = true; |
| send_codecs.push_back(negotiated_codecs.front()); |
| } |
| } else { |
| send_codecs.push_back(negotiated_codecs.front()); |
| } |
| } |
| |
| if (needs_update) { |
| send_streams_.begin()->second->SetRtpParameters(rtp_parameters, nullptr); |
| } |
| } |
| |
| if (send_codecs_ != send_codecs) { |
| changed_params->send_codecs = send_codecs; |
| } |
| |
| // Handle RTP header extensions. |
| if (params.extmap_allow_mixed != ExtmapAllowMixed()) { |
| changed_params->extmap_allow_mixed = params.extmap_allow_mixed; |
| } |
| std::vector<webrtc::RtpExtension> filtered_extensions = FilterRtpExtensions( |
| params.extensions, webrtc::RtpExtension::IsSupportedForVideo, true, |
| call_->trials()); |
| if (send_rtp_extensions_ != filtered_extensions) { |
| changed_params->rtp_header_extensions = |
| std::optional<std::vector<webrtc::RtpExtension>>(filtered_extensions); |
| } |
| |
| if (params.mid != send_params_.mid) { |
| changed_params->mid = params.mid; |
| } |
| |
| // Handle max bitrate. |
| if (params.max_bandwidth_bps != send_params_.max_bandwidth_bps && |
| params.max_bandwidth_bps >= -1) { |
| // 0 or -1 uncaps max bitrate. |
| // TODO(pbos): Reconsider how 0 should be treated. It is not mentioned as a |
| // special value and might very well be used for stopping sending. |
| changed_params->max_bandwidth_bps = |
| params.max_bandwidth_bps == 0 ? -1 : params.max_bandwidth_bps; |
| } |
| |
| // Handle conference mode. |
| if (params.conference_mode != send_params_.conference_mode) { |
| changed_params->conference_mode = params.conference_mode; |
| } |
| |
| // Handle RTCP mode. |
| if (params.rtcp.reduced_size != send_params_.rtcp.reduced_size) { |
| changed_params->rtcp_mode = params.rtcp.reduced_size |
| ? webrtc::RtcpMode::kReducedSize |
| : webrtc::RtcpMode::kCompound; |
| } |
| |
| return true; |
| } |
| |
| bool WebRtcVideoSendChannel::SetSenderParameters( |
| const VideoSenderParameters& params) { |
| RTC_DCHECK_RUN_ON(&thread_checker_); |
| TRACE_EVENT0("webrtc", "WebRtcVideoSendChannel::SetSenderParameters"); |
| RTC_LOG(LS_INFO) << "SetSenderParameters: " << params.ToString(); |
| ChangedSenderParameters changed_params; |
| if (!GetChangedSenderParameters(params, &changed_params)) { |
| return false; |
| } |
| |
| if (changed_params.negotiated_codecs) { |
| for (const auto& send_codec : *changed_params.negotiated_codecs) |
| RTC_LOG(LS_INFO) << "Negotiated codec: " << send_codec.codec.ToString(); |
| } |
| |
| send_params_ = params; |
| return ApplyChangedParams(changed_params); |
| } |
| |
| void WebRtcVideoSendChannel::RequestEncoderFallback() { |
| if (!worker_thread_->IsCurrent()) { |
| worker_thread_->PostTask( |
| SafeTask(task_safety_.flag(), [this] { RequestEncoderFallback(); })); |
| return; |
| } |
| |
| RTC_DCHECK_RUN_ON(&thread_checker_); |
| if (negotiated_codecs_.size() <= 1) { |
| RTC_LOG(LS_WARNING) << "Encoder failed but no fallback codec is available"; |
| return; |
| } |
| |
| ChangedSenderParameters params; |
| params.negotiated_codecs = negotiated_codecs_; |
| params.negotiated_codecs->erase(params.negotiated_codecs->begin()); |
| params.send_codec = params.negotiated_codecs->front(); |
| ApplyChangedParams(params); |
| } |
| |
| void WebRtcVideoSendChannel::RequestEncoderSwitch( |
| const webrtc::SdpVideoFormat& format, |
| bool allow_default_fallback) { |
| if (!worker_thread_->IsCurrent()) { |
| worker_thread_->PostTask( |
| SafeTask(task_safety_.flag(), [this, format, allow_default_fallback] { |
| RequestEncoderSwitch(format, allow_default_fallback); |
| })); |
| return; |
| } |
| |
| RTC_DCHECK_RUN_ON(&thread_checker_); |
| |
| for (const VideoCodecSettings& codec_setting : negotiated_codecs_) { |
| if (format.IsSameCodec( |
| {codec_setting.codec.name, codec_setting.codec.params})) { |
| VideoCodecSettings new_codec_setting = codec_setting; |
| for (const auto& kv : format.parameters) { |
| new_codec_setting.codec.params[kv.first] = kv.second; |
| } |
| |
| if (send_codec() == new_codec_setting) { |
| // Already using this codec, no switch required. |
| return; |
| } |
| |
| ChangedSenderParameters params; |
| params.send_codec = new_codec_setting; |
| ApplyChangedParams(params); |
| return; |
| } |
| } |
| |
| RTC_LOG(LS_WARNING) << "Failed to switch encoder to: " << format.ToString() |
| << ". Is default fallback allowed: " |
| << allow_default_fallback; |
| |
| if (allow_default_fallback) { |
| RequestEncoderFallback(); |
| } |
| } |
| |
| bool WebRtcVideoSendChannel::ApplyChangedParams( |
| const ChangedSenderParameters& changed_params) { |
| RTC_DCHECK_RUN_ON(&thread_checker_); |
| if (changed_params.negotiated_codecs) |
| negotiated_codecs_ = *changed_params.negotiated_codecs; |
| |
| if (changed_params.send_codec) |
| send_codec() = changed_params.send_codec; |
| |
| if (changed_params.send_codecs) |
| send_codecs_ = *changed_params.send_codecs; |
| |
| if (changed_params.extmap_allow_mixed) { |
| SetExtmapAllowMixed(*changed_params.extmap_allow_mixed); |
| } |
| if (changed_params.rtp_header_extensions) { |
| send_rtp_extensions_ = *changed_params.rtp_header_extensions; |
| } |
| |
| if (changed_params.send_codec || changed_params.max_bandwidth_bps) { |
| if (send_params_.max_bandwidth_bps == -1) { |
| // Unset the global max bitrate (max_bitrate_bps) if max_bandwidth_bps is |
| // -1, which corresponds to no "b=AS" attribute in SDP. Note that the |
| // global max bitrate may be set below in GetBitrateConfigForCodec, from |
| // the codec max bitrate. |
| // TODO(pbos): This should be reconsidered (codec max bitrate should |
| // probably not affect global call max bitrate). |
| bitrate_config_.max_bitrate_bps = -1; |
| } |
| |
| if (send_codec()) { |
| // TODO(holmer): Changing the codec parameters shouldn't necessarily mean |
| // that we change the min/max of bandwidth estimation. Reevaluate this. |
| bitrate_config_ = GetBitrateConfigForCodec(send_codec()->codec); |
| if (!changed_params.send_codec) { |
| // If the codec isn't changing, set the start bitrate to -1 which means |
| // "unchanged" so that BWE isn't affected. |
| bitrate_config_.start_bitrate_bps = -1; |
| } |
| } |
| |
| if (send_params_.max_bandwidth_bps >= 0) { |
| // Note that max_bandwidth_bps intentionally takes priority over the |
| // bitrate config for the codec. This allows FEC to be applied above the |
| // codec target bitrate. |
| // TODO(pbos): Figure out whether b=AS means max bitrate for this |
| // WebRtcVideoSendChannel (in which case we're good), or per sender |
| // (SSRC), in which case this should not set a BitrateConstraints but |
| // rather reconfigure all senders. |
| bitrate_config_.max_bitrate_bps = send_params_.max_bandwidth_bps == 0 |
| ? -1 |
| : send_params_.max_bandwidth_bps; |
| } |
| |
| call_->GetTransportControllerSend()->SetSdpBitrateParameters( |
| bitrate_config_); |
| } |
| |
| for (auto& kv : send_streams_) { |
| kv.second->SetSenderParameters(changed_params); |
| } |
| if (changed_params.send_codec || changed_params.rtcp_mode) { |
| if (send_codec_changed_callback_) { |
| send_codec_changed_callback_(); |
| } |
| } |
| return true; |
| } |
| |
| webrtc::RtpParameters WebRtcVideoSendChannel::GetRtpSendParameters( |
| uint32_t ssrc) const { |
| RTC_DCHECK_RUN_ON(&thread_checker_); |
| auto it = send_streams_.find(ssrc); |
| if (it == send_streams_.end()) { |
| RTC_LOG(LS_WARNING) << "Attempting to get RTP send parameters for stream " |
| "with ssrc " |
| << ssrc << " which doesn't exist."; |
| return webrtc::RtpParameters(); |
| } |
| |
| webrtc::RtpParameters rtp_params = it->second->GetRtpParameters(); |
| // Need to add the common list of codecs to the send stream-specific |
| // RTP parameters. |
| for (const Codec& codec : send_params_.codecs) { |
| if (send_codec() && send_codec()->codec.id == codec.id) { |
| // Put the current send codec to the front of the codecs list. |
| RTC_DCHECK_EQ(codec.name, send_codec()->codec.name); |
| rtp_params.codecs.insert(rtp_params.codecs.begin(), |
| codec.ToCodecParameters()); |
| } else { |
| rtp_params.codecs.push_back(codec.ToCodecParameters()); |
| } |
| } |
| |
| return rtp_params; |
| } |
| |
| webrtc::RTCError WebRtcVideoSendChannel::SetRtpSendParameters( |
| uint32_t ssrc, |
| const webrtc::RtpParameters& parameters, |
| webrtc::SetParametersCallback callback) { |
| RTC_DCHECK_RUN_ON(&thread_checker_); |
| TRACE_EVENT0("webrtc", "WebRtcVideoSendChannel::SetRtpSendParameters"); |
| auto it = send_streams_.find(ssrc); |
| if (it == send_streams_.end()) { |
| RTC_LOG(LS_ERROR) << "Attempting to set RTP send parameters for stream " |
| "with ssrc " |
| << ssrc << " which doesn't exist."; |
| return webrtc::InvokeSetParametersCallback( |
| callback, webrtc::RTCError(webrtc::RTCErrorType::INTERNAL_ERROR)); |
| } |
| |
| // TODO(deadbeef): Handle setting parameters with a list of codecs in a |
| // different order (which should change the send codec). |
| webrtc::RtpParameters current_parameters = GetRtpSendParameters(ssrc); |
| if (current_parameters.codecs != parameters.codecs) { |
| RTC_DLOG(LS_ERROR) << "Using SetParameters to change the set of codecs " |
| "is not currently supported."; |
| return webrtc::InvokeSetParametersCallback( |
| callback, webrtc::RTCError(webrtc::RTCErrorType::INTERNAL_ERROR)); |
| } |
| |
| if (!parameters.encodings.empty()) { |
| // Note that these values come from: |
| // https://tools.ietf.org/html/draft-ietf-tsvwg-rtcweb-qos-16#section-5 |
| // TODO(deadbeef): Change values depending on whether we are sending a |
| // keyframe or non-keyframe. |
| rtc::DiffServCodePoint new_dscp = rtc::DSCP_DEFAULT; |
| switch (parameters.encodings[0].network_priority) { |
| case webrtc::Priority::kVeryLow: |
| new_dscp = rtc::DSCP_CS1; |
| break; |
| case webrtc::Priority::kLow: |
| new_dscp = rtc::DSCP_DEFAULT; |
| break; |
| case webrtc::Priority::kMedium: |
| new_dscp = rtc::DSCP_AF42; |
| break; |
| case webrtc::Priority::kHigh: |
| new_dscp = rtc::DSCP_AF41; |
| break; |
| } |
| |
| // Since we validate that all layers have the same value, we can just check |
| // the first layer. |
| // TODO(orphis): Support mixed-codec simulcast |
| if (parameters.encodings[0].codec && send_codec_ && |
| !IsSameRtpCodec(send_codec_->codec, *parameters.encodings[0].codec)) { |
| RTC_LOG(LS_VERBOSE) << "Trying to change codec to " |
| << parameters.encodings[0].codec->name; |
| auto matched_codec = |
| absl::c_find_if(negotiated_codecs_, [&](auto negotiated_codec) { |
| return IsSameRtpCodec(negotiated_codec.codec, |
| *parameters.encodings[0].codec); |
| }); |
| if (matched_codec == negotiated_codecs_.end()) { |
| return webrtc::InvokeSetParametersCallback( |
| callback, webrtc::RTCError( |
| webrtc::RTCErrorType::INVALID_MODIFICATION, |
| "Attempted to use an unsupported codec for layer 0")); |
| } |
| |
| ChangedSenderParameters params; |
| params.send_codec = *matched_codec; |
| ApplyChangedParams(params); |
| } |
| |
| SetPreferredDscp(new_dscp); |
| } |
| |
| return it->second->SetRtpParameters(parameters, std::move(callback)); |
| } |
| std::optional<Codec> WebRtcVideoSendChannel::GetSendCodec() const { |
| RTC_DCHECK_RUN_ON(&thread_checker_); |
| if (!send_codec()) { |
| RTC_LOG(LS_VERBOSE) << "GetSendCodec: No send codec set."; |
| return std::nullopt; |
| } |
| return send_codec()->codec; |
| } |
| |
| bool WebRtcVideoSendChannel::SetSend(bool send) { |
| RTC_DCHECK_RUN_ON(&thread_checker_); |
| TRACE_EVENT0("webrtc", "WebRtcVideoSendChannel::SetSend"); |
| RTC_LOG(LS_VERBOSE) << "SetSend: " << (send ? "true" : "false"); |
| if (send && !send_codec()) { |
| RTC_DLOG(LS_ERROR) << "SetSend(true) called before setting codec."; |
| return false; |
| } |
| for (const auto& kv : send_streams_) { |
| kv.second->SetSend(send); |
| } |
| sending_ = send; |
| return true; |
| } |
| |
| bool WebRtcVideoSendChannel::SetVideoSend( |
| uint32_t ssrc, |
| const VideoOptions* options, |
| rtc::VideoSourceInterface<webrtc::VideoFrame>* source) { |
| RTC_DCHECK_RUN_ON(&thread_checker_); |
| TRACE_EVENT0("webrtc", "SetVideoSend"); |
| RTC_DCHECK(ssrc != 0); |
| RTC_LOG(LS_INFO) << "SetVideoSend (ssrc= " << ssrc << ", options: " |
| << (options ? options->ToString() : "nullptr") |
| << ", source = " << (source ? "(source)" : "nullptr") << ")"; |
| |
| const auto& kv = send_streams_.find(ssrc); |
| if (kv == send_streams_.end()) { |
| // Allow unknown ssrc only if source is null. |
| RTC_CHECK(source == nullptr); |
| RTC_LOG(LS_ERROR) << "No sending stream on ssrc " << ssrc; |
| return false; |
| } |
| |
| return kv->second->SetVideoSend(options, source); |
| } |
| |
| bool WebRtcVideoSendChannel::ValidateSendSsrcAvailability( |
| const StreamParams& sp) const { |
| for (uint32_t ssrc : sp.ssrcs) { |
| if (send_ssrcs_.find(ssrc) != send_ssrcs_.end()) { |
| RTC_LOG(LS_ERROR) << "Send stream with SSRC '" << ssrc |
| << "' already exists."; |
| return false; |
| } |
| } |
| return true; |
| } |
| bool WebRtcVideoSendChannel::AddSendStream(const StreamParams& sp) { |
| RTC_DCHECK_RUN_ON(&thread_checker_); |
| |
| RTC_LOG(LS_INFO) << "AddSendStream: " << sp.ToString(); |
| if (!ValidateStreamParams(sp)) |
| return false; |
| |
| if (!ValidateSendSsrcAvailability(sp)) |
| return false; |
| |
| for (uint32_t used_ssrc : sp.ssrcs) |
| send_ssrcs_.insert(used_ssrc); |
| |
| webrtc::VideoSendStream::Config config(transport()); |
| |
| for (const RidDescription& rid : sp.rids()) { |
| config.rtp.rids.push_back(rid.rid); |
| } |
| |
| config.suspend_below_min_bitrate = video_config_.suspend_below_min_bitrate; |
| config.periodic_alr_bandwidth_probing = |
| video_config_.periodic_alr_bandwidth_probing; |
| config.encoder_settings.experiment_cpu_load_estimator = |
| video_config_.experiment_cpu_load_estimator; |
| config.encoder_settings.encoder_factory = encoder_factory_; |
| config.encoder_settings.bitrate_allocator_factory = |
| bitrate_allocator_factory_; |
| config.encoder_settings.encoder_switch_request_callback = this; |
| |
| config.crypto_options = crypto_options_; |
| config.rtp.extmap_allow_mixed = ExtmapAllowMixed(); |
| config.rtcp_report_interval_ms = video_config_.rtcp_report_interval_ms; |
| config.rtp.enable_send_packet_batching = |
| video_config_.enable_send_packet_batching; |
| |
| WebRtcVideoSendStream* stream = new WebRtcVideoSendStream( |
| call_, sp, std::move(config), default_send_options_, |
| video_config_.enable_cpu_adaptation, bitrate_config_.max_bitrate_bps, |
| send_codec(), send_codecs_, send_rtp_extensions_, send_params_); |
| |
| uint32_t ssrc = sp.first_ssrc(); |
| RTC_DCHECK(ssrc != 0); |
| send_streams_[ssrc] = stream; |
| |
| if (ssrc_list_changed_callback_) { |
| ssrc_list_changed_callback_(send_ssrcs_); |
| } |
| |
| if (sending_) { |
| stream->SetSend(true); |
| } |
| |
| return true; |
| } |
| |
| bool WebRtcVideoSendChannel::RemoveSendStream(uint32_t ssrc) { |
| RTC_DCHECK_RUN_ON(&thread_checker_); |
| RTC_LOG(LS_INFO) << "RemoveSendStream: " << ssrc; |
| |
| WebRtcVideoSendStream* removed_stream; |
| auto it = send_streams_.find(ssrc); |
| if (it == send_streams_.end()) { |
| return false; |
| } |
| |
| for (uint32_t old_ssrc : it->second->GetSsrcs()) |
| send_ssrcs_.erase(old_ssrc); |
| |
| removed_stream = it->second; |
| send_streams_.erase(it); |
| |
| // Switch receiver report SSRCs, in case the one in use is no longer valid. |
| if (ssrc_list_changed_callback_) { |
| ssrc_list_changed_callback_(send_ssrcs_); |
| } |
| |
| delete removed_stream; |
| |
| return true; |
| } |
| |
| bool WebRtcVideoSendChannel::GetStats(VideoMediaSendInfo* info) { |
| RTC_DCHECK_RUN_ON(&thread_checker_); |
| TRACE_EVENT0("webrtc", "WebRtcVideoSendChannel::GetSendStats"); |
| |
| info->Clear(); |
| if (send_streams_.empty()) { |
| return true; |
| } |
| |
| // Log stats periodically. |
| bool log_stats = false; |
| int64_t now_ms = rtc::TimeMillis(); |
| if (last_send_stats_log_ms_ == -1 || |
| now_ms - last_send_stats_log_ms_ > kStatsLogIntervalMs) { |
| last_send_stats_log_ms_ = now_ms; |
| log_stats = true; |
| } |
| |
| info->Clear(); |
| FillSenderStats(info, log_stats); |
| FillSendCodecStats(info); |
| // TODO(holmer): We should either have rtt available as a metric on |
| // VideoSend/ReceiveStreams, or we should remove rtt from VideoSenderInfo. |
| webrtc::Call::Stats stats = call_->GetStats(); |
| if (stats.rtt_ms != -1) { |
| for (size_t i = 0; i < info->senders.size(); ++i) { |
| info->senders[i].rtt_ms = stats.rtt_ms; |
| } |
| for (size_t i = 0; i < info->aggregated_senders.size(); ++i) { |
| info->aggregated_senders[i].rtt_ms = stats.rtt_ms; |
| } |
| } |
| |
| if (log_stats) |
| RTC_LOG(LS_INFO) << stats.ToString(now_ms); |
| |
| return true; |
| } |
| void WebRtcVideoSendChannel::FillSenderStats( |
| VideoMediaSendInfo* video_media_info, |
| bool log_stats) { |
| for (const auto& it : send_streams_) { |
| auto infos = it.second->GetPerLayerVideoSenderInfos(log_stats); |
| if (infos.empty()) |
| continue; |
| video_media_info->aggregated_senders.push_back( |
| it.second->GetAggregatedVideoSenderInfo(infos)); |
| for (auto&& info : infos) { |
| video_media_info->senders.push_back(info); |
| } |
| } |
| } |
| |
| void WebRtcVideoSendChannel::FillBitrateInfo( |
| BandwidthEstimationInfo* bwe_info) { |
| RTC_DCHECK_RUN_ON(&thread_checker_); |
| for (const auto& it : send_streams_) { |
| it.second->FillBitrateInfo(bwe_info); |
| } |
| } |
| |
| void WebRtcVideoSendChannel::FillSendCodecStats( |
| VideoMediaSendInfo* video_media_info) { |
| RTC_DCHECK_RUN_ON(&thread_checker_); |
| if (!send_codec()) { |
| return; |
| } |
| // Note: since RTP stats don't account for RTX and FEC separately (see |
| // https://w3c.github.io/webrtc-stats/#dom-rtcstatstype-outbound-rtp) |
| // we can omit the codec information for those here and only insert the |
| // primary codec that is being used to send here. |
| video_media_info->send_codecs.insert(std::make_pair( |
| send_codec()->codec.id, send_codec()->codec.ToCodecParameters())); |
| } |
| |
| void WebRtcVideoSendChannel::OnPacketSent(const rtc::SentPacket& sent_packet) { |
| RTC_DCHECK_RUN_ON(&network_thread_checker_); |
| // TODO(tommi): We shouldn't need to go through call_ to deliver this |
| // notification. We should already have direct access to |
| // video_send_delay_stats_ and transport_send_ptr_ via `stream_`. |
| // So we should be able to remove OnSentPacket from Call and handle this per |
| // channel instead. At the moment Call::OnSentPacket calls OnSentPacket for |
| // the video stats, for all sent packets, including audio, which causes |
| // unnecessary lookups. |
| call_->OnSentPacket(sent_packet); |
| } |
| |
| void WebRtcVideoSendChannel::OnReadyToSend(bool ready) { |
| RTC_DCHECK_RUN_ON(&network_thread_checker_); |
| RTC_LOG(LS_VERBOSE) << "OnReadyToSend: " << (ready ? "Ready." : "Not ready."); |
| call_->SignalChannelNetworkState( |
| webrtc::MediaType::VIDEO, |
| ready ? webrtc::kNetworkUp : webrtc::kNetworkDown); |
| } |
| |
| void WebRtcVideoSendChannel::OnNetworkRouteChanged( |
| absl::string_view transport_name, |
| const rtc::NetworkRoute& network_route) { |
| RTC_DCHECK_RUN_ON(&network_thread_checker_); |
| worker_thread_->PostTask(SafeTask( |
| task_safety_.flag(), |
| [this, name = std::string(transport_name), route = network_route] { |
| RTC_DCHECK_RUN_ON(&thread_checker_); |
| webrtc::RtpTransportControllerSendInterface* transport = |
| call_->GetTransportControllerSend(); |
| transport->OnNetworkRouteChanged(name, route); |
| transport->OnTransportOverheadChanged(route.packet_overhead); |
| })); |
| } |
| |
| void WebRtcVideoSendChannel::SetInterface(MediaChannelNetworkInterface* iface) { |
| RTC_DCHECK_RUN_ON(&network_thread_checker_); |
| MediaChannelUtil::SetInterface(iface); |
| |
| // Speculative change to increase the outbound socket buffer size. |
| // In b/15152257, we are seeing a significant number of packets discarded |
| // due to lack of socket buffer space, although it's not yet clear what the |
| // ideal value should be. |
| const std::string group_name_send_buf_size = |
| call_->trials().Lookup("WebRTC-SendBufferSizeBytes"); |
| int send_buffer_size = kVideoRtpSendBufferSize; |
| if (!group_name_send_buf_size.empty() && |
| (sscanf(group_name_send_buf_size.c_str(), "%d", &send_buffer_size) != 1 || |
| send_buffer_size <= 0)) { |
| RTC_LOG(LS_WARNING) << "Invalid send buffer size: " |
| << group_name_send_buf_size; |
| send_buffer_size = kVideoRtpSendBufferSize; |
| } |
| |
| MediaChannelUtil::SetOption(MediaChannelNetworkInterface::ST_RTP, |
| rtc::Socket::OPT_SNDBUF, send_buffer_size); |
| } |
| |
| void WebRtcVideoSendChannel::SetFrameEncryptor( |
| uint32_t ssrc, |
| rtc::scoped_refptr<webrtc::FrameEncryptorInterface> frame_encryptor) { |
| RTC_DCHECK_RUN_ON(&thread_checker_); |
| auto matching_stream = send_streams_.find(ssrc); |
| if (matching_stream != send_streams_.end()) { |
| matching_stream->second->SetFrameEncryptor(frame_encryptor); |
| } else { |
| RTC_LOG(LS_ERROR) << "No stream found to attach frame encryptor"; |
| } |
| } |
| |
| void WebRtcVideoSendChannel::SetEncoderSelector( |
| uint32_t ssrc, |
| webrtc::VideoEncoderFactory::EncoderSelectorInterface* encoder_selector) { |
| RTC_DCHECK_RUN_ON(&thread_checker_); |
| auto matching_stream = send_streams_.find(ssrc); |
| if (matching_stream != send_streams_.end()) { |
| matching_stream->second->SetEncoderSelector(encoder_selector); |
| } else { |
| RTC_LOG(LS_ERROR) << "No stream found to attach encoder selector"; |
| } |
| } |
| |
| WebRtcVideoSendChannel::WebRtcVideoSendStream::VideoSendStreamParameters:: |
| VideoSendStreamParameters( |
| webrtc::VideoSendStream::Config config, |
| const VideoOptions& options, |
| int max_bitrate_bps, |
| const std::optional<VideoCodecSettings>& codec_settings, |
| const std::vector<VideoCodecSettings>& codec_settings_list) |
| : config(std::move(config)), |
| options(options), |
| max_bitrate_bps(max_bitrate_bps), |
| conference_mode(false), |
| codec_settings(codec_settings), |
| codec_settings_list(codec_settings_list) {} |
| |
| WebRtcVideoSendChannel::WebRtcVideoSendStream::WebRtcVideoSendStream( |
| webrtc::Call* call, |
| const StreamParams& sp, |
| webrtc::VideoSendStream::Config config, |
| const VideoOptions& options, |
| bool enable_cpu_overuse_detection, |
| int max_bitrate_bps, |
| const std::optional<VideoCodecSettings>& codec_settings, |
| const std::vector<VideoCodecSettings>& codec_settings_list, |
| const std::optional<std::vector<webrtc::RtpExtension>>& rtp_extensions, |
| // TODO(deadbeef): Don't duplicate information between send_params, |
| // rtp_extensions, options, etc. |
| const VideoSenderParameters& send_params) |
| : worker_thread_(call->worker_thread()), |
| ssrcs_(sp.ssrcs), |
| ssrc_groups_(sp.ssrc_groups), |
| call_(call), |
| enable_cpu_overuse_detection_(enable_cpu_overuse_detection), |
| source_(nullptr), |
| stream_(nullptr), |
| parameters_(std::move(config), |
| options, |
| max_bitrate_bps, |
| codec_settings, |
| codec_settings_list), |
| rtp_parameters_(CreateRtpParametersWithEncodings(sp)), |
| sending_(false), |
| disable_automatic_resize_( |
| IsEnabled(call->trials(), "WebRTC-Video-DisableAutomaticResize")) { |
| // Maximum packet size may come in RtpConfig from external transport, for |
| // example from QuicTransportInterface implementation, so do not exceed |
| // given max_packet_size. |
| parameters_.config.rtp.max_packet_size = |
| std::min<size_t>(parameters_.config.rtp.max_packet_size, kVideoMtu); |
| parameters_.conference_mode = send_params.conference_mode; |
| |
| sp.GetPrimarySsrcs(¶meters_.config.rtp.ssrcs); |
| |
| // ValidateStreamParams should prevent this from happening. |
| RTC_CHECK(!parameters_.config.rtp.ssrcs.empty()); |
| rtp_parameters_.encodings[0].ssrc = parameters_.config.rtp.ssrcs[0]; |
| |
| // RTX. |
| sp.GetFidSsrcs(parameters_.config.rtp.ssrcs, |
| ¶meters_.config.rtp.rtx.ssrcs); |
| |
| // FlexFEC SSRCs. |
| // TODO(brandtr): This code needs to be generalized when we add support for |
| // multistream protection. |
| if (IsEnabled(call_->trials(), "WebRTC-FlexFEC-03")) { |
| uint32_t flexfec_ssrc; |
| bool flexfec_enabled = false; |
| for (uint32_t primary_ssrc : parameters_.config.rtp.ssrcs) { |
| if (sp.GetFecFrSsrc(primary_ssrc, &flexfec_ssrc)) { |
| if (flexfec_enabled) { |
| RTC_LOG(LS_INFO) |
| << "Multiple FlexFEC streams in local SDP, but " |
| "our implementation only supports a single FlexFEC " |
| "stream. Will not enable FlexFEC for proposed " |
| "stream with SSRC: " |
| << flexfec_ssrc << "."; |
| continue; |
| } |
| |
| flexfec_enabled = true; |
| parameters_.config.rtp.flexfec.ssrc = flexfec_ssrc; |
| parameters_.config.rtp.flexfec.protected_media_ssrcs = {primary_ssrc}; |
| } |
| } |
| } |
| |
| parameters_.config.rtp.c_name = sp.cname; |
| if (rtp_extensions) { |
| parameters_.config.rtp.extensions = *rtp_extensions; |
| rtp_parameters_.header_extensions = *rtp_extensions; |
| } |
| parameters_.config.rtp.rtcp_mode = send_params.rtcp.reduced_size |
| ? webrtc::RtcpMode::kReducedSize |
| : webrtc::RtcpMode::kCompound; |
| parameters_.config.rtp.mid = send_params.mid; |
| rtp_parameters_.rtcp.reduced_size = send_params.rtcp.reduced_size; |
| |
| if (codec_settings) { |
| SetCodec(*codec_settings, codec_settings_list); |
| } |
| } |
| |
| WebRtcVideoSendChannel::WebRtcVideoSendStream::~WebRtcVideoSendStream() { |
| if (stream_ != NULL) { |
| call_->DestroyVideoSendStream(stream_); |
| } |
| } |
| |
| bool WebRtcVideoSendChannel::WebRtcVideoSendStream::SetVideoSend( |
| const VideoOptions* options, |
| rtc::VideoSourceInterface<webrtc::VideoFrame>* source) { |
| TRACE_EVENT0("webrtc", "WebRtcVideoSendStream::SetVideoSend"); |
| RTC_DCHECK_RUN_ON(&thread_checker_); |
| |
| bool reconfiguration_needed = false; |
| if (options) { |
| VideoOptions old_options = parameters_.options; |
| parameters_.options.SetAll(*options); |
| if (parameters_.options.is_screencast.value_or(false) != |
| old_options.is_screencast.value_or(false) && |
| parameters_.codec_settings) { |
| // If screen content settings change, we may need to recreate the codec |
| // instance so that the correct type is used. |
| |
| SetCodec(*parameters_.codec_settings, parameters_.codec_settings_list); |
| // Mark screenshare parameter as being updated, then test for any other |
| // changes that may require codec reconfiguration. |
| old_options.is_screencast = options->is_screencast; |
| } |
| if (parameters_.options != old_options) { |
| reconfiguration_needed = true; |
| } |
| } |
| |
| if (source_ && stream_) { |
| stream_->SetSource(nullptr, webrtc::DegradationPreference::DISABLED); |
| if (source && source != source_) { |
| reconfiguration_needed = true; |
| } |
| } |
| |
| if (reconfiguration_needed) { |
| ReconfigureEncoder(nullptr); |
| } |
| |
| // Switch to the new source. |
| source_ = source; |
| if (source && stream_) { |
| stream_->SetSource(source_, GetDegradationPreference()); |
| } |
| return true; |
| } |
| |
| webrtc::DegradationPreference |
| WebRtcVideoSendChannel::WebRtcVideoSendStream::GetDegradationPreference() |
| const { |
| // Do not adapt resolution for screen content as this will likely |
| // result in blurry and unreadable text. |
| // `this` acts like a VideoSource to make sure SinkWants are handled on the |
| // correct thread. |
| if (!enable_cpu_overuse_detection_) { |
| return webrtc::DegradationPreference::DISABLED; |
| } |
| |
| webrtc::DegradationPreference degradation_preference; |
| if (rtp_parameters_.degradation_preference.has_value()) { |
| degradation_preference = *rtp_parameters_.degradation_preference; |
| } else { |
| if (parameters_.options.content_hint == |
| webrtc::VideoTrackInterface::ContentHint::kFluid) { |
| degradation_preference = |
| webrtc::DegradationPreference::MAINTAIN_FRAMERATE; |
| } else if (parameters_.options.is_screencast.value_or(false) || |
| parameters_.options.content_hint == |
| webrtc::VideoTrackInterface::ContentHint::kDetailed || |
| parameters_.options.content_hint == |
| webrtc::VideoTrackInterface::ContentHint::kText) { |
| degradation_preference = |
| webrtc::DegradationPreference::MAINTAIN_RESOLUTION; |
| } else if (IsEnabled(call_->trials(), "WebRTC-Video-BalancedDegradation")) { |
| // Standard wants balanced by default, but it needs to be tuned first. |
| degradation_preference = webrtc::DegradationPreference::BALANCED; |
| } else { |
| // Keep MAINTAIN_FRAMERATE by default until BALANCED has been tuned for |
| // all codecs and launched. |
| degradation_preference = |
| webrtc::DegradationPreference::MAINTAIN_FRAMERATE; |
| } |
| } |
| |
| return degradation_preference; |
| } |
| |
| const std::vector<uint32_t>& |
| WebRtcVideoSendChannel::WebRtcVideoSendStream::GetSsrcs() const { |
| return ssrcs_; |
| } |
| |
| void WebRtcVideoSendChannel::WebRtcVideoSendStream::SetCodec( |
| const VideoCodecSettings& codec_settings, |
| const std::vector<VideoCodecSettings>& codec_settings_list) { |
| RTC_DCHECK_RUN_ON(&thread_checker_); |
| FallbackToDefaultScalabilityModeIfNotSupported( |
| codec_settings.codec, parameters_.config, rtp_parameters_.encodings); |
| |
| parameters_.encoder_config = CreateVideoEncoderConfig(codec_settings.codec); |
| RTC_DCHECK_GT(parameters_.encoder_config.number_of_streams, 0); |
| |
| parameters_.config.rtp.payload_name = codec_settings.codec.name; |
| parameters_.config.rtp.payload_type = codec_settings.codec.id; |
| parameters_.config.rtp.raw_payload = |
| codec_settings.codec.packetization == kPacketizationParamRaw; |
| parameters_.config.rtp.ulpfec = codec_settings.ulpfec; |
| parameters_.config.rtp.flexfec.payload_type = |
| codec_settings.flexfec_payload_type; |
| |
| // Set RTX payload type if RTX is enabled. |
| if (!parameters_.config.rtp.rtx.ssrcs.empty()) { |
| if (codec_settings.rtx_payload_type == -1) { |
| RTC_LOG(LS_WARNING) |
| << "RTX SSRCs configured but there's no configured RTX " |
| "payload type. Ignoring."; |
| parameters_.config.rtp.rtx.ssrcs.clear(); |
| } else { |
| parameters_.config.rtp.rtx.payload_type = codec_settings.rtx_payload_type; |
| } |
| } |
| |
| const bool has_lntf = HasLntf(codec_settings.codec); |
| parameters_.config.rtp.lntf.enabled = has_lntf; |
| parameters_.config.encoder_settings.capabilities.loss_notification = has_lntf; |
| |
| parameters_.config.rtp.nack.rtp_history_ms = |
| HasNack(codec_settings.codec) ? kNackHistoryMs : 0; |
| |
| parameters_.codec_settings = codec_settings; |
| |
| // Settings for mixed-codec simulcast |
| if (!codec_settings_list.empty()) { |
| RTC_DCHECK_EQ(parameters_.config.rtp.ssrcs.size(), |
| codec_settings_list.size()); |
| parameters_.config.rtp.stream_configs.resize( |
| parameters_.config.rtp.ssrcs.size()); |
| for (size_t i = 0; i < codec_settings_list.size(); i++) { |
| auto& stream_config = parameters_.config.rtp.stream_configs[i]; |
| const auto& cs = codec_settings_list[i]; |
| stream_config.ssrc = parameters_.config.rtp.ssrcs[i]; |
| if (i < parameters_.config.rtp.rids.size()) { |
| stream_config.rid = parameters_.config.rtp.rids[i]; |
| } |
| stream_config.payload_name = cs.codec.name; |
| stream_config.payload_type = cs.codec.id; |
| stream_config.raw_payload = |
| cs.codec.packetization == kPacketizationParamRaw; |
| if (i < parameters_.config.rtp.rtx.ssrcs.size()) { |
| auto& rtx = stream_config.rtx.emplace( |
| decltype(stream_config.rtx)::value_type()); |
| rtx.ssrc = parameters_.config.rtp.rtx.ssrcs[i]; |
| rtx.payload_type = cs.rtx_payload_type; |
| } |
| } |
| } |
| parameters_.codec_settings_list = codec_settings_list; |
| |
| // TODO(bugs.webrtc.org/8830): Avoid recreation, it should be enough to call |
| // ReconfigureEncoder. |
| RTC_LOG(LS_INFO) << "RecreateWebRtcStream (send) because of SetCodec."; |
| RecreateWebRtcStream(); |
| } |
| |
| void WebRtcVideoSendChannel::WebRtcVideoSendStream::SetSenderParameters( |
| const ChangedSenderParameters& params) { |
| RTC_DCHECK_RUN_ON(&thread_checker_); |
| // `recreate_stream` means construction-time parameters have changed and the |
| // sending stream needs to be reset with the new config. |
| bool recreate_stream = false; |
| if (params.rtcp_mode) { |
| parameters_.config.rtp.rtcp_mode = *params.rtcp_mode; |
| rtp_parameters_.rtcp.reduced_size = |
| parameters_.config.rtp.rtcp_mode == webrtc::RtcpMode::kReducedSize; |
| recreate_stream = true; |
| } |
| if (params.extmap_allow_mixed) { |
| parameters_.config.rtp.extmap_allow_mixed = *params.extmap_allow_mixed; |
| recreate_stream = true; |
| } |
| if (params.rtp_header_extensions) { |
| parameters_.config.rtp.extensions = *params.rtp_header_extensions; |
| rtp_parameters_.header_extensions = *params.rtp_header_extensions; |
| recreate_stream = true; |
| } |
| if (params.mid) { |
| parameters_.config.rtp.mid = *params.mid; |
| recreate_stream = true; |
| } |
| if (params.max_bandwidth_bps) { |
| parameters_.max_bitrate_bps = *params.max_bandwidth_bps; |
| ReconfigureEncoder(nullptr); |
| } |
| if (params.conference_mode) { |
| parameters_.conference_mode = *params.conference_mode; |
| } |
| |
| // Set codecs and options. |
| if (params.send_codec) { |
| SetCodec(*params.send_codec, |
| params.send_codecs.value_or(parameters_.codec_settings_list)); |
| recreate_stream = false; // SetCodec has already recreated the stream. |
| } else if (params.conference_mode && parameters_.codec_settings) { |
| SetCodec(*parameters_.codec_settings, parameters_.codec_settings_list); |
| recreate_stream = false; // SetCodec has already recreated the stream. |
| } |
| if (recreate_stream) { |
| RTC_LOG(LS_INFO) |
| << "RecreateWebRtcStream (send) because of SetSenderParameters"; |
| RecreateWebRtcStream(); |
| } |
| } |
| |
| webrtc::RTCError |
| WebRtcVideoSendChannel::WebRtcVideoSendStream::SetRtpParameters( |
| const webrtc::RtpParameters& new_parameters, |
| webrtc::SetParametersCallback callback) { |
| RTC_DCHECK_RUN_ON(&thread_checker_); |
| // This is checked higher in the stack (RtpSender), so this is only checking |
| // for users accessing the private APIs or tests, not specification |
| // conformance. |
| // TODO(orphis): Migrate tests to later make this a DCHECK only |
| webrtc::RTCError error = CheckRtpParametersInvalidModificationAndValues( |
| rtp_parameters_, new_parameters, call_->trials()); |
| if (!error.ok()) { |
| return webrtc::InvokeSetParametersCallback(callback, error); |
| } |
| |
| bool new_param = false; |
| for (size_t i = 0; i < rtp_parameters_.encodings.size(); ++i) { |
| if ((new_parameters.encodings[i].min_bitrate_bps != |
| rtp_parameters_.encodings[i].min_bitrate_bps) || |
| (new_parameters.encodings[i].max_bitrate_bps != |
| rtp_parameters_.encodings[i].max_bitrate_bps) || |
| (new_parameters.encodings[i].max_framerate != |
| rtp_parameters_.encodings[i].max_framerate) || |
| (new_parameters.encodings[i].scale_resolution_down_by != |
| rtp_parameters_.encodings[i].scale_resolution_down_by) || |
| (new_parameters.encodings[i].num_temporal_layers != |
| rtp_parameters_.encodings[i].num_temporal_layers) || |
| (new_parameters.encodings[i].scale_resolution_down_to != |
| rtp_parameters_.encodings[i].scale_resolution_down_to) || |
| (new_parameters.encodings[i].scalability_mode != |
| rtp_parameters_.encodings[i].scalability_mode) || |
| (new_parameters.encodings[i].codec != |
| rtp_parameters_.encodings[i].codec)) { |
| new_param = true; |
| break; |
| } |
| } |
| |
| bool new_degradation_preference = false; |
| if (new_parameters.degradation_preference != |
| rtp_parameters_.degradation_preference) { |
| new_degradation_preference = true; |
| } |
| |
| // Some fields (e.g. bitrate priority) only need to update the bitrate |
| // allocator which is updated via ReconfigureEncoder (however, note that the |
| // actual encoder should only be reconfigured if needed). |
| bool reconfigure_encoder = |
| new_param || (new_parameters.encodings[0].bitrate_priority != |
| rtp_parameters_.encodings[0].bitrate_priority); |
| |
| // Note that the simulcast encoder adapter relies on the fact that layers |
| // de/activation triggers encoder reinitialization. |
| bool new_send_state = false; |
| for (size_t i = 0; i < rtp_parameters_.encodings.size(); ++i) { |
| bool new_active = IsLayerActive(new_parameters.encodings[i]); |
| bool old_active = IsLayerActive(rtp_parameters_.encodings[i]); |
| if (new_active != old_active) { |
| new_send_state = true; |
| } |
| } |
| |
| rtp_parameters_ = new_parameters; |
| // Codecs are currently handled at the WebRtcVideoSendChannel level. |
| rtp_parameters_.codecs.clear(); |
| if (reconfigure_encoder || new_send_state) { |
| // Callback responsibility is delegated to ReconfigureEncoder() |
| ReconfigureEncoder(std::move(callback)); |
| callback = nullptr; |
| } |
| if (new_degradation_preference) { |
| if (source_ && stream_) { |
| stream_->SetSource(source_, GetDegradationPreference()); |
| } |
| } |
| // Check if a key frame was requested via setParameters. |
| std::vector<std::string> key_frames_requested_by_rid; |
| for (const auto& encoding : rtp_parameters_.encodings) { |
| if (encoding.request_key_frame) { |
| key_frames_requested_by_rid.push_back(encoding.rid); |
| } |
| } |
| if (!key_frames_requested_by_rid.empty()) { |
| if (key_frames_requested_by_rid.size() == 1 && |
| key_frames_requested_by_rid[0] == "") { |
| // For non-simulcast cases there is no rid, |
| // request a keyframe on all layers. |
| key_frames_requested_by_rid.clear(); |
| } |
| GenerateKeyFrame(key_frames_requested_by_rid); |
| } |
| return webrtc::InvokeSetParametersCallback(callback, webrtc::RTCError::OK()); |
| } |
| |
| webrtc::RtpParameters |
| WebRtcVideoSendChannel::WebRtcVideoSendStream::GetRtpParameters() const { |
| RTC_DCHECK_RUN_ON(&thread_checker_); |
| return rtp_parameters_; |
| } |
| |
| void WebRtcVideoSendChannel::WebRtcVideoSendStream::SetFrameEncryptor( |
| rtc::scoped_refptr<webrtc::FrameEncryptorInterface> frame_encryptor) { |
| RTC_DCHECK_RUN_ON(&thread_checker_); |
| parameters_.config.frame_encryptor = frame_encryptor; |
| if (stream_) { |
| RTC_LOG(LS_INFO) |
| << "RecreateWebRtcStream (send) because of SetFrameEncryptor, ssrc=" |
| << parameters_.config.rtp.ssrcs[0]; |
| RecreateWebRtcStream(); |
| } |
| } |
| |
| void WebRtcVideoSendChannel::WebRtcVideoSendStream::SetEncoderSelector( |
| webrtc::VideoEncoderFactory::EncoderSelectorInterface* encoder_selector) { |
| RTC_DCHECK_RUN_ON(&thread_checker_); |
| parameters_.config.encoder_selector = encoder_selector; |
| if (stream_) { |
| RTC_LOG(LS_INFO) |
| << "RecreateWebRtcStream (send) because of SetEncoderSelector, ssrc=" |
| << parameters_.config.rtp.ssrcs[0]; |
| RecreateWebRtcStream(); |
| } |
| } |
| |
| void WebRtcVideoSendChannel::WebRtcVideoSendStream::UpdateSendState() { |
| RTC_DCHECK_RUN_ON(&thread_checker_); |
| if (sending_) { |
| RTC_DCHECK(stream_ != nullptr); |
| // This allows the the Stream to be used. Ie, DTLS is connected and the |
| // RtpTransceiver direction allows sending. |
| stream_->Start(); |
| } else { |
| if (stream_ != nullptr) { |
| stream_->Stop(); |
| } |
| } |
| } |
| |
| webrtc::VideoEncoderConfig |
| WebRtcVideoSendChannel::WebRtcVideoSendStream::CreateVideoEncoderConfig( |
| const Codec& codec) const { |
| RTC_DCHECK_RUN_ON(&thread_checker_); |
| webrtc::VideoEncoderConfig encoder_config; |
| encoder_config.codec_type = webrtc::PayloadStringToCodecType(codec.name); |
| encoder_config.video_format = |
| webrtc::SdpVideoFormat(codec.name, codec.params); |
| |
| bool is_screencast = parameters_.options.is_screencast.value_or(false); |
| if (is_screencast) { |
| encoder_config.min_transmit_bitrate_bps = |
| 1000 * parameters_.options.screencast_min_bitrate_kbps.value_or(0); |
| encoder_config.content_type = |
| webrtc::VideoEncoderConfig::ContentType::kScreen; |
| } else { |
| encoder_config.min_transmit_bitrate_bps = 0; |
| encoder_config.content_type = |
| webrtc::VideoEncoderConfig::ContentType::kRealtimeVideo; |
| } |
| |
| // By default, the stream count for the codec configuration should match the |
| // number of negotiated ssrcs but this may be capped below depending on the |
| // `legacy_scalability_mode` and codec used. |
| encoder_config.number_of_streams = parameters_.config.rtp.ssrcs.size(); |
| bool legacy_scalability_mode = true; |
| for (const webrtc::RtpEncodingParameters& encoding : |
| rtp_parameters_.encodings) { |
| if (encoding.scalability_mode.has_value() && |
| (encoding.scale_resolution_down_by.has_value() || |
| encoding.scale_resolution_down_to.has_value())) { |
| legacy_scalability_mode = false; |
| break; |
| } |
| } |
| // Maybe limit the number of simulcast layers depending on |
| // `legacy_scalability_mode`, codec types (VP9/AV1). This path only exists |
| // for backwards compatibility and will one day be deleted. If you want SVC, |
| // please specify with the `scalability_mode` API instead amd disabling all |
| // but one encoding. |
| if (IsCodecDisabledForSimulcast(legacy_scalability_mode, |
| encoder_config.codec_type)) { |
| encoder_config.number_of_streams = 1; |
| } |
| |
| // parameters_.max_bitrate comes from the max bitrate set at the SDP |
| // (m-section) level with the attribute "b=AS." Note that stream max bitrate |
| // is the RtpSender's max bitrate, but each individual encoding may also have |
| // its own max bitrate specified by SetParameters. |
| int stream_max_bitrate = parameters_.max_bitrate_bps; |
| // The codec max bitrate comes from the "x-google-max-bitrate" parameter |
| // attribute set in the SDP for a specific codec. It only has an effect if |
| // max bitrate is not specified through other means. |
| bool encodings_has_max_bitrate = false; |
| for (const auto& encoding : rtp_parameters_.encodings) { |
| if (encoding.active && encoding.max_bitrate_bps.value_or(0) > 0) { |
| encodings_has_max_bitrate = true; |
| break; |
| } |
| } |
| int codec_max_bitrate_kbps; |
| if (codec.GetParam(kCodecParamMaxBitrate, &codec_max_bitrate_kbps) && |
| stream_max_bitrate == -1 && !encodings_has_max_bitrate) { |
| stream_max_bitrate = codec_max_bitrate_kbps * 1000; |
| } |
| encoder_config.max_bitrate_bps = stream_max_bitrate; |
| |
| // The encoder config's default bitrate priority is set to 1.0, |
| // unless it is set through the sender's encoding parameters. |
| // The bitrate priority, which is used in the bitrate allocation, is done |
| // on a per sender basis, so we use the first encoding's value. |
| encoder_config.bitrate_priority = |
| rtp_parameters_.encodings[0].bitrate_priority; |
| |
| // Application-controlled state is held in the encoder_config's |
| // simulcast_layers. Currently this is used to control which simulcast layers |
| // are active and for configuring the min/max bitrate and max framerate. |
| // The encoder_config's simulcast_layers is also used for non-simulcast (when |
| // there is a single layer). |
| RTC_DCHECK_GE(rtp_parameters_.encodings.size(), |
| encoder_config.number_of_streams); |
| RTC_DCHECK_GT(encoder_config.number_of_streams, 0); |
| |
| // Copy all provided constraints. |
| encoder_config.simulcast_layers.resize(rtp_parameters_.encodings.size()); |
| for (size_t i = 0; i < encoder_config.simulcast_layers.size(); ++i) { |
| encoder_config.simulcast_layers[i].active = |
| rtp_parameters_.encodings[i].active; |
| encoder_config.simulcast_layers[i].scalability_mode = |
| webrtc::ScalabilityModeFromString( |
| rtp_parameters_.encodings[i].scalability_mode.value_or("")); |
| if (rtp_parameters_.encodings[i].min_bitrate_bps) { |
| encoder_config.simulcast_layers[i].min_bitrate_bps = |
| *rtp_parameters_.encodings[i].min_bitrate_bps; |
| } |
| if (rtp_parameters_.encodings[i].max_bitrate_bps) { |
| encoder_config.simulcast_layers[i].max_bitrate_bps = |
| *rtp_parameters_.encodings[i].max_bitrate_bps; |
| } |
| if (rtp_parameters_.encodings[i].max_framerate) { |
| encoder_config.simulcast_layers[i].max_framerate = |
| *rtp_parameters_.encodings[i].max_framerate; |
| } |
| if (rtp_parameters_.encodings[i].scale_resolution_down_by) { |
| encoder_config.simulcast_layers[i].scale_resolution_down_by = |
| *rtp_parameters_.encodings[i].scale_resolution_down_by; |
| } |
| if (rtp_parameters_.encodings[i].num_temporal_layers) { |
| encoder_config.simulcast_layers[i].num_temporal_layers = |
| *rtp_parameters_.encodings[i].num_temporal_layers; |
| } |
| encoder_config.simulcast_layers[i].scale_resolution_down_to = |
| rtp_parameters_.encodings[i].scale_resolution_down_to; |
| } |
| |
| encoder_config.legacy_conference_mode = parameters_.conference_mode; |
| |
| encoder_config.is_quality_scaling_allowed = |
| !disable_automatic_resize_ && !is_screencast && |
| (parameters_.config.rtp.ssrcs.size() == 1 || |
| NumActiveStreams(rtp_parameters_) == 1); |
| |
| // Ensure frame dropping is always enabled. |
| encoder_config.frame_drop_enabled = true; |
| |
| int max_qp = -1; |
| if (codec.GetParam(kCodecParamMaxQuantization, &max_qp) && max_qp > 0) { |
| encoder_config.max_qp = max_qp; |
| } |
| |
| return encoder_config; |
| } |
| |
| void WebRtcVideoSendChannel::WebRtcVideoSendStream::ReconfigureEncoder( |
| webrtc::SetParametersCallback callback) { |
| RTC_DCHECK_RUN_ON(&thread_checker_); |
| if (!stream_) { |
| // The webrtc::VideoSendStream `stream_` has not yet been created but other |
| // parameters has changed. |
| webrtc::InvokeSetParametersCallback(callback, webrtc::RTCError::OK()); |
| return; |
| } |
| |
| RTC_DCHECK_GT(parameters_.encoder_config.number_of_streams, 0); |
| |
| RTC_CHECK(parameters_.codec_settings); |
| VideoCodecSettings codec_settings = *parameters_.codec_settings; |
| |
| FallbackToDefaultScalabilityModeIfNotSupported( |
| codec_settings.codec, parameters_.config, rtp_parameters_.encodings); |
| |
| // Latest config, with and without encoder specfic settings. |
| webrtc::VideoEncoderConfig encoder_config = |
| CreateVideoEncoderConfig(codec_settings.codec); |
| encoder_config.encoder_specific_settings = |
| ConfigureVideoEncoderSettings(codec_settings.codec); |
| webrtc::VideoEncoderConfig encoder_config_with_specifics = |
| encoder_config.Copy(); |
| encoder_config.encoder_specific_settings = nullptr; |
| |
| // When switching between legacy SVC (3 encodings interpreted as 1 stream with |
| // 3 spatial layers) and the standard API (3 encodings = 3 streams and spatial |
| // layers specified by `scalability_mode`), the number of streams can change. |
| bool num_streams_changed = parameters_.encoder_config.number_of_streams != |
| encoder_config.number_of_streams; |
| parameters_.encoder_config = std::move(encoder_config); |
| |
| if (num_streams_changed) { |
| // The app is switching between legacy and standard modes, recreate instead |
| // of reconfiguring to avoid number of streams not matching in lower layers. |
| RecreateWebRtcStream(); |
| webrtc::InvokeSetParametersCallback(callback, webrtc::RTCError::OK()); |
| return; |
| } |
| |
| stream_->ReconfigureVideoEncoder(std::move(encoder_config_with_specifics), |
| std::move(callback)); |
| } |
| |
| void WebRtcVideoSendChannel::WebRtcVideoSendStream::SetSend(bool send) { |
| RTC_DCHECK_RUN_ON(&thread_checker_); |
| sending_ = send; |
| UpdateSendState(); |
| } |
| |
| std::vector<VideoSenderInfo> |
| WebRtcVideoSendChannel::WebRtcVideoSendStream::GetPerLayerVideoSenderInfos( |
| bool log_stats) { |
| RTC_DCHECK_RUN_ON(&thread_checker_); |
| VideoSenderInfo common_info; |
| if (parameters_.codec_settings) { |
| common_info.codec_name = parameters_.codec_settings->codec.name; |
| common_info.codec_payload_type = parameters_.codec_settings->codec.id; |
| } |
| std::vector<VideoSenderInfo> infos; |
| webrtc::VideoSendStream::Stats stats; |
| if (stream_ == nullptr) { |
| for (uint32_t ssrc : parameters_.config.rtp.ssrcs) { |
| common_info.add_ssrc(ssrc); |
| } |
| infos.push_back(common_info); |
| return infos; |
| } else { |
| stats = stream_->GetStats(); |
| if (log_stats) |
| RTC_LOG(LS_INFO) << stats.ToString(rtc::TimeMillis()); |
| |
| // Metrics that are in common for all substreams. |
| common_info.adapt_changes = stats.number_of_cpu_adapt_changes; |
| common_info.adapt_reason = |
| stats.cpu_limited_resolution ? ADAPTREASON_CPU : ADAPTREASON_NONE; |
| common_info.has_entered_low_resolution = stats.has_entered_low_resolution; |
| |
| // Get bandwidth limitation info from stream_->GetStats(). |
| // Input resolution (output from video_adapter) can be further scaled down |
| // or higher video layer(s) can be dropped due to bitrate constraints. |
| // Note, adapt_changes only include changes from the video_adapter. |
| if (stats.bw_limited_resolution) |
| common_info.adapt_reason |= ADAPTREASON_BANDWIDTH; |
| |
| common_info.quality_limitation_reason = stats.quality_limitation_reason; |
| common_info.quality_limitation_durations_ms = |
| stats.quality_limitation_durations_ms; |
| common_info.quality_limitation_resolution_changes = |
| stats.quality_limitation_resolution_changes; |
| common_info.encoder_implementation_name = stats.encoder_implementation_name; |
| common_info.target_bitrate = stats.target_media_bitrate_bps; |
| common_info.ssrc_groups = ssrc_groups_; |
| common_info.frames = stats.frames; |
| common_info.framerate_input = stats.input_frame_rate; |
| common_info.avg_encode_ms = stats.avg_encode_time_ms; |
| common_info.encode_usage_percent = stats.encode_usage_percent; |
| common_info.nominal_bitrate = stats.media_bitrate_bps; |
| common_info.content_type = stats.content_type; |
| common_info.aggregated_framerate_sent = stats.encode_frame_rate; |
| common_info.aggregated_huge_frames_sent = stats.huge_frames_sent; |
| common_info.power_efficient_encoder = stats.power_efficient_encoder; |
| |
| // 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); |
| } |
| common_info.active = |
| IsActiveFromEncodings(std::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; |
| common_info.total_encoded_bytes_target = stats.total_encoded_bytes_target; |
| common_info.frames_sent = stats.frames_encoded; |
| common_info.huge_frames_sent = stats.huge_frames_sent; |
| infos.push_back(common_info); |
| 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; |
| uint32_t ssrc = pair.first; |
| info.add_ssrc(ssrc); |
| info.rid = parameters_.config.rtp.GetRidForSsrc(ssrc); |
| info.active = IsActiveFromEncodings( |
| !is_svc ? std::optional<uint32_t>(ssrc) : std::nullopt, |
| rtp_parameters_.encodings); |
| auto stream_stats = pair.second; |
| RTC_DCHECK_EQ(stream_stats.type, |
| webrtc::VideoSendStream::StreamStats::StreamType::kMedia); |
| info.payload_bytes_sent = stream_stats.rtp_stats.transmitted.payload_bytes; |
| info.header_and_padding_bytes_sent = |
| stream_stats.rtp_stats.transmitted.header_bytes + |
| stream_stats.rtp_stats.transmitted.padding_bytes; |
| info.packets_sent = stream_stats.rtp_stats.transmitted.packets; |
| info.total_packet_send_delay += |
| stream_stats.rtp_stats.transmitted.total_packet_delay; |
| info.send_frame_width = stream_stats.width; |
| info.send_frame_height = stream_stats.height; |
| info.key_frames_encoded = stream_stats.frame_counts.key_frames; |
| info.framerate_sent = stream_stats.encode_frame_rate; |
| info.frames_encoded = stream_stats.frames_encoded; |
| info.frames_sent = stream_stats.frames_encoded; |
| info.retransmitted_bytes_sent = |
| stream_stats.rtp_stats.retransmitted.payload_bytes; |
| info.retransmitted_packets_sent = |
| stream_stats.rtp_stats.retransmitted.packets; |
| info.firs_received = stream_stats.rtcp_packet_type_counts.fir_packets; |
| info.nacks_received = stream_stats.rtcp_packet_type_counts.nack_packets; |
| info.plis_received = stream_stats.rtcp_packet_type_counts.pli_packets; |
| if (stream_stats.report_block_data.has_value()) { |
| info.packets_lost = stream_stats.report_block_data->cumulative_lost(); |
| info.fraction_lost = stream_stats.report_block_data->fraction_lost(); |
| info.report_block_datas.push_back(*stream_stats.report_block_data); |
| } |
| info.qp_sum = stream_stats.qp_sum; |
| info.total_encode_time_ms = stream_stats.total_encode_time_ms; |
| info.total_encoded_bytes_target = stream_stats.total_encoded_bytes_target; |
| info.huge_frames_sent = stream_stats.huge_frames_sent; |
| info.scalability_mode = stream_stats.scalability_mode; |
| infos.push_back(info); |
| } |
| return infos; |
| } |
| |
| VideoSenderInfo |
| WebRtcVideoSendChannel::WebRtcVideoSendStream::GetAggregatedVideoSenderInfo( |
| const std::vector<VideoSenderInfo>& infos) const { |
| RTC_DCHECK_RUN_ON(&thread_checker_); |
| RTC_CHECK(!infos.empty()); |
| if (infos.size() == 1) { |
| return infos[0]; |
| } |
| VideoSenderInfo info = infos[0]; |
| info.local_stats.clear(); |
| for (uint32_t ssrc : parameters_.config.rtp.ssrcs) { |
| info.add_ssrc(ssrc); |
| } |
| info.framerate_sent = info.aggregated_framerate_sent; |
| info.huge_frames_sent = info.aggregated_huge_frames_sent; |
| |
| for (size_t i = 1; i < infos.size(); i++) { |
| info.key_frames_encoded += infos[i].key_frames_encoded; |
| info.payload_bytes_sent += infos[i].payload_bytes_sent; |
| info.header_and_padding_bytes_sent += |
| infos[i].header_and_padding_bytes_sent; |
| info.packets_sent += infos[i].packets_sent; |
| info.total_packet_send_delay += infos[i].total_packet_send_delay; |
| info.retransmitted_bytes_sent += infos[i].retransmitted_bytes_sent; |
| info.retransmitted_packets_sent += infos[i].retransmitted_packets_sent; |
| info.packets_lost += infos[i].packets_lost; |
| if (infos[i].send_frame_width > info.send_frame_width) |
| info.send_frame_width = infos[i].send_frame_width; |
| if (infos[i].send_frame_height > info.send_frame_height) |
| info.send_frame_height = infos[i].send_frame_height; |
| info.firs_received += infos[i].firs_received; |
| info.nacks_received += infos[i].nacks_received; |
| info.plis_received += infos[i].plis_received; |
| if (infos[i].report_block_datas.size()) |
| info.report_block_datas.push_back(infos[i].report_block_datas[0]); |
| if (infos[i].qp_sum) { |
| if (!info.qp_sum) { |
| info.qp_sum = 0; |
| } |
| info.qp_sum = *info.qp_sum + *infos[i].qp_sum; |
| } |
| info.frames_encoded += infos[i].frames_encoded; |
| info.frames_sent += infos[i].frames_sent; |
| info.total_encode_time_ms += infos[i].total_encode_time_ms; |
| info.total_encoded_bytes_target += infos[i].total_encoded_bytes_target; |
| } |
| return info; |
| } |
| |
| void WebRtcVideoSendChannel::WebRtcVideoSendStream::FillBitrateInfo( |
| BandwidthEstimationInfo* bwe_info) { |
| RTC_DCHECK_RUN_ON(&thread_checker_); |
| if (stream_ == NULL) { |
| return; |
| } |
| webrtc::VideoSendStream::Stats stats = stream_->GetStats(); |
| for (const auto& it : stats.substreams) { |
| bwe_info->transmit_bitrate += it.second.total_bitrate_bps; |
| bwe_info->retransmit_bitrate += it.second.retransmit_bitrate_bps; |
| } |
| bwe_info->target_enc_bitrate += stats.target_media_bitrate_bps; |
| bwe_info->actual_enc_bitrate += stats.media_bitrate_bps; |
| } |
| |
| void WebRtcVideoSendChannel::WebRtcVideoSendStream:: |
| SetEncoderToPacketizerFrameTransformer( |
| rtc::scoped_refptr<webrtc::FrameTransformerInterface> |
| frame_transformer) { |
| RTC_DCHECK_RUN_ON(&thread_checker_); |
| parameters_.config.frame_transformer = std::move(frame_transformer); |
| if (stream_) |
| RecreateWebRtcStream(); |
| } |
| |
| void WebRtcVideoSendChannel::WebRtcVideoSendStream::RecreateWebRtcStream() { |
| RTC_DCHECK_RUN_ON(&thread_checker_); |
| if (stream_ != NULL) { |
| call_->DestroyVideoSendStream(stream_); |
| } |
| |
| RTC_CHECK(parameters_.codec_settings); |
| RTC_DCHECK_EQ((parameters_.encoder_config.content_type == |
| webrtc::VideoEncoderConfig::ContentType::kScreen), |
| parameters_.options.is_screencast.value_or(false)) |
| << "encoder content type inconsistent with screencast option"; |
| parameters_.encoder_config.encoder_specific_settings = |
| ConfigureVideoEncoderSettings(parameters_.codec_settings->codec); |
| |
| webrtc::VideoSendStream::Config config = parameters_.config.Copy(); |
| if (!config.rtp.rtx.ssrcs.empty() && config.rtp.rtx.payload_type == -1) { |
| RTC_LOG(LS_WARNING) << "RTX SSRCs configured but there's no configured RTX " |
| "payload type the set codec. Ignoring RTX."; |
| config.rtp.rtx.ssrcs.clear(); |
| } |
| if (parameters_.encoder_config.number_of_streams == 1) { |
| // SVC is used instead of simulcast. Remove unnecessary SSRCs. |
| if (config.rtp.ssrcs.size() > 1) { |
| config.rtp.ssrcs.resize(1); |
| if (config.rtp.rtx.ssrcs.size() > 1) { |
| config.rtp.rtx.ssrcs.resize(1); |
| } |
| } |
| } |
| |
| if (webrtc::RtpExtension::FindHeaderExtensionByUri( |
| config.rtp.extensions, webrtc::RtpExtension::kCorruptionDetectionUri, |
| webrtc::RtpExtension::kRequireEncryptedExtension)) { |
| config.encoder_settings.enable_frame_instrumentation_generator = true; |
| } |
| |
| stream_ = call_->CreateVideoSendStream(std::move(config), |
| parameters_.encoder_config.Copy()); |
| |
| parameters_.encoder_config.encoder_specific_settings = NULL; |
| |
| // Calls stream_->StartPerRtpStream() to start the VideoSendStream |
| // if necessary conditions are met. |
| UpdateSendState(); |
| |
| // Attach the source after starting the send stream to prevent frames from |
| // being injected into a not-yet initializated video stream encoder. |
| if (source_) { |
| stream_->SetSource(source_, GetDegradationPreference()); |
| } |
| } |
| |
| void WebRtcVideoSendChannel::WebRtcVideoSendStream::GenerateKeyFrame( |
| const std::vector<std::string>& rids) { |
| RTC_DCHECK_RUN_ON(&thread_checker_); |
| if (stream_ != NULL) { |
| stream_->GenerateKeyFrame(rids); |
| } else { |
| RTC_LOG(LS_WARNING) |
| << "Absent send stream; ignoring request to generate keyframe."; |
| } |
| } |
| |
| void WebRtcVideoSendChannel::GenerateSendKeyFrame( |
| uint32_t ssrc, |
| const std::vector<std::string>& rids) { |
| RTC_DCHECK_RUN_ON(&thread_checker_); |
| auto it = send_streams_.find(ssrc); |
| if (it != send_streams_.end()) { |
| it->second->GenerateKeyFrame(rids); |
| } else { |
| RTC_LOG(LS_ERROR) |
| << "Absent send stream; ignoring key frame generation for ssrc " |
| << ssrc; |
| } |
| } |
| |
| void WebRtcVideoSendChannel::SetEncoderToPacketizerFrameTransformer( |
| uint32_t ssrc, |
| rtc::scoped_refptr<webrtc::FrameTransformerInterface> frame_transformer) { |
| RTC_DCHECK_RUN_ON(&thread_checker_); |
| auto matching_stream = send_streams_.find(ssrc); |
| if (matching_stream != send_streams_.end()) { |
| matching_stream->second->SetEncoderToPacketizerFrameTransformer( |
| std::move(frame_transformer)); |
| } |
| } |
| |
| // ------------------------ WebRtcVideoReceiveChannel --------------------- |
| WebRtcVideoReceiveChannel::WebRtcVideoReceiveChannel( |
| webrtc::Call* call, |
| const MediaConfig& config, |
| const VideoOptions& options, |
| const webrtc::CryptoOptions& crypto_options, |
| webrtc::VideoDecoderFactory* decoder_factory) |
| : MediaChannelUtil(call->network_thread(), config.enable_dscp), |
| worker_thread_(call->worker_thread()), |
| receiving_(false), |
| call_(call), |
| default_sink_(nullptr), |
| video_config_(config.video), |
| decoder_factory_(decoder_factory), |
| default_send_options_(options), |
| last_receive_stats_log_ms_(-1), |
| discard_unknown_ssrc_packets_( |
| IsEnabled(call_->trials(), |
| "WebRTC-Video-DiscardPacketsWithUnknownSsrc")), |
| crypto_options_(crypto_options), |
| receive_buffer_size_(ParseReceiveBufferSize(call_->trials())) { |
| RTC_DCHECK_RUN_ON(&thread_checker_); |
| rtcp_receiver_report_ssrc_ = kDefaultRtcpReceiverReportSsrc; |
| recv_codecs_ = MapCodecs(GetPayloadTypesAndDefaultCodecs( |
| decoder_factory_, /*is_decoder_factory=*/true, |
| /*include_rtx=*/true, call_->trials())); |
| recv_flexfec_payload_type_ = |
| recv_codecs_.empty() ? 0 : recv_codecs_.front().flexfec_payload_type; |
| } |
| |
| WebRtcVideoReceiveChannel::~WebRtcVideoReceiveChannel() { |
| for (auto& kv : receive_streams_) |
| delete kv.second; |
| } |
| |
| void WebRtcVideoReceiveChannel::SetReceiverFeedbackParameters( |
| bool lntf_enabled, |
| bool nack_enabled, |
| webrtc::RtcpMode rtcp_mode, |
| std::optional<int> rtx_time) { |
| RTC_DCHECK_RUN_ON(&thread_checker_); |
| |
| // Update receive feedback parameters from new codec or RTCP mode. |
| for (auto& kv : receive_streams_) { |
| RTC_DCHECK(kv.second != nullptr); |
| kv.second->SetFeedbackParameters(lntf_enabled, nack_enabled, rtcp_mode, |
| rtx_time); |
| } |
| // Store for future creation of receive streams |
| rtp_config_.lntf.enabled = lntf_enabled; |
| if (nack_enabled) { |
| rtp_config_.nack.rtp_history_ms = kNackHistoryMs; |
| } else { |
| rtp_config_.nack.rtp_history_ms = 0; |
| } |
| rtp_config_.rtcp_mode = rtcp_mode; |
| // Note: There is no place in config to store rtx_time. |
| } |
| |
| webrtc::RtpParameters WebRtcVideoReceiveChannel::GetRtpReceiverParameters( |
| uint32_t ssrc) const { |
| RTC_DCHECK_RUN_ON(&thread_checker_); |
| webrtc::RtpParameters rtp_params; |
| auto it = receive_streams_.find(ssrc); |
| if (it == receive_streams_.end()) { |
| RTC_LOG(LS_WARNING) |
| << "Attempting to get RTP receive parameters for stream " |
| "with SSRC " |
| << ssrc << " which doesn't exist."; |
| return webrtc::RtpParameters(); |
| } |
| rtp_params = it->second->GetRtpParameters(); |
| rtp_params.header_extensions = recv_rtp_extensions_; |
| |
| // Add codecs, which any stream is prepared to receive. |
| for (const Codec& codec : recv_params_.codecs) { |
| rtp_params.codecs.push_back(codec.ToCodecParameters()); |
| } |
| |
| return rtp_params; |
| } |
| |
| webrtc::RtpParameters |
| WebRtcVideoReceiveChannel::GetDefaultRtpReceiveParameters() const { |
| RTC_DCHECK_RUN_ON(&thread_checker_); |
| webrtc::RtpParameters rtp_params; |
| if (!default_sink_) { |
| // Getting parameters on a default, unsignaled video receive stream but |
| // because we've not configured to receive such a stream, `encodings` is |
| // empty. |
| return rtp_params; |
| } |
| rtp_params.encodings.emplace_back(); |
| |
| // Add codecs, which any stream is prepared to receive. |
| for (const Codec& codec : recv_params_.codecs) { |
| rtp_params.codecs.push_back(codec.ToCodecParameters()); |
| } |
| |
| return rtp_params; |
| } |
| |
| bool WebRtcVideoReceiveChannel::GetChangedReceiverParameters( |
| const VideoReceiverParameters& params, |
| ChangedReceiverParameters* changed_params) const { |
| if (!ValidateCodecFormats(params.codecs) || |
| !ValidateRtpExtensions(params.extensions, recv_rtp_extensions_)) { |
| return false; |
| } |
| |
| // Handle receive codecs. |
| const std::vector<VideoCodecSettings> mapped_codecs = |
| MapCodecs(params.codecs); |
| if (mapped_codecs.empty()) { |
| RTC_LOG(LS_ERROR) |
| << "GetChangedReceiverParameters called without any video codecs."; |
| return false; |
| } |
| |
| // Verify that every mapped codec is supported locally. |
| if (params.is_stream_active) { |
| const std::vector<Codec> local_supported_codecs = |
| GetPayloadTypesAndDefaultCodecs(decoder_factory_, |
| /*is_decoder_factory=*/true, |
| /*include_rtx=*/true, call_->trials()); |
| for (const VideoCodecSettings& mapped_codec : mapped_codecs) { |
| if (!FindMatchingVideoCodec(local_supported_codecs, mapped_codec.codec)) { |
| RTC_LOG(LS_ERROR) << "GetChangedReceiverParameters called with " |
| "unsupported video codec: " |
| << mapped_codec.codec.ToString(); |
| return false; |
| } |
| } |
| } |
| |
| if (NonFlexfecReceiveCodecsHaveChanged(recv_codecs_, mapped_codecs)) { |
| changed_params->codec_settings = |
| std::optional<std::vector<VideoCodecSettings>>(mapped_codecs); |
| } |
| |
| // Handle RTP header extensions. |
| std::vector<webrtc::RtpExtension> filtered_extensions = FilterRtpExtensions( |
| params.extensions, webrtc::RtpExtension::IsSupportedForVideo, false, |
| call_->trials()); |
| if (filtered_extensions != recv_rtp_extensions_) { |
| changed_params->rtp_header_extensions = |
| std::optional<std::vector<webrtc::RtpExtension>>(filtered_extensions); |
| } |
| |
| int flexfec_payload_type = mapped_codecs.front().flexfec_payload_type; |
| if (flexfec_payload_type != recv_flexfec_payload_type_) { |
| changed_params->flexfec_payload_type = flexfec_payload_type; |
| } |
| |
| return true; |
| } |
| |
| bool WebRtcVideoReceiveChannel::SetReceiverParameters( |
| const VideoReceiverParameters& params) { |
| RTC_DCHECK_RUN_ON(&thread_checker_); |
| TRACE_EVENT0("webrtc", "WebRtcVideoReceiveChannel::SetReceiverParameters"); |
| RTC_LOG(LS_INFO) << "SetReceiverParameters: " << params.ToString(); |
| ChangedReceiverParameters changed_params; |
| if (!GetChangedReceiverParameters(params, &changed_params)) { |
| return false; |
| } |
| if (changed_params.flexfec_payload_type) { |
| RTC_DLOG(LS_INFO) << "Changing FlexFEC payload type (recv) from " |
| << recv_flexfec_payload_type_ << " to " |
| << *changed_params.flexfec_payload_type; |
| recv_flexfec_payload_type_ = *changed_params.flexfec_payload_type; |
| } |
| if (changed_params.rtp_header_extensions) { |
| recv_rtp_extensions_ = *changed_params.rtp_header_extensions; |
| recv_rtp_extension_map_ = |
| webrtc::RtpHeaderExtensionMap(recv_rtp_extensions_); |
| } |
| if (changed_params.codec_settings) { |
| RTC_DLOG(LS_INFO) << "Changing recv codecs from " |
| << CodecSettingsVectorToString(recv_codecs_) << " to " |
| << CodecSettingsVectorToString( |
| *changed_params.codec_settings); |
| recv_codecs_ = *changed_params.codec_settings; |
| } |
| |
| for (auto& kv : receive_streams_) { |
| kv.second->SetReceiverParameters(changed_params); |
| } |
| recv_params_ = params; |
| return true; |
| } |
| |
| void WebRtcVideoReceiveChannel::SetReceiverReportSsrc(uint32_t ssrc) { |
| RTC_DCHECK_RUN_ON(&thread_checker_); |
| if (ssrc == rtcp_receiver_report_ssrc_) |
| return; |
| |
| rtcp_receiver_report_ssrc_ = ssrc; |
| for (auto& [unused, receive_stream] : receive_streams_) |
| receive_stream->SetLocalSsrc(ssrc); |
| } |
| |
| void WebRtcVideoReceiveChannel::ChooseReceiverReportSsrc( |
| const std::set<uint32_t>& choices) { |
| RTC_DCHECK_RUN_ON(&thread_checker_); |
| // If we can continue using the current receiver report, do so. |
| if (choices.find(rtcp_receiver_report_ssrc_) != choices.end()) { |
| return; |
| } |
| // Go back to the default if list has been emptied. |
| if (choices.empty()) { |
| SetReceiverReportSsrc(kDefaultRtcpReceiverReportSsrc); |
| return; |
| } |
| // Any number is as good as any other. |
| SetReceiverReportSsrc(*choices.begin()); |
| } |
| |
| void WebRtcVideoReceiveChannel::SetReceive(bool receive) { |
| RTC_DCHECK_RUN_ON(&thread_checker_); |
| TRACE_EVENT0("webrtc", "WebRtcVideoReceiveChannel::SetReceive"); |
| RTC_LOG(LS_VERBOSE) << "SetReceive: " << (receive ? "true" : "false"); |
| for (const auto& kv : receive_streams_) { |
| if (receive) { |
| kv.second->StartReceiveStream(); |
| } else { |
| kv.second->StopReceiveStream(); |
| } |
| } |
| receiving_ = receive; |
| } |
| |
| bool WebRtcVideoReceiveChannel::ValidateReceiveSsrcAvailability( |
| const StreamParams& sp) const { |
| for (uint32_t ssrc : sp.ssrcs) { |
| if (receive_ssrcs_.find(ssrc) != receive_ssrcs_.end()) { |
| RTC_LOG(LS_ERROR) << "Receive stream with SSRC '" << ssrc |
| << "' already exists."; |
| return false; |
| } |
| } |
| return true; |
| } |
| |
| void WebRtcVideoReceiveChannel::DeleteReceiveStream( |
| WebRtcVideoReceiveStream* stream) { |
| for (uint32_t old_ssrc : stream->GetSsrcs()) |
| receive_ssrcs_.erase(old_ssrc); |
| delete stream; |
| } |
| |
| bool WebRtcVideoReceiveChannel::AddRecvStream(const StreamParams& sp) { |
| return AddRecvStream(sp, false); |
| } |
| |
| bool WebRtcVideoReceiveChannel::AddRecvStream(const StreamParams& sp, |
| bool default_stream) { |
| RTC_DCHECK_RUN_ON(&thread_checker_); |
| |
| RTC_LOG(LS_INFO) << "AddRecvStream" |
| << (default_stream ? " (default stream)" : "") << ": " |
| << sp.ToString(); |
| if (!sp.has_ssrcs()) { |
| // This is a StreamParam with unsignaled SSRCs. Store it, so it can be used |
| // later when we know the SSRC on the first packet arrival. |
| unsignaled_stream_params_ = sp; |
| return true; |
| } |
| |
| if (!ValidateStreamParams(sp)) |
| return false; |
| |
| for (uint32_t ssrc : sp.ssrcs) { |
| // Remove running stream if this was a default stream. |
| const auto& prev_stream = receive_streams_.find(ssrc); |
| if (prev_stream != receive_streams_.end()) { |
| if (default_stream || !prev_stream->second->IsDefaultStream()) { |
| RTC_LOG(LS_ERROR) << "Receive stream for SSRC '" << ssrc |
| << "' already exists."; |
| return false; |
| } |
| DeleteReceiveStream(prev_stream->second); |
| receive_streams_.erase(prev_stream); |
| } |
| } |
| |
| if (!ValidateReceiveSsrcAvailability(sp)) |
| return false; |
| |
| for (uint32_t used_ssrc : sp.ssrcs) |
| receive_ssrcs_.insert(used_ssrc); |
| |
| webrtc::VideoReceiveStreamInterface::Config config(transport(), |
| decoder_factory_); |
| webrtc::FlexfecReceiveStream::Config flexfec_config(transport()); |
| ConfigureReceiverRtp(&config, &flexfec_config, sp); |
| |
| config.crypto_options = crypto_options_; |
| config.enable_prerenderer_smoothing = |
| video_config_.enable_prerenderer_smoothing; |
| if (!sp.stream_ids().empty()) { |
| config.sync_group = sp.stream_ids()[0]; |
| } |
| |
| if (unsignaled_frame_transformer_ && !config.frame_transformer) |
| config.frame_transformer = unsignaled_frame_transformer_; |
| |
| auto receive_stream = |
| new WebRtcVideoReceiveStream(call_, sp, std::move(config), default_stream, |
| recv_codecs_, flexfec_config); |
| if (receiving_) { |
| receive_stream->StartReceiveStream(); |
| } |
| receive_streams_[sp.first_ssrc()] = receive_stream; |
| return true; |
| } |
| |
| void WebRtcVideoReceiveChannel::ConfigureReceiverRtp( |
| webrtc::VideoReceiveStreamInterface::Config* config, |
| webrtc::FlexfecReceiveStream::Config* flexfec_config, |
| const StreamParams& sp) const { |
| uint32_t ssrc = sp.first_ssrc(); |
| |
| config->rtp.remote_ssrc = ssrc; |
| config->rtp.local_ssrc = rtcp_receiver_report_ssrc_; |
| |
| // TODO(pbos): This protection is against setting the same local ssrc as |
| // remote which is not permitted by the lower-level API. RTCP requires a |
| // corresponding sender SSRC. Figure out what to do when we don't have |
| // (receive-only) or know a good local SSRC. |
| if (config->rtp.remote_ssrc == config->rtp.local_ssrc) { |
| if (config->rtp.local_ssrc != kDefaultRtcpReceiverReportSsrc) { |
| config->rtp.local_ssrc = kDefaultRtcpReceiverReportSsrc; |
| } else { |
| config->rtp.local_ssrc = kDefaultRtcpReceiverReportSsrc + 1; |
| } |
| } |
| |
| // The mode and rtx time is determined by a call to the configuration |
| // function. |
| config->rtp.rtcp_mode = rtp_config_.rtcp_mode; |
| |
| sp.GetFidSsrc(ssrc, &config->rtp.rtx_ssrc); |
| |
| // TODO(brandtr): Generalize when we add support for multistream protection. |
| flexfec_config->payload_type = recv_flexfec_payload_type_; |
| if (!IsDisabled(call_->trials(), "WebRTC-FlexFEC-03-Advertised") && |
| sp.GetFecFrSsrc(ssrc, &flexfec_config->rtp.remote_ssrc)) { |
| flexfec_config->protected_media_ssrcs = {ssrc}; |
| flexfec_config->rtp.local_ssrc = config->rtp.local_ssrc; |
| flexfec_config->rtcp_mode = config->rtp.rtcp_mode; |
| } |
| } |
| |
| bool WebRtcVideoReceiveChannel::RemoveRecvStream(uint32_t ssrc) { |
| RTC_DCHECK_RUN_ON(&thread_checker_); |
| RTC_LOG(LS_INFO) << "RemoveRecvStream: " << ssrc; |
| |
| auto stream = receive_streams_.find(ssrc); |
| if (stream == receive_streams_.end()) { |
| RTC_LOG(LS_ERROR) << "Stream not found for ssrc: " << ssrc; |
| return false; |
| } |
| DeleteReceiveStream(stream->second); |
| receive_streams_.erase(stream); |
| |
| return true; |
| } |
| |
| void WebRtcVideoReceiveChannel::ResetUnsignaledRecvStream() { |
| RTC_DCHECK_RUN_ON(&thread_checker_); |
| RTC_LOG(LS_INFO) << "ResetUnsignaledRecvStream."; |
| unsignaled_stream_params_ = StreamParams(); |
| last_unsignalled_ssrc_creation_time_ms_ = std::nullopt; |
| |
| // Delete any created default streams. This is needed to avoid SSRC collisions |
| // in Call's RtpDemuxer, in the case that `this` has created a default video |
| // receiver, and then some other WebRtcVideoReceiveChannel gets the SSRC |
| // signaled in the corresponding Unified Plan "m=" section. |
| auto it = receive_streams_.begin(); |
| while (it != receive_streams_.end()) { |
| if (it->second->IsDefaultStream()) { |
| DeleteReceiveStream(it->second); |
| receive_streams_.erase(it++); |
| } else { |
| ++it; |
| } |
| } |
| } |
| |
| std::optional<uint32_t> WebRtcVideoReceiveChannel::GetUnsignaledSsrc() const { |
| RTC_DCHECK_RUN_ON(&thread_checker_); |
| std::optional<uint32_t> ssrc; |
| for (auto it = receive_streams_.begin(); it != receive_streams_.end(); ++it) { |
| if (it->second->IsDefaultStream()) { |
| ssrc.emplace(it->first); |
| break; |
| } |
| } |
| return ssrc; |
| } |
| |
| void WebRtcVideoReceiveChannel::OnDemuxerCriteriaUpdatePending() { |
| RTC_DCHECK_RUN_ON(&thread_checker_); |
| ++demuxer_criteria_id_; |
| } |
| |
| void WebRtcVideoReceiveChannel::OnDemuxerCriteriaUpdateComplete() { |
| RTC_DCHECK_RUN_ON(&thread_checker_); |
| ++demuxer_criteria_completed_id_; |
| } |
| |
| bool WebRtcVideoReceiveChannel::SetSink( |
| uint32_t ssrc, |
| rtc::VideoSinkInterface<webrtc::VideoFrame>* sink) { |
| RTC_DCHECK_RUN_ON(&thread_checker_); |
| RTC_LOG(LS_INFO) << "SetSink: ssrc:" << ssrc << " " |
| << (sink ? "(ptr)" : "nullptr"); |
| |
| auto it = receive_streams_.find(ssrc); |
| if (it == receive_streams_.end()) { |
| return false; |
| } |
| |
| it->second->SetSink(sink); |
| return true; |
| } |
| |
| void WebRtcVideoReceiveChannel::SetDefaultSink( |
| rtc::VideoSinkInterface<webrtc::VideoFrame>* sink) { |
| RTC_DCHECK_RUN_ON(&thread_checker_); |
| RTC_LOG(LS_INFO) << "SetDefaultSink: " << (sink ? "(ptr)" : "nullptr"); |
| default_sink_ = sink; |
| } |
| |
| bool WebRtcVideoReceiveChannel::GetStats(VideoMediaReceiveInfo* info) { |
| RTC_DCHECK_RUN_ON(&thread_checker_); |
| TRACE_EVENT0("webrtc", "WebRtcVideoReceiveChannel::GetStats"); |
| |
| info->Clear(); |
| if (receive_streams_.empty()) { |
| return true; |
| } |
| |
| // Log stats periodically. |
| bool log_stats = false; |
| int64_t now_ms = rtc::TimeMillis(); |
| if (last_receive_stats_log_ms_ == -1 || |
| now_ms - last_receive_stats_log_ms_ > kStatsLogIntervalMs) { |
| last_receive_stats_log_ms_ = now_ms; |
| log_stats = true; |
| } |
| |
| FillReceiverStats(info, log_stats); |
| FillReceiveCodecStats(info); |
| |
| return true; |
| } |
| |
| void WebRtcVideoReceiveChannel::FillReceiverStats( |
| VideoMediaReceiveInfo* video_media_info, |
| bool log_stats) { |
| for (const auto& it : receive_streams_) { |
| video_media_info->receivers.push_back( |
| it.second->GetVideoReceiverInfo(log_stats)); |
| } |
| } |
| |
| void WebRtcVideoReceiveChannel::FillReceiveCodecStats( |
| VideoMediaReceiveInfo* video_media_info) { |
| for (const auto& receiver : video_media_info->receivers) { |
| auto codec = |
| absl::c_find_if(recv_params_.codecs, [&receiver](const Codec& c) { |
| return receiver.codec_payload_type && |
| *receiver.codec_payload_type == c.id; |
| }); |
| if (codec != recv_params_.codecs.end()) { |
| video_media_info->receive_codecs.insert( |
| std::make_pair(codec->id, codec->ToCodecParameters())); |
| } |
| } |
| } |
| |
| void WebRtcVideoReceiveChannel::OnPacketReceived( |
| const webrtc::RtpPacketReceived& packet) { |
| // Note: the network_thread_checker may refer to the worker thread if the two |
| // threads are combined, but this is either always true or always false |
| // depending on configuration set at object initialization. |
| RTC_DCHECK_RUN_ON(&network_thread_checker_); |
| |
| // TODO(crbug.com/1373439): Stop posting to the worker thread when the |
| // combined network/worker project launches. |
| if (webrtc::TaskQueueBase::Current() != worker_thread_) { |
| worker_thread_->PostTask( |
| SafeTask(task_safety_.flag(), [this, packet = packet]() mutable { |
| RTC_DCHECK_RUN_ON(&thread_checker_); |
| ProcessReceivedPacket(std::move(packet)); |
| })); |
| } else { |
| RTC_DCHECK_RUN_ON(&thread_checker_); |
| ProcessReceivedPacket(packet); |
| } |
| } |
| |
| bool WebRtcVideoReceiveChannel::MaybeCreateDefaultReceiveStream( |
| const webrtc::RtpPacketReceived& packet) { |
| if (discard_unknown_ssrc_packets_) { |
| return false; |
| } |
| |
| if (packet.PayloadType() == recv_flexfec_payload_type_) { |
| return false; |
| } |
| |
| // Ignore unknown ssrcs if there is a demuxer criteria update pending. |
| // During a demuxer update we may receive ssrcs that were recently |
| // removed or we may receve ssrcs that were recently configured for a |
| // different video channel. |
| if (demuxer_criteria_id_ != demuxer_criteria_completed_id_) { |
| return false; |
| } |
| |
| // See if this payload_type is registered as one that usually gets its |
| // own SSRC (RTX) or at least is safe to drop either way (FEC). If it |
| // is, and it wasn't handled above by DeliverPacket, that means we don't |
| // know what stream it associates with, and we shouldn't ever create an |
| // implicit channel for these. |
| bool is_rtx_payload = false; |
| for (auto& codec : recv_codecs_) { |
| if (packet.PayloadType() == codec.ulpfec.red_rtx_payload_type || |
| packet.PayloadType() == codec.ulpfec.ulpfec_payload_type) { |
| return false; |
| } |
| |
| if (packet.PayloadType() == codec.rtx_payload_type) { |
| is_rtx_payload = true; |
| break; |
| } |
| } |
| |
| if (is_rtx_payload) { |
| // As we don't support receiving simulcast there can only be one RTX |
| // stream, which will be associated with unsignaled media stream. |
| std::optional<uint32_t> current_default_ssrc = GetUnsignaledSsrc(); |
| if (current_default_ssrc) { |
| FindReceiveStream(*current_default_ssrc)->UpdateRtxSsrc(packet.Ssrc()); |
| return true; |
| } |
| // Default media SSRC not known yet. Drop the packet. |
| // BWE has already been notified of this received packet. |
| return false; |
| } |
| // Ignore unknown ssrcs if we recently created an unsignalled receive |
| // stream since this shouldn't happen frequently. Getting into a state |
| // of creating decoders on every packet eats up processing time (e.g. |
| // https://crbug.com/1069603) and this cooldown prevents that. |
| if (last_unsignalled_ssrc_creation_time_ms_.has_value()) { |
| int64_t now_ms = rtc::TimeMillis(); |
| if (now_ms - last_unsignalled_ssrc_creation_time_ms_.value() < |
| kUnsignaledSsrcCooldownMs) { |
| // We've already created an unsignalled ssrc stream within the last |
| // 0.5 s, ignore with a warning. |
| RTC_LOG(LS_WARNING) |
| << "Another unsignalled ssrc packet arrived shortly after the " |
| << "creation of an unsignalled ssrc stream. Dropping packet."; |
| return false; |
| } |
| } |
| |
| // RTX SSRC not yet known. |
| ReCreateDefaultReceiveStream(packet.Ssrc(), std::nullopt); |
| last_unsignalled_ssrc_creation_time_ms_ = rtc::TimeMillis(); |
| return true; |
| } |
| |
| void WebRtcVideoReceiveChannel::ReCreateDefaultReceiveStream( |
| uint32_t ssrc, |
| std::optional<uint32_t> rtx_ssrc) { |
| RTC_DCHECK_RUN_ON(&thread_checker_); |
| |
| std::optional<uint32_t> default_recv_ssrc = GetUnsignaledSsrc(); |
| if (default_recv_ssrc) { |
| RTC_LOG(LS_INFO) << "Destroying old default receive stream for SSRC=" |
| << ssrc << "."; |
| RemoveRecvStream(*default_recv_ssrc); |
| } |
| |
| StreamParams sp = unsignaled_stream_params(); |
| sp.ssrcs.push_back(ssrc); |
| if (rtx_ssrc) { |
| sp.AddFidSsrc(ssrc, *rtx_ssrc); |
| } |
| RTC_LOG(LS_INFO) << "Creating default receive stream for SSRC=" << ssrc |
| << "."; |
| if (!AddRecvStream(sp, /*default_stream=*/true)) { |
| RTC_LOG(LS_WARNING) << "Could not create default receive stream."; |
| } |
| |
| // SSRC 0 returns default_recv_base_minimum_delay_ms. |
| const int unsignaled_ssrc = 0; |
| int default_recv_base_minimum_delay_ms = |
| GetBaseMinimumPlayoutDelayMs(unsignaled_ssrc).value_or(0); |
| // Set base minimum delay if it was set before for the default receive |
| // stream. |
| SetBaseMinimumPlayoutDelayMs(ssrc, default_recv_base_minimum_delay_ms); |
| SetSink(ssrc, default_sink_); |
| } |
| |
| void WebRtcVideoReceiveChannel::SetInterface( |
| MediaChannelNetworkInterface* iface) { |
| RTC_DCHECK_RUN_ON(&network_thread_checker_); |
| MediaChannelUtil::SetInterface(iface); |
| // Set the RTP recv/send buffer to a bigger size. |
| MediaChannelUtil::SetOption(MediaChannelNetworkInterface::ST_RTP, |
| rtc::Socket::OPT_RCVBUF, receive_buffer_size_); |
| } |
| |
| void WebRtcVideoReceiveChannel::SetFrameDecryptor( |
| uint32_t ssrc, |
| rtc::scoped_refptr<webrtc::FrameDecryptorInterface> frame_decryptor) { |
| RTC_DCHECK_RUN_ON(&thread_checker_); |
| auto matching_stream = receive_streams_.find(ssrc); |
| if (matching_stream != receive_streams_.end()) { |
| matching_stream->second->SetFrameDecryptor(frame_decryptor); |
| } |
| } |
| |
| bool WebRtcVideoReceiveChannel::SetBaseMinimumPlayoutDelayMs(uint32_t ssrc, |
| int delay_ms) { |
| RTC_DCHECK_RUN_ON(&thread_checker_); |
| std::optional<uint32_t> default_ssrc = GetUnsignaledSsrc(); |
| |
| // SSRC of 0 represents the default receive stream. |
| if (ssrc == 0) { |
| default_recv_base_minimum_delay_ms_ = delay_ms; |
| } |
| |
| if (ssrc == 0 && !default_ssrc) { |
| return true; |
| } |
| |
| if (ssrc == 0 && default_ssrc) { |
| ssrc = default_ssrc.value(); |
| } |
| |
| auto stream = receive_streams_.find(ssrc); |
| if (stream != receive_streams_.end()) { |
| stream->second->SetBaseMinimumPlayoutDelayMs(delay_ms); |
| return true; |
| } else { |
| RTC_LOG(LS_ERROR) << "No stream found to set base minimum playout delay"; |
| return false; |
| } |
| } |
| |
| std::optional<int> WebRtcVideoReceiveChannel::GetBaseMinimumPlayoutDelayMs( |
| uint32_t ssrc) const { |
| RTC_DCHECK_RUN_ON(&thread_checker_); |
| // SSRC of 0 represents the default receive stream. |
| if (ssrc == 0) { |
| return default_recv_base_minimum_delay_ms_; |
| } |
| |
| auto stream = receive_streams_.find(ssrc); |
| if (stream != receive_streams_.end()) { |
| return stream->second->GetBaseMinimumPlayoutDelayMs(); |
| } else { |
| RTC_LOG(LS_ERROR) << "No stream found to get base minimum playout delay"; |
| return std::nullopt; |
| } |
| } |
| |
| std::vector<webrtc::RtpSource> WebRtcVideoReceiveChannel::GetSources( |
| uint32_t ssrc) const { |
| RTC_DCHECK_RUN_ON(&thread_checker_); |
| auto it = receive_streams_.find(ssrc); |
| if (it == receive_streams_.end()) { |
| // TODO(bugs.webrtc.org/9781): Investigate standard compliance |
| // with sources for streams that has been removed. |
| RTC_LOG(LS_ERROR) << "Attempting to get contributing sources for SSRC:" |
| << ssrc << " which doesn't exist."; |
| return {}; |
| } |
| return it->second->GetSources(); |
| } |
| |
| WebRtcVideoReceiveChannel::WebRtcVideoReceiveStream::WebRtcVideoReceiveStream( |
| webrtc::Call* call, |
| const StreamParams& sp, |
| webrtc::VideoReceiveStreamInterface::Config config, |
| bool default_stream, |
| const std::vector<VideoCodecSettings>& recv_codecs, |
| const webrtc::FlexfecReceiveStream::Config& flexfec_config) |
| : call_(call), |
| stream_params_(sp), |
| stream_(NULL), |
| default_stream_(default_stream), |
| config_(std::move(config)), |
| flexfec_config_(flexfec_config), |
| flexfec_stream_(nullptr), |
| sink_(NULL), |
| first_frame_timestamp_(-1), |
| estimated_remote_start_ntp_time_ms_(0), |
| receiving_(false) { |
| RTC_DCHECK(config_.decoder_factory); |
| RTC_DCHECK(config_.decoders.empty()) |
| << "Decoder info is supplied via `recv_codecs`"; |
| |
| ExtractCodecInformation(recv_codecs, config_.rtp.rtx_associated_payload_types, |
| config_.rtp.raw_payload_types, config_.decoders); |
| const VideoCodecSettings& codec = recv_codecs.front(); |
| config_.rtp.ulpfec_payload_type = codec.ulpfec.ulpfec_payload_type; |
| config_.rtp.red_payload_type = codec.ulpfec.red_payload_type; |
| config_.rtp.lntf.enabled = HasLntf(codec.codec); |
| config_.rtp.nack.rtp_history_ms = HasNack(codec.codec) ? kNackHistoryMs : 0; |
| if (codec.rtx_time && config_.rtp.nack.rtp_history_ms != 0) { |
| config_.rtp.nack.rtp_history_ms = *codec.rtx_time; |
| } |
| |
| config_.rtp.rtcp_xr.receiver_reference_time_report = HasRrtr(codec.codec); |
| |
| if (codec.ulpfec.red_rtx_payload_type != -1) { |
| config_.rtp |
| .rtx_associated_payload_types[codec.ulpfec.red_rtx_payload_type] = |
| codec.ulpfec.red_payload_type; |
| } |
| |
| config_.renderer = this; |
| flexfec_config_.payload_type = flexfec_config.payload_type; |
| |
| CreateReceiveStream(); |
| } |
| |
| WebRtcVideoReceiveChannel::WebRtcVideoReceiveStream:: |
| ~WebRtcVideoReceiveStream() { |
| call_->DestroyVideoReceiveStream(stream_); |
| if (flexfec_stream_) |
| call_->DestroyFlexfecReceiveStream(flexfec_stream_); |
| } |
| |
| webrtc::VideoReceiveStreamInterface& |
| WebRtcVideoReceiveChannel::WebRtcVideoReceiveStream::stream() { |
| RTC_DCHECK(stream_); |
| return *stream_; |
| } |
| |
| webrtc::FlexfecReceiveStream* |
| WebRtcVideoReceiveChannel::WebRtcVideoReceiveStream::flexfec_stream() { |
| return flexfec_stream_; |
| } |
| |
| const std::vector<uint32_t>& |
| WebRtcVideoReceiveChannel::WebRtcVideoReceiveStream::GetSsrcs() const { |
| return stream_params_.ssrcs; |
| } |
| |
| std::vector<webrtc::RtpSource> |
| WebRtcVideoReceiveChannel::WebRtcVideoReceiveStream::GetSources() { |
| RTC_DCHECK(stream_); |
| return stream_->GetSources(); |
| } |
| |
| webrtc::RtpParameters |
| WebRtcVideoReceiveChannel::WebRtcVideoReceiveStream::GetRtpParameters() const { |
| webrtc::RtpParameters rtp_parameters; |
| |
| std::vector<uint32_t> primary_ssrcs; |
| stream_params_.GetPrimarySsrcs(&primary_ssrcs); |
| for (uint32_t ssrc : primary_ssrcs) { |
| rtp_parameters.encodings.emplace_back(); |
| rtp_parameters.encodings.back().ssrc = ssrc; |
| } |
| |
| rtp_parameters.rtcp.reduced_size = |
| config_.rtp.rtcp_mode == webrtc::RtcpMode::kReducedSize; |
| |
| return rtp_parameters; |
| } |
| |
| bool WebRtcVideoReceiveChannel::WebRtcVideoReceiveStream::ReconfigureCodecs( |
| const std::vector<VideoCodecSettings>& recv_codecs) { |
| RTC_DCHECK(stream_); |
| RTC_DCHECK(!recv_codecs.empty()); |
| |
| std::map<int, int> rtx_associated_payload_types; |
| std::set<int> raw_payload_types; |
| std::vector<webrtc::VideoReceiveStreamInterface::Decoder> decoders; |
| ExtractCodecInformation(recv_codecs, rtx_associated_payload_types, |
| raw_payload_types, decoders); |
| |
| const auto& codec = recv_codecs.front(); |
| |
| if (config_.rtp.red_payload_type != codec.ulpfec.red_payload_type || |
| config_.rtp.ulpfec_payload_type != codec.ulpfec.ulpfec_payload_type) { |
| config_.rtp.ulpfec_payload_type = codec.ulpfec.ulpfec_payload_type; |
| config_.rtp.red_payload_type = codec.ulpfec.red_payload_type; |
| stream_->SetProtectionPayloadTypes(config_.rtp.red_payload_type, |
| config_.rtp.ulpfec_payload_type); |
| } |
| |
| const bool has_lntf = HasLntf(codec.codec); |
| if (config_.rtp.lntf.enabled != has_lntf) { |
| config_.rtp.lntf.enabled = has_lntf; |
| stream_->SetLossNotificationEnabled(has_lntf); |
| } |
| |
| int new_history_ms = config_.rtp.nack.rtp_history_ms; |
| const int rtp_history_ms = HasNack(codec.codec) ? kNackHistoryMs : 0; |
| if (rtp_history_ms != config_.rtp.nack.rtp_history_ms) { |
| new_history_ms = rtp_history_ms; |
| } |
| |
| // The rtx-time parameter can be used to override the hardcoded default for |
| // the NACK buffer length. |
| if (codec.rtx_time && new_history_ms != 0) { |
| new_history_ms = *codec.rtx_time; |
| } |
| |
| if (config_.rtp.nack.rtp_history_ms != new_history_ms) { |
| config_.rtp.nack.rtp_history_ms = new_history_ms; |
| stream_->SetNackHistory(webrtc::TimeDelta::Millis(new_history_ms)); |
| } |
| |
| const bool has_rtr = HasRrtr(codec.codec); |
| if (has_rtr != config_.rtp.rtcp_xr.receiver_reference_time_report) { |
| config_.rtp.rtcp_xr.receiver_reference_time_report = has_rtr; |
| stream_->SetRtcpXr(config_.rtp.rtcp_xr); |
| } |
| |
| if (codec.ulpfec.red_rtx_payload_type != -1) { |
| rtx_associated_payload_types[codec.ulpfec.red_rtx_payload_type] = |
| codec.ulpfec.red_payload_type; |
| } |
| |
| if (config_.rtp.rtx_associated_payload_types != |
| rtx_associated_payload_types) { |
| stream_->SetAssociatedPayloadTypes(rtx_associated_payload_types); |
| rtx_associated_payload_types.swap(config_.rtp.rtx_associated_payload_types); |
| } |
| |
| bool recreate_needed = false; |
| |
| if (raw_payload_types != config_.rtp.raw_payload_types) { |
| raw_payload_types.swap(config_.rtp.raw_payload_types); |
| recreate_needed = true; |
| } |
| |
| if (decoders != config_.decoders) { |
| decoders.swap(config_.decoders); |
| recreate_needed = true; |
| } |
| |
| return recreate_needed; |
| } |
| |
| void WebRtcVideoReceiveChannel::WebRtcVideoReceiveStream::SetFeedbackParameters( |
| bool lntf_enabled, |
| bool nack_enabled, |
| webrtc::RtcpMode rtcp_mode, |
| std::optional<int> rtx_time) { |
| RTC_DCHECK(stream_); |
| |
| if (config_.rtp.rtcp_mode != rtcp_mode) { |
| config_.rtp.rtcp_mode = rtcp_mode; |
| stream_->SetRtcpMode(rtcp_mode); |
| |
| flexfec_config_.rtcp_mode = rtcp_mode; |
| if (flexfec_stream_) { |
| flexfec_stream_->SetRtcpMode(rtcp_mode); |
| } |
| } |
| |
| config_.rtp.lntf.enabled = lntf_enabled; |
| stream_->SetLossNotificationEnabled(lntf_enabled); |
| |
| int nack_history_ms = nack_enabled ? rtx_time.value_or(kNackHistoryMs) : 0; |
| config_.rtp.nack.rtp_history_ms = nack_history_ms; |
| stream_->SetNackHistory(webrtc::TimeDelta::Millis(nack_history_ms)); |
| } |
| |
| void WebRtcVideoReceiveChannel::WebRtcVideoReceiveStream::SetFlexFecPayload( |
| int payload_type) { |
| // TODO(bugs.webrtc.org/11993, tommi): See if it is better to always have a |
| // flexfec stream object around and instead of recreating the video stream, |
| // reconfigure the flexfec object from within the rtp callback (soon to be on |
| // the network thread). |
| if (flexfec_stream_) { |
| if (flexfec_stream_->payload_type() == payload_type) { |
| RTC_DCHECK_EQ(flexfec_config_.payload_type, payload_type); |
| return; |
| } |
| |
| flexfec_config_.payload_type = payload_type; |
| flexfec_stream_->SetPayloadType(payload_type); |
| |
| if (payload_type == -1) { |
| stream_->SetFlexFecProtection(nullptr); |
| call_->DestroyFlexfecReceiveStream(flexfec_stream_); |
| flexfec_stream_ = nullptr; |
| } |
| } else if (payload_type != -1) { |
| flexfec_config_.payload_type = payload_type; |
| if (flexfec_config_.IsCompleteAndEnabled()) { |
| flexfec_stream_ = call_->CreateFlexfecReceiveStream(flexfec_config_); |
| stream_->SetFlexFecProtection(flexfec_stream_); |
| } |
| } else { |
| // Noop. No flexfec stream exists and "new" payload_type == -1. |
| RTC_DCHECK(!flexfec_config_.IsCompleteAndEnabled()); |
| flexfec_config_.payload_type = payload_type; |
| } |
| } |
| |
| void WebRtcVideoReceiveChannel::WebRtcVideoReceiveStream::SetReceiverParameters( |
| const ChangedReceiverParameters& params) { |
| RTC_DCHECK(stream_); |
| bool video_needs_recreation = false; |
| if (params.codec_settings) { |
| video_needs_recreation = ReconfigureCodecs(*params.codec_settings); |
| } |
| |
| if (params.flexfec_payload_type) |
| SetFlexFecPayload(*params.flexfec_payload_type); |
| |
| if (video_needs_recreation) { |
| RecreateReceiveStream(); |
| } else { |
| RTC_DLOG_F(LS_INFO) << "No receive stream recreate needed."; |
| } |
| } |
| |
| void WebRtcVideoReceiveChannel::WebRtcVideoReceiveStream:: |
| RecreateReceiveStream() { |
| RTC_DCHECK_RUN_ON(&thread_checker_); |
| RTC_DCHECK(stream_); |
| std::optional<int> base_minimum_playout_delay_ms; |
| std::optional<webrtc::VideoReceiveStreamInterface::RecordingState> |
| recording_state; |
| if (stream_) { |
| base_minimum_playout_delay_ms = stream_->GetBaseMinimumPlayoutDelayMs(); |
| recording_state = stream_->SetAndGetRecordingState( |
| webrtc::VideoReceiveStreamInterface::RecordingState(), |
| /*generate_key_frame=*/false); |
| call_->DestroyVideoReceiveStream(stream_); |
| stream_ = nullptr; |
| } |
| |
| if (flexfec_stream_) { |
| call_->DestroyFlexfecReceiveStream(flexfec_stream_); |
| flexfec_stream_ = nullptr; |
| } |
| |
| CreateReceiveStream(); |
| |
| if (base_minimum_playout_delay_ms) { |
| stream_->SetBaseMinimumPlayoutDelayMs( |
| base_minimum_playout_delay_ms.value()); |
| } |
| if (recording_state) { |
| stream_->SetAndGetRecordingState(std::move(*recording_state), |
| /*generate_key_frame=*/false); |
| } |
| if (receiving_) { |
| StartReceiveStream(); |
| } |
| } |
| |
| void WebRtcVideoReceiveChannel::WebRtcVideoReceiveStream:: |
| CreateReceiveStream() { |
| RTC_DCHECK(!stream_); |
| RTC_DCHECK(!flexfec_stream_); |
| if (flexfec_config_.IsCompleteAndEnabled()) { |
| flexfec_stream_ = call_->CreateFlexfecReceiveStream(flexfec_config_); |
| } |
| |
| webrtc::VideoReceiveStreamInterface::Config config = config_.Copy(); |
| config.rtp.protected_by_flexfec = (flexfec_stream_ != nullptr); |
| config.rtp.packet_sink_ = flexfec_stream_; |
| stream_ = call_->CreateVideoReceiveStream(std::move(config)); |
| } |
| |
| void WebRtcVideoReceiveChannel::WebRtcVideoReceiveStream::StartReceiveStream() { |
| RTC_DCHECK_RUN_ON(&thread_checker_); |
| receiving_ = true; |
| stream_->Start(); |
| } |
| |
| void WebRtcVideoReceiveChannel::WebRtcVideoReceiveStream::StopReceiveStream() { |
| RTC_DCHECK_RUN_ON(&thread_checker_); |
| receiving_ = false; |
| stream_->Stop(); |
| RecreateReceiveStream(); |
| } |
| |
| void WebRtcVideoReceiveChannel::WebRtcVideoReceiveStream::OnFrame( |
| const webrtc::VideoFrame& frame) { |
| webrtc::MutexLock lock(&sink_lock_); |
| |
| int64_t time_now_ms = rtc::TimeMillis(); |
| if (first_frame_timestamp_ < 0) |
| first_frame_timestamp_ = time_now_ms; |
| int64_t elapsed_time_ms = time_now_ms - first_frame_timestamp_; |
| if (frame.ntp_time_ms() > 0) |
| estimated_remote_start_ntp_time_ms_ = frame.ntp_time_ms() - elapsed_time_ms; |
| |
| if (sink_ == NULL) { |
| RTC_LOG(LS_WARNING) |
| << "VideoReceiveStreamInterface not connected to a VideoSink."; |
| return; |
| } |
| |
| sink_->OnFrame(frame); |
| } |
| |
| bool WebRtcVideoReceiveChannel::WebRtcVideoReceiveStream::IsDefaultStream() |
| const { |
| return default_stream_; |
| } |
| |
| void WebRtcVideoReceiveChannel::WebRtcVideoReceiveStream::SetFrameDecryptor( |
| rtc::scoped_refptr<webrtc::FrameDecryptorInterface> frame_decryptor) { |
| config_.frame_decryptor = frame_decryptor; |
| if (stream_) { |
| RTC_LOG(LS_INFO) |
| << "Setting FrameDecryptor (recv) because of SetFrameDecryptor, " |
| "remote_ssrc=" |
| << config_.rtp.remote_ssrc; |
| stream_->SetFrameDecryptor(frame_decryptor); |
| } |
| } |
| |
| bool WebRtcVideoReceiveChannel::WebRtcVideoReceiveStream:: |
| SetBaseMinimumPlayoutDelayMs(int delay_ms) { |
| return stream_ ? stream_->SetBaseMinimumPlayoutDelayMs(delay_ms) : false; |
| } |
| |
| int WebRtcVideoReceiveChannel::WebRtcVideoReceiveStream:: |
| GetBaseMinimumPlayoutDelayMs() const { |
| return stream_ ? stream_->GetBaseMinimumPlayoutDelayMs() : 0; |
| } |
| |
| void WebRtcVideoReceiveChannel::WebRtcVideoReceiveStream::SetSink( |
| rtc::VideoSinkInterface<webrtc::VideoFrame>* sink) { |
| webrtc::MutexLock lock(&sink_lock_); |
| sink_ = sink; |
| } |
| |
| VideoReceiverInfo |
| WebRtcVideoReceiveChannel::WebRtcVideoReceiveStream::GetVideoReceiverInfo( |
| bool log_stats) { |
| VideoReceiverInfo info; |
| info.ssrc_groups = stream_params_.ssrc_groups; |
| info.add_ssrc(config_.rtp.remote_ssrc); |
| webrtc::VideoReceiveStreamInterface::Stats stats = stream_->GetStats(); |
| info.decoder_implementation_name = stats.decoder_implementation_name; |
| info.power_efficient_decoder = stats.power_efficient_decoder; |
| if (stats.current_payload_type != -1) { |
| info.codec_payload_type = stats.current_payload_type; |
| auto decoder_it = absl::c_find_if(config_.decoders, [&](const auto& d) { |
| return d.payload_type == stats.current_payload_type; |
| }); |
| if (decoder_it != config_.decoders.end()) |
| info.codec_name = decoder_it->video_format.name; |
| } |
| info.payload_bytes_received = stats.rtp_stats.packet_counter.payload_bytes; |
| info.header_and_padding_bytes_received = |
| stats.rtp_stats.packet_counter.header_bytes + |
| stats.rtp_stats.packet_counter.padding_bytes; |
| info.packets_received = stats.rtp_stats.packet_counter.packets; |
| info.packets_lost = stats.rtp_stats.packets_lost; |
| info.jitter_ms = stats.rtp_stats.jitter / (kVideoCodecClockrate / 1000); |
| |
| info.framerate_received = stats.network_frame_rate; |
| info.framerate_decoded = stats.decode_frame_rate; |
| info.framerate_output = stats.render_frame_rate; |
| info.frame_width = stats.width; |
| info.frame_height = stats.height; |
| |
| { |
| webrtc::MutexLock frame_cs(&sink_lock_); |
| info.capture_start_ntp_time_ms = estimated_remote_start_ntp_time_ms_; |
| } |
| |
| info.decode_ms = stats.decode_ms; |
| info.max_decode_ms = stats.max_decode_ms; |
| info.current_delay_ms = stats.current_delay_ms; |
| info.target_delay_ms = stats.target_delay_ms; |
| info.jitter_buffer_ms = stats.jitter_buffer_ms; |
| info.jitter_buffer_delay_seconds = |
| stats.jitter_buffer_delay.seconds<double>(); |
| info.jitter_buffer_target_delay_seconds = |
| stats.jitter_buffer_target_delay.seconds<double>(); |
| info.jitter_buffer_emitted_count = stats.jitter_buffer_emitted_count; |
| info.jitter_buffer_minimum_delay_seconds = |
| stats.jitter_buffer_minimum_delay.seconds<double>(); |
| info.min_playout_delay_ms = stats.min_playout_delay_ms; |
| info.render_delay_ms = stats.render_delay_ms; |
| info.frames_received = |
| stats.frame_counts.key_frames + stats.frame_counts.delta_frames; |
| info.frames_dropped = stats.frames_dropped; |
| info.frames_decoded = stats.frames_decoded; |
| info.key_frames_decoded = stats.frame_counts.key_frames; |
| info.frames_rendered = stats.frames_rendered; |
| info.qp_sum = stats.qp_sum; |
| info.corruption_score_sum = stats.corruption_score_sum; |
| info.corruption_score_squared_sum = stats.corruption_score_squared_sum; |
| info.corruption_score_count = stats.corruption_score_count; |
| info.total_decode_time = stats.total_decode_time; |
| info.total_processing_delay = stats.total_processing_delay; |
| info.total_assembly_time = stats.total_assembly_time; |
| info.frames_assembled_from_multiple_packets = |
| stats.frames_assembled_from_multiple_packets; |
| info.last_packet_received = stats.rtp_stats.last_packet_received; |
| info.estimated_playout_ntp_timestamp_ms = |
| stats.estimated_playout_ntp_timestamp_ms; |
| info.first_frame_received_to_decoded_ms = |
| stats.first_frame_received_to_decoded_ms; |
| info.total_inter_frame_delay = stats.total_inter_frame_delay; |
| info.total_squared_inter_frame_delay = stats.total_squared_inter_frame_delay; |
| info.interframe_delay_max_ms = stats.interframe_delay_max_ms; |
| info.freeze_count = stats.freeze_count; |
| info.pause_count = stats.pause_count; |
| info.total_freezes_duration_ms = stats.total_freezes_duration_ms; |
| info.total_pauses_duration_ms = stats.total_pauses_duration_ms; |
| |
| info.content_type = stats.content_type; |
| |
| info.firs_sent = stats.rtcp_packet_type_counts.fir_packets; |
| info.plis_sent = stats.rtcp_packet_type_counts.pli_packets; |
| info.nacks_sent = stats.rtcp_packet_type_counts.nack_packets; |
| // TODO(bugs.webrtc.org/10662): Add stats for LNTF. |
| |
| info.timing_frame_info = stats.timing_frame_info; |
| |
| if (stats.rtx_rtp_stats.has_value()) { |
| info.retransmitted_packets_received = |
| stats.rtx_rtp_stats->packet_counter.packets; |
| info.retransmitted_bytes_received = |
| stats.rtx_rtp_stats->packet_counter.payload_bytes; |
| // RTX information gets added to primary counters. |
| info.payload_bytes_received += |
| stats.rtx_rtp_stats->packet_counter.payload_bytes; |
| info.header_and_padding_bytes_received += |
| stats.rtx_rtp_stats->packet_counter.header_bytes + |
| stats.rtx_rtp_stats->packet_counter.padding_bytes; |
| info.packets_received += stats.rtx_rtp_stats->packet_counter.packets; |
| } |
| |
| if (flexfec_stream_) { |
| const webrtc::ReceiveStatistics* fec_stats = flexfec_stream_->GetStats(); |
| if (fec_stats) { |
| const webrtc::StreamStatistician* statistican = |
| fec_stats->GetStatistician(flexfec_config_.rtp.remote_ssrc); |
| if (statistican) { |
| const webrtc::RtpReceiveStats fec_rtp_stats = statistican->GetStats(); |
| info.fec_packets_received = fec_rtp_stats.packet_counter.packets; |
| // TODO(bugs.webrtc.org/15250): implement fecPacketsDiscarded. |
| info.fec_bytes_received = fec_rtp_stats.packet_counter.payload_bytes; |
| // FEC information gets added to primary counters. |
| info.payload_bytes_received += |
| fec_rtp_stats.packet_counter.payload_bytes; |
| info.header_and_padding_bytes_received += |
| fec_rtp_stats.packet_counter.header_bytes + |
| fec_rtp_stats.packet_counter.padding_bytes; |
| info.packets_received += fec_rtp_stats.packet_counter.packets; |
| } else { |
| info.fec_packets_received = 0; |
| } |
| } |
| } |
| |
| // remote-outbound-rtp stats. |
| info.last_sender_report_timestamp = stats.last_sender_report_timestamp; |
| info.last_sender_report_utc_timestamp = |
| stats.last_sender_report_utc_timestamp; |
| info.last_sender_report_remote_utc_timestamp = |
| stats.last_sender_report_remote_utc_timestamp; |
| info.sender_reports_packets_sent = stats.sender_reports_packets_sent; |
| info.sender_reports_bytes_sent = stats.sender_reports_bytes_sent; |
| info.sender_reports_reports_count = stats.sender_reports_reports_count; |
| // TODO(bugs.webrtc.org/12529): RTT-related fields are missing and can only be |
| // present if DLRR is enabled. |
| |
| if (log_stats) |
| RTC_LOG(LS_INFO) << stats.ToString(rtc::TimeMillis()); |
| |
| return info; |
| } |
| |
| void WebRtcVideoReceiveChannel::WebRtcVideoReceiveStream:: |
| SetRecordableEncodedFrameCallback( |
| std::function<void(const webrtc::RecordableEncodedFrame&)> callback) { |
| if (stream_) { |
| stream_->SetAndGetRecordingState( |
| webrtc::VideoReceiveStreamInterface::RecordingState( |
| std::move(callback)), |
| /*generate_key_frame=*/true); |
| } else { |
| RTC_LOG(LS_ERROR) << "Absent receive stream; ignoring setting encoded " |
| "frame sink"; |
| } |
| } |
| |
| void WebRtcVideoReceiveChannel::WebRtcVideoReceiveStream:: |
| ClearRecordableEncodedFrameCallback() { |
| if (stream_) { |
| stream_->SetAndGetRecordingState( |
| webrtc::VideoReceiveStreamInterface::RecordingState(), |
| /*generate_key_frame=*/false); |
| } else { |
| RTC_LOG(LS_ERROR) << "Absent receive stream; ignoring clearing encoded " |
| "frame sink"; |
| } |
| } |
| |
| void WebRtcVideoReceiveChannel::WebRtcVideoReceiveStream::GenerateKeyFrame() { |
| if (stream_) { |
| stream_->GenerateKeyFrame(); |
| } else { |
| RTC_LOG(LS_ERROR) |
| << "Absent receive stream; ignoring key frame generation request."; |
| } |
| } |
| |
| void WebRtcVideoReceiveChannel::WebRtcVideoReceiveStream:: |
| SetDepacketizerToDecoderFrameTransformer( |
| rtc::scoped_refptr<webrtc::FrameTransformerInterface> |
| frame_transformer) { |
| config_.frame_transformer = frame_transformer; |
| if (stream_) |
| stream_->SetDepacketizerToDecoderFrameTransformer(frame_transformer); |
| } |
| |
| void WebRtcVideoReceiveChannel::WebRtcVideoReceiveStream::SetLocalSsrc( |
| uint32_t ssrc) { |
| config_.rtp.local_ssrc = ssrc; |
| call_->OnLocalSsrcUpdated(stream(), ssrc); |
| if (flexfec_stream_) |
| call_->OnLocalSsrcUpdated(*flexfec_stream_, ssrc); |
| } |
| |
| void WebRtcVideoReceiveChannel::WebRtcVideoReceiveStream::UpdateRtxSsrc( |
| uint32_t ssrc) { |
| stream_->UpdateRtxSsrc(ssrc); |
| } |
| WebRtcVideoReceiveChannel::WebRtcVideoReceiveStream* |
| WebRtcVideoReceiveChannel::FindReceiveStream(uint32_t ssrc) { |
| if (ssrc == 0) { |
| std::optional<uint32_t> default_ssrc = GetUnsignaledSsrc(); |
| if (!default_ssrc) { |
| return nullptr; |
| } |
| ssrc = *default_ssrc; |
| } |
| auto it = receive_streams_.find(ssrc); |
| if (it != receive_streams_.end()) { |
| return it->second; |
| } |
| return nullptr; |
| } |
| |
| // RTC_RUN_ON(worker_thread_) |
| void WebRtcVideoReceiveChannel::ProcessReceivedPacket( |
| webrtc::RtpPacketReceived packet) { |
| // TODO(bugs.webrtc.org/11993): This code is very similar to what |
| // WebRtcVoiceMediaChannel::OnPacketReceived does. For maintainability and |
| // consistency it would be good to move the interaction with call_->Receiver() |
| // to a common implementation and provide a callback on the worker thread |
| // for the exception case (DELIVERY_UNKNOWN_SSRC) and how retry is attempted. |
| // TODO(bugs.webrtc.org/7135): extensions in `packet` is currently set |
| // in RtpTransport and does not neccessarily include extensions specific |
| // to this channel/MID. Also see comment in |
| // BaseChannel::MaybeUpdateDemuxerAndRtpExtensions_w. |
| // It would likely be good if extensions where merged per BUNDLE and |
| // applied directly in RtpTransport::DemuxPacket; |
| packet.IdentifyExtensions(recv_rtp_extension_map_); |
| packet.set_payload_type_frequency(webrtc::kVideoPayloadTypeFrequency); |
| if (!packet.arrival_time().IsFinite()) { |
| packet.set_arrival_time(webrtc::Timestamp::Micros(rtc::TimeMicros())); |
| } |
| |
| call_->Receiver()->DeliverRtpPacket( |
| webrtc::MediaType::VIDEO, std::move(packet), |
| absl::bind_front( |
| &WebRtcVideoReceiveChannel::MaybeCreateDefaultReceiveStream, this)); |
| } |
| |
| void WebRtcVideoReceiveChannel::SetRecordableEncodedFrameCallback( |
| uint32_t ssrc, |
| std::function<void(const webrtc::RecordableEncodedFrame&)> callback) { |
| RTC_DCHECK_RUN_ON(&thread_checker_); |
| WebRtcVideoReceiveStream* stream = FindReceiveStream(ssrc); |
| if (stream) { |
| stream->SetRecordableEncodedFrameCallback(std::move(callback)); |
| } else { |
| RTC_LOG(LS_ERROR) << "Absent receive stream; ignoring setting encoded " |
| "frame sink for ssrc " |
| << ssrc; |
| } |
| } |
| |
| void WebRtcVideoReceiveChannel::ClearRecordableEncodedFrameCallback( |
| uint32_t ssrc) { |
| RTC_DCHECK_RUN_ON(&thread_checker_); |
| WebRtcVideoReceiveStream* stream = FindReceiveStream(ssrc); |
| if (stream) { |
| stream->ClearRecordableEncodedFrameCallback(); |
| } else { |
| RTC_LOG(LS_ERROR) << "Absent receive stream; ignoring clearing encoded " |
| "frame sink for ssrc " |
| << ssrc; |
| } |
| } |
| |
| void WebRtcVideoReceiveChannel::RequestRecvKeyFrame(uint32_t ssrc) { |
| RTC_DCHECK_RUN_ON(&thread_checker_); |
| WebRtcVideoReceiveStream* stream = FindReceiveStream(ssrc); |
| if (stream) { |
| return stream->GenerateKeyFrame(); |
| } else { |
| RTC_LOG(LS_ERROR) |
| << "Absent receive stream; ignoring key frame generation for ssrc " |
| << ssrc; |
| } |
| } |
| |
| void WebRtcVideoReceiveChannel::SetDepacketizerToDecoderFrameTransformer( |
| uint32_t ssrc, |
| rtc::scoped_refptr<webrtc::FrameTransformerInterface> frame_transformer) { |
| RTC_DCHECK(frame_transformer); |
| RTC_DCHECK_RUN_ON(&thread_checker_); |
| if (ssrc == 0) { |
| // If the receiver is unsignaled, save the frame transformer and set it |
| // when the stream is associated with an ssrc. |
| unsignaled_frame_transformer_ = std::move(frame_transformer); |
| return; |
| } |
| |
| auto matching_stream = receive_streams_.find(ssrc); |
| if (matching_stream != receive_streams_.end()) { |
| matching_stream->second->SetDepacketizerToDecoderFrameTransformer( |
| std::move(frame_transformer)); |
| } |
| } |
| |
| // ------------------------- VideoCodecSettings -------------------- |
| |
| VideoCodecSettings::VideoCodecSettings(const Codec& codec) |
| : codec(codec), flexfec_payload_type(-1), rtx_payload_type(-1) {} |
| |
| bool VideoCodecSettings::operator==(const VideoCodecSettings& other) const { |
| return codec == other.codec && ulpfec == other.ulpfec && |
| flexfec_payload_type == other.flexfec_payload_type && |
| rtx_payload_type == other.rtx_payload_type && |
| rtx_time == other.rtx_time; |
| } |
| |
| bool VideoCodecSettings::EqualsDisregardingFlexfec( |
| const VideoCodecSettings& a, |
| const VideoCodecSettings& b) { |
| return a.codec == b.codec && a.ulpfec == b.ulpfec && |
| a.rtx_payload_type == b.rtx_payload_type && a.rtx_time == b.rtx_time; |
| } |
| |
| bool VideoCodecSettings::operator!=(const VideoCodecSettings& other) const { |
| return !(*this == other); |
| } |
| |
| } // namespace cricket |