/// <summary> /// Validates RtpEncodingParameters. It may modify given data by adding missing /// fields with default values. /// It throws if invalid. /// </summary> public static void ValidateRtpEncodingParameters(RtpEncodingParameters encoding) { if (encoding == null) { throw new ArgumentNullException(nameof(encoding)); } // ssrc is optional. // 在 Node.js 实现中,判断了 ssrc 的数据类型。在强类型语言中不需要。 // rid is optional. // 在 Node.js 实现中,判断了 rid 的数据类型。在强类型语言中不需要。 // rtx is optional. // 在 Node.js 实现中,判断了 rtx 的数据类型。在强类型语言中不需要。 if (encoding.Rtx != null) { // RTX ssrc is mandatory if rtx is present. // 在 Node.js 实现中,判断了 rtx.ssrc 的数据类型。在强类型语言中不需要。 } // dtx is optional. If unset set it to false. if (!encoding.Dtx.HasValue) { encoding.Dtx = false; } // scalabilityMode is optional. // 在 Node.js 实现中,判断了 scalabilityMode 的数据类型。在强类型语言中不需要。 }
/// <summary> /// Generate RTP parameters for a specific Consumer. /// /// It reduces encodings to just one and takes into account given RTP capabilities /// to reduce codecs, codecs' RTCP feedback and header extensions, and also enables /// or disabled RTX. /// </summary> public static RtpParameters GetConsumerRtpParameters(RtpParameters consumableParams, RtpCapabilities caps) { var consumerParams = new RtpParameters { Codecs = new List <RtpCodecParameters>(), HeaderExtensions = new List <RtpHeaderExtensionParameters>(), Encodings = new List <RtpEncodingParameters>(), Rtcp = consumableParams.Rtcp }; foreach (var capCodec in caps.Codecs) // TODO: (alby)注意 null 引用 { ValidateRtpCodecCapability(capCodec); } var consumableCodecs = consumableParams.Codecs.DeepClone <List <RtpCodecParameters> >(); var rtxSupported = false; foreach (var codec in consumableCodecs) { var matchedCapCodec = caps.Codecs .Where(capCodec => MatchCodecs(capCodec, codec, true)) .FirstOrDefault(); if (matchedCapCodec == null) { continue; } codec.RtcpFeedback = matchedCapCodec.RtcpFeedback; consumerParams.Codecs.Add(codec); if (!rtxSupported && IsRtxMimeType(codec.MimeType)) { rtxSupported = true; } } // Ensure there is at least one media codec. if (consumerParams.Codecs.Count == 0 || IsRtxMimeType(consumerParams.Codecs[0].MimeType)) { throw new Exception("no compatible media codecs"); } consumerParams.HeaderExtensions = consumableParams.HeaderExtensions .Where(ext => caps.HeaderExtensions .Any(capExt => capExt.PreferredId == ext.Id && capExt.Uri == ext.Uri) ).ToList(); // Reduce codecs' RTCP feedback. Use Transport-CC if available, REMB otherwise. if (consumerParams.HeaderExtensions.Any(ext => ext.Uri == "http://www.ietf.org/id/draft-holmer-rmcat-transport-wide-cc-extensions-01")) { foreach (var codec in consumerParams.Codecs) { codec.RtcpFeedback = codec.RtcpFeedback.Where(fb => fb.Type != "goog-remb").ToArray(); } } else if (consumerParams.HeaderExtensions.Any(ext => ext.Uri == "http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time")) { foreach (var codec in consumerParams.Codecs) { codec.RtcpFeedback = codec.RtcpFeedback.Where(fb => fb.Type != "transport-cc").ToArray(); } } else { foreach (var codec in consumerParams.Codecs) { codec.RtcpFeedback = codec.RtcpFeedback.Where(fb => fb.Type != "transport-cc" && fb.Type != "goog-remb").ToArray(); } } var consumerEncoding = new RtpEncodingParameters { Ssrc = Utils.GenerateRandomNumber() }; if (rtxSupported) { consumerEncoding.Rtx = new Rtx { Ssrc = Utils.GenerateRandomNumber() }; } // If any in the consumableParams.Encodings has scalabilityMode, process it // (assume all encodings have the same value). var encodingWithScalabilityMode = consumableParams.Encodings.Where(encoding => !encoding.ScalabilityMode.IsNullOrWhiteSpace()).FirstOrDefault(); var scalabilityMode = encodingWithScalabilityMode?.ScalabilityMode; // If there is simulast, mangle spatial layers in scalabilityMode. if (consumableParams.Encodings.Count > 1) // TODO: (alby)注意 null 引用 { var scalabilityModeObject = ScalabilityMode.Parse(scalabilityMode); // TODO: (alby)注意 null 引用 scalabilityMode = $"S{ consumableParams.Encodings.Count }T{ scalabilityModeObject.TemporalLayers }"; } if (!scalabilityMode.IsNullOrWhiteSpace()) { consumerEncoding.ScalabilityMode = scalabilityMode; } // Use the maximum maxBitrate in any encoding and honor it in the Consumer's // encoding. var maxEncodingMaxBitrate = consumableParams.Encodings.Max(m => m.MaxBitrate); if (maxEncodingMaxBitrate.HasValue && maxEncodingMaxBitrate.Value > 0) { consumerEncoding.MaxBitrate = maxEncodingMaxBitrate; } // Set a single encoding for the Consumer. consumerParams.Encodings.Add(consumerEncoding); // Copy verbatim. consumerParams.Rtcp = consumableParams.Rtcp; return(consumerParams); }