/// <summary> /// Validates RtpCodecCapability. It may modify given data by adding missing /// fields with default values. /// It throws if invalid. /// </summary> public static void ValidateRtpCodecCapability(RtpCodecCapability codec) { // mimeType is mandatory. if (codec.MimeType.IsNullOrWhiteSpace()) { throw new ArgumentException(nameof(codec.MimeType)); } var mimeType = codec.MimeType.ToLower(); if (!MimeTypeRegex.IsMatch(mimeType)) { throw new ArgumentException(nameof(codec.MimeType)); } // Just override kind with media component in mimeType. codec.Kind = mimeType.StartsWith("video") ? MediaKind.Video : MediaKind.Audio; // preferredPayloadType is optional. // 在 Node.js 实现中,判断了 preferredPayloadType 在有值的情况下的数据类型。在强类型语言中不需要。 // clockRate is mandatory. // 在 Node.js 实现中,判断了 mandatory 的数据类型。在强类型语言中不需要。 // channels is optional. If unset, set it to 1 (just if audio). if (codec.Kind == MediaKind.Audio && (!codec.Channels.HasValue || codec.Channels < 1)) { codec.Channels = 1; } // parameters is optional. If unset, set it to an empty object. if (codec.Parameters == null) { codec.Parameters = new Dictionary <string, object>(); } foreach (var item in codec.Parameters) { var key = item.Key; var value = item.Value; if (value == null) { codec.Parameters[item.Key] = ""; value = ""; } if (!value.IsStringType() && !value.IsNumericType()) { throw new ArgumentOutOfRangeException($"invalid codec parameter[key:{key}, value:{value}]"); } // Specific parameters validation. if (key == "apt" && !value.IsNumericType()) { throw new ArgumentOutOfRangeException("invalid codec apt parameter"); } } // rtcpFeedback is optional. If unset, set it to an empty array. if (codec.RtcpFeedback == null) { codec.RtcpFeedback = Array.Empty <RtcpFeedback>(); } foreach (var fb in codec.RtcpFeedback) { ValidateRtcpFeedback(fb); } }
/// <summary> /// Generate RTP capabilities for the Router based on the given media codecs and /// mediasoup supported RTP capabilities. /// </summary> public static RtpCapabilities GenerateRouterRtpCapabilities(RtpCodecCapability[] mediaCodecs) { if (mediaCodecs == null) { throw new ArgumentNullException(nameof(mediaCodecs)); } // Normalize supported RTP capabilities. ValidateRtpCapabilities(RtpCapabilities.SupportedRtpCapabilities); var clonedSupportedRtpCapabilities = RtpCapabilities.SupportedRtpCapabilities.DeepClone <RtpCapabilities>(); var dynamicPayloadTypes = DynamicPayloadTypes.DeepClone <int[]>().ToList(); var caps = new RtpCapabilities { Codecs = new List <RtpCodecCapability>(), HeaderExtensions = clonedSupportedRtpCapabilities.HeaderExtensions }; foreach (var mediaCodec in mediaCodecs) { // This may throw. ValidateRtpCodecCapability(mediaCodec); var matchedSupportedCodec = clonedSupportedRtpCapabilities .Codecs ! .Where(supportedCodec => MatchCodecs(mediaCodec, supportedCodec, false)).FirstOrDefault(); if (matchedSupportedCodec == null) { throw new Exception($"media codec not supported[mimeType:{mediaCodec.MimeType}]"); } // Clone the supported codec. var codec = matchedSupportedCodec.DeepClone <RtpCodecCapability>(); // If the given media codec has preferredPayloadType, keep it. if (mediaCodec.PreferredPayloadType.HasValue) { codec.PreferredPayloadType = mediaCodec.PreferredPayloadType; // Also remove the pt from the list in available dynamic values. dynamicPayloadTypes.Remove(codec.PreferredPayloadType.Value); } // Otherwise if the supported codec has preferredPayloadType, use it. else if (codec.PreferredPayloadType.HasValue) { // No need to remove it from the list since it's not a dynamic value. } // Otherwise choose a dynamic one. else { // Take the first available pt and remove it from the list. var pt = dynamicPayloadTypes.FirstOrDefault(); if (pt == 0) { throw new Exception("cannot allocate more dynamic codec payload types"); } dynamicPayloadTypes.RemoveAt(0); codec.PreferredPayloadType = pt; } // Ensure there is not duplicated preferredPayloadType values. if (caps.Codecs.Any(c => c.PreferredPayloadType == codec.PreferredPayloadType)) { throw new Exception("duplicated codec.preferredPayloadType"); } // Merge the media codec parameters. codec.Parameters = codec.Parameters.Merge(mediaCodec.Parameters); // Append to the codec list. caps.Codecs.Add(codec); // Add a RTX video codec if video. if (codec.Kind == MediaKind.Video) { // Take the first available pt and remove it from the list. var pt = dynamicPayloadTypes.FirstOrDefault(); if (pt == 0) { throw new Exception("cannot allocate more dynamic codec payload types"); } dynamicPayloadTypes.RemoveAt(0); var rtxCodec = new RtpCodecCapability { Kind = codec.Kind, MimeType = $"{codec.Kind.GetEnumMemberValue()}/rtx", PreferredPayloadType = pt, ClockRate = codec.ClockRate, Parameters = new Dictionary <string, object> { { "apt", codec.PreferredPayloadType } }, RtcpFeedback = Array.Empty <RtcpFeedback>(), }; // Append to the codec list. caps.Codecs.Add(rtxCodec); } } return(caps); }