예제 #1
0
        /// <summary>
        /// Check whether the given RTP capabilities can consume the given Producer.
        /// </summary>
        public static bool CanConsume(RtpParameters consumableParams, RtpCapabilities caps)
        {
            // This may throw.
            ValidateRtpCapabilities(caps);

            var matchingCodecs = new List <RtpCodecParameters>();

            foreach (var codec in consumableParams.Codecs)
            {
                var matchedCapCodec = caps.Codecs
                                      .Where(capCodec => MatchCodecs(capCodec, codec, true))
                                      .FirstOrDefault();

                if (matchedCapCodec == null)
                {
                    continue;
                }

                matchingCodecs.Add(codec);
            }

            // Ensure there is at least one media codec.
            if (matchingCodecs.Count == 0 || IsRtxMimeType(matchingCodecs[0].MimeType))
            {
                return(false);
            }

            return(true);
        }
예제 #2
0
        /// <summary>
        /// Get a mapping in codec payloads and encodings in the given Producer RTP
        /// parameters as values expected by the Router.
        ///
        /// It may throw if invalid or non supported RTP parameters are given.
        /// </summary>
        public static RtpMapping GetProducerRtpParametersMapping(RtpParameters parameters, RtpCapabilities caps)
        {
            var rtpMapping = new RtpMapping
            {
                Codecs    = new List <RtpMappingCodec>(),
                Encodings = new List <RtpMappingEncoding>()
            };

            // Match parameters media codecs to capabilities media codecs.
            var codecToCapCodec = new Dictionary <RtpCodecParameters, RtpCodecCapability>();

            foreach (var codec in parameters.Codecs)
            {
                if (IsRtxMimeType(codec.MimeType))
                {
                    continue;
                }

                // Search for the same media codec in capabilities.
                var matchedCapCodec = caps.Codecs
                                      .Where(capCodec => MatchCodecs(codec, capCodec, true, true))
                                      .FirstOrDefault();
                codecToCapCodec[codec] = matchedCapCodec ?? throw new Exception($"unsupported codec[mimeType:{ codec.MimeType }, payloadType:{ codec.PayloadType }, Channels:{ codec.Channels }]");
            }

            // Match parameters RTX codecs to capabilities RTX codecs.
            foreach (var codec in parameters.Codecs)
            {
                if (!IsRtxMimeType(codec.MimeType))
                {
                    continue;
                }

                // Search for the associated media codec.
                var associatedMediaCodec = parameters.Codecs
                                           .Where(mediaCodec => MatchCodecsWithPayloadTypeAndApt(mediaCodec.PayloadType, codec.Parameters))
                                           .FirstOrDefault();

                if (associatedMediaCodec == null)
                {
                    throw new Exception($"missing media codec found for RTX PT { codec.PayloadType}");
                }

                var capMediaCodec = codecToCapCodec[associatedMediaCodec];

                // Ensure that the capabilities media codec has a RTX codec.
                var associatedCapRtxCodec = caps.Codecs
                                            .Where(capCodec => IsRtxMimeType(capCodec.MimeType) && MatchCodecsWithPayloadTypeAndApt(capMediaCodec.PreferredPayloadType, capCodec.Parameters))
                                            .FirstOrDefault();
                codecToCapCodec[codec] = associatedCapRtxCodec ?? throw new Exception($"no RTX codec for capability codec PT { capMediaCodec.PreferredPayloadType}");
            }

            // Generate codecs mapping.
            foreach (var item in codecToCapCodec)
            {
                rtpMapping.Codecs.Add(new RtpMappingCodec
                {
                    PayloadType       = item.Key.PayloadType,
                    MappedPayloadType = item.Value.PreferredPayloadType !.Value,
                });
예제 #3
0
        /// <summary>
        /// <para>Events:</para>
        /// <para>@emits transportclose</para>
        /// <para>@emits producerclose</para>
        /// <para>@emits producerpause</para>
        /// <para>@emits producerresume</para>
        /// <para>@emits score - (score: ConsumerScore)</para>
        /// <para>@emits layerschange - (layers: ConsumerLayers | undefined)</para>
        /// <para>@emits trace - (trace: ConsumerTraceEventData)</para>
        /// <para>@emits @close</para>
        /// <para>@emits @producerclose</para>
        /// <para>Observer events:</para>
        /// <para>@emits close</para>
        /// <para>@emits pause</para>
        /// <para>@emits resume</para>
        /// <para>@emits score - (score: ConsumerScore)</para>
        /// <para>@emits layerschange - (layers: ConsumerLayers | undefined)</para>
        /// <para>@emits trace - (trace: ConsumerTraceEventData)</para>
        /// </summary>
        /// <param name="loggerFactory"></param>
        /// <param name="consumerInternalData"></param>
        /// <param name="kind"></param>
        /// <param name="rtpParameters"></param>
        /// <param name="type"></param>
        /// <param name="channel"></param>
        /// <param name="appData"></param>
        /// <param name="paused"></param>
        /// <param name="producerPaused"></param>
        /// <param name="score"></param>
        /// <param name="preferredLayers"></param>
        public Consumer(ILoggerFactory loggerFactory,
                        ConsumerInternalData consumerInternalData,
                        MediaKind kind,
                        RtpParameters rtpParameters,
                        ConsumerType type,
                        Channel channel,
                        Dictionary <string, object>?appData,
                        bool paused,
                        bool producerPaused,
                        ConsumerScore?score,
                        ConsumerLayers?preferredLayers
                        )
        {
            _logger = loggerFactory.CreateLogger <Consumer>();

            // Internal
            Internal = consumerInternalData;

            // Data
            Kind          = kind;
            RtpParameters = rtpParameters;
            Type          = type;

            _channel        = channel;
            AppData         = appData;
            Paused          = paused;
            ProducerPaused  = producerPaused;
            Score           = score;
            PreferredLayers = preferredLayers;

            HandleWorkerNotifications();
        }
예제 #4
0
        /// <summary>
        /// Generate RTP parameters for a pipe Consumer.
        ///
        /// It keeps all original consumable encodings and removes support for BWE. If
        /// enableRtx is false, it also removes RTX and NACK support.
        /// </summary>
        public static RtpParameters GetPipeConsumerRtpParameters(RtpParameters consumableParams, bool enableRtx = false)
        {
            var consumerParams = new RtpParameters
            {
                Codecs           = new List <RtpCodecParameters>(),
                HeaderExtensions = new List <RtpHeaderExtensionParameters>(),
                Encodings        = new List <RtpEncodingParameters>(),
                Rtcp             = consumableParams.Rtcp
            };

            var consumableCodecs = consumableParams.Codecs.DeepClone <List <RtpCodecParameters> >();

            foreach (var codec in consumableCodecs)
            {
                if (!enableRtx && IsRtxMimeType(codec.MimeType))
                {
                    continue;
                }

                codec.RtcpFeedback = codec.RtcpFeedback
                                     .Where(fb =>
                                            (fb.Type == "nack" && fb.Parameter == "pli") ||
                                            (fb.Type == "ccm" && fb.Parameter == "fir") ||
                                            (enableRtx && fb.Type == "nack" && fb.Parameter.IsNullOrWhiteSpace())
                                            ).ToArray();

                consumerParams.Codecs.Add(codec);
            }

            // Reduce RTP extensions by disabling transport MID and BWE related ones.
            consumerParams.HeaderExtensions = consumableParams.HeaderExtensions
                                              .Where(ext => (ext.Uri != "urn:ietf:parameters:rtp-hdrext:sdes:mid" &&
                                                             ext.Uri != "http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time" &&
                                                             ext.Uri != "http://www.ietf.org/id/draft-holmer-rmcat-transport-wide-cc-extensions-01"
                                                             )).ToList();

            var consumableEncodings = consumableParams.Encodings.DeepClone <List <RtpEncodingParameters> >();

            foreach (var encoding in consumableEncodings)
            {
                if (!enableRtx)
                {
                    // 在 Node.js 实现中,delete 了 rtx 。
                    encoding.Rtx = null;
                }

                consumerParams.Encodings.Add(encoding);
            }

            return(consumerParams);
        }
예제 #5
0
        /// <summary>
        /// Validates RtpParameters. It may modify given data by adding missing
        /// fields with default values.
        /// It throws if invalid.
        /// </summary>
        public static void ValidateRtpParameters(RtpParameters parameters)
        {
            if (parameters == null)
            {
                throw new ArgumentNullException(nameof(parameters));
            }
            // mid is optional.
            // 在 Node.js 实现中,判断了 mid 的数据类型。在强类型语言中不需要。

            // codecs is mandatory.
            if (parameters.Codecs == null)
            {
                throw new ArgumentException(nameof(parameters.Codecs));
            }

            foreach (var codec in parameters.Codecs)
            {
                ValidateRtpCodecParameters(codec);
            }

            // headerExtensions is optional. If unset, fill with an empty array.
            if (parameters.HeaderExtensions == null)
            {
                parameters.HeaderExtensions = new List <RtpHeaderExtensionParameters>();
            }

            foreach (var ext in parameters.HeaderExtensions)
            {
                ValidateRtpHeaderExtensionParameters(ext);
            }

            // encodings is optional. If unset, fill with an empty array.
            if (parameters.Encodings == null)
            {
                parameters.Encodings = new List <RtpEncodingParameters>();
            }

            foreach (var encoding in parameters.Encodings)
            {
                ValidateRtpEncodingParameters(encoding);
            }

            // rtcp is optional. If unset, fill with an empty object.
            // TODO: (alby)强类型无法转 Empty 对象,注意对象属性默认值可能导致的问题。
            if (parameters.Rtcp == null)
            {
                parameters.Rtcp = new RtcpParameters();
            }
            ValidateRtcpParameters(parameters.Rtcp);
        }
예제 #6
0
        /// <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);
        }
예제 #7
0
        /// <summary>
        /// Generate RTP parameters to be internally used by Consumers given the RTP
        /// parameters in a Producer and the RTP capabilities in the Router.
        /// </summary>
        public static RtpParameters GetConsumableRtpParameters(MediaKind kind, RtpParameters parameters, RtpCapabilities caps, RtpMapping rtpMapping)
        {
            var consumableParams = new RtpParameters
            {
                Codecs           = new List <RtpCodecParameters>(),
                HeaderExtensions = new List <RtpHeaderExtensionParameters>(),
                Encodings        = new List <RtpEncodingParameters>(),
                Rtcp             = new RtcpParameters(),
            };

            foreach (var codec in parameters.Codecs)
            {
                if (IsRtxMimeType(codec.MimeType))
                {
                    continue;
                }

                var consumableCodecPt = rtpMapping.Codecs
                                        .Where(entry => entry.PayloadType == codec.PayloadType)
                                        .Select(m => m.MappedPayloadType)
                                        .FirstOrDefault();

                var matchedCapCodec = caps.Codecs
                                      .Where(capCodec => capCodec.PreferredPayloadType == consumableCodecPt)
                                      .FirstOrDefault();

                var consumableCodec = new RtpCodecParameters
                {
                    MimeType     = matchedCapCodec.MimeType,
                    PayloadType  = matchedCapCodec.PreferredPayloadType.Value, // TODO: (alby)注意 null 引用
                    ClockRate    = matchedCapCodec.ClockRate,
                    Channels     = matchedCapCodec.Channels,
                    Parameters   = codec.Parameters, // Keep the Producer codec parameters.
                    RtcpFeedback = matchedCapCodec.RtcpFeedback
                };

                consumableParams.Codecs.Add(consumableCodec);

                var consumableCapRtxCodec = caps.Codecs
                                            .Where(capRtxCodec => IsRtxMimeType(capRtxCodec.MimeType) && MatchCodecsWithPayloadTypeAndApt(consumableCodec.PayloadType, capRtxCodec.Parameters))
                                            .FirstOrDefault();

                if (consumableCapRtxCodec != null)
                {
                    var consumableRtxCodec = new RtpCodecParameters
                    {
                        MimeType     = consumableCapRtxCodec.MimeType,
                        PayloadType  = consumableCapRtxCodec.PreferredPayloadType.Value, // TODO: (alby)注意 null 引用
                        ClockRate    = consumableCapRtxCodec.ClockRate,
                        Channels     = consumableCapRtxCodec.Channels,
                        Parameters   = consumableCapRtxCodec.Parameters, // Keep the Producer codec parameters.
                        RtcpFeedback = consumableCapRtxCodec.RtcpFeedback
                    };

                    consumableParams.Codecs.Add(consumableRtxCodec);
                }
            }

            foreach (var capExt in caps.HeaderExtensions) // TODO: (alby)注意 null 引用
            {
                // Just take RTP header extension that can be used in Consumers.
                if (capExt.Kind != kind || (capExt.Direction != RtpHeaderExtensionDirection.SendReceive && capExt.Direction != RtpHeaderExtensionDirection.SendOnly))
                {
                    continue;
                }

                var consumableExt = new RtpHeaderExtensionParameters
                {
                    Uri        = capExt.Uri,
                    Id         = capExt.PreferredId,
                    Encrypt    = capExt.PreferredEncrypt,
                    Parameters = new Dictionary <string, object>(),
                };

                consumableParams.HeaderExtensions.Add(consumableExt);
            }

            // Clone Producer encodings since we'll mangle them.
            var consumableEncodings = parameters.Encodings.DeepClone <List <RtpEncodingParameters> >();

            for (var i = 0; i < consumableEncodings.Count; ++i)
            {
                var consumableEncoding = consumableEncodings[i];
                var mappedSsrc         = rtpMapping.Encodings[i].MappedSsrc;

                // Remove useless fields.
                // 在 Node.js 实现中,rid, rtx, codecPayloadType 被 delete 了。
                consumableEncoding.Rid = null;
                consumableEncoding.Rtx = null;
                consumableEncoding.CodecPayloadType = null;

                // Set the mapped ssrc.
                consumableEncoding.Ssrc = mappedSsrc;

                consumableParams.Encodings.Add(consumableEncoding);
            }

            consumableParams.Rtcp = new RtcpParameters
            {
                CNAME       = parameters.Rtcp.CNAME, // TODO: (alby)注意 null 引用
                ReducedSize = true,
                Mux         = true,
            };

            return(consumableParams);
        }