Example #1
0
        // Adds multi-ssrc based simulcast into the given SDP media section offer.
        public static void AddLegacySimulcast(MediaObject offerMediaObject, int numStreams)
        {
            if (numStreams <= 1)
            {
                throw new Exception("'numStreams must be greater than 1");
            }

            // Get the SSRC.
            var ssrcMsidLine = (offerMediaObject.MediaDescription.Attributes.Ssrcs ?? new List <Ssrc>())
                               .FirstOrDefault(line => line.Attribute == "msid");

            if (ssrcMsidLine is null)
            {
                throw new Exception("'a=ssrc line with msid information not found");
            }

            var  values       = ssrcMsidLine.Value.Split(' ');
            var  streamId     = values[0];
            var  trackId      = values.Length > 1 ? values[1] : null;
            var  firstSsrc    = ssrcMsidLine.Id;
            uint?firstRtxSsrc = null;

            // Get the SSRC for RTX.
            (offerMediaObject.MediaDescription.Attributes.SsrcGroups ?? new List <SsrcGroup>()).Any(line =>
            {
                if (line.Semantics != "FID")
                {
                    return(false);
                }

                if (uint.Parse(line.SsrcIds[0]) == firstSsrc)
                {
                    firstRtxSsrc = uint.Parse(line.SsrcIds[1]);
                    return(true);
                }
                else
                {
                    return(false);
                }
            });

            var ssrcCnameLine = offerMediaObject.MediaDescription.Attributes.Ssrcs
                                .FirstOrDefault(line => line.Attribute == "cname");

            if (ssrcCnameLine is null)
            {
                throw new Exception("a=ssrc line with cname information not found");
            }

            var         cname    = ssrcCnameLine.Value;
            List <uint> ssrcs    = new();
            List <uint> rtxSsrcs = new();

            for (uint i = 0; i < numStreams; ++i)
            {
                ssrcs.Add(firstSsrc + i);

                if (firstRtxSsrc is not null)
                {
                    rtxSsrcs.Add((uint)(firstRtxSsrc + i));
                }
            }

            offerMediaObject.MediaDescription.Attributes.SsrcGroups = new List <SsrcGroup>();
            offerMediaObject.MediaDescription.Attributes.Ssrcs      = new List <Ssrc>();

            var x = ssrcs.Select(ssrc => ssrc.ToString()).ToArray();

            offerMediaObject.MediaDescription.Attributes.SsrcGroups.Add(new SsrcGroup
            {
                Semantics = "SIM",
                SsrcIds   = ssrcs.Select(ssrc => ssrc.ToString()).ToArray()
            });

            for (int i = 0; i < ssrcs.Count; ++i)
            {
                var ssrc = ssrcs[i];

                offerMediaObject.MediaDescription.Attributes.Ssrcs.Add(new Ssrc
                {
                    Id        = ssrc,
                    Attribute = "cname",
                    Value     = cname
                });

                offerMediaObject.MediaDescription.Attributes.Ssrcs.Add(new Ssrc
                {
                    Id        = ssrc,
                    Attribute = "msid",
                    Value     = $"{ streamId} { trackId}"
                });
            }

            for (int i = 0; i < rtxSsrcs.Count; ++i)
            {
                var ssrc    = ssrcs[i];
                var rtxSsrc = rtxSsrcs[i];

                offerMediaObject.MediaDescription.Attributes.Ssrcs.Add(new Ssrc
                {
                    Id        = rtxSsrc,
                    Attribute = "cname",
                    Value     = cname
                });

                offerMediaObject.MediaDescription.Attributes.Ssrcs.Add(new Ssrc
                {
                    Id        = rtxSsrc,
                    Attribute = "msid",
                    Value     = $"{streamId} ${ trackId}"
                });

                offerMediaObject.MediaDescription.Attributes.SsrcGroups.Add(new SsrcGroup
                {
                    Semantics = "FID",
                    SsrcIds   = new string[] { $"{ ssrc} ${ rtxSsrc}" }
                });
            }
        }
Example #2
0
        public AnswerMediaSection(
            IceParameters iceParameters,
            IceCandidate[] iceCandidates,
            DtlsParameters dtlsParameters,
            SctpParameters sctpParameters,
            PlainRtpParameters plainRtpParameters,
            bool planB,
            MediaObject offerMediaObject,
            RtpParameters offerRtpParameters,
            RtpParameters answerRtpParameters,
            ProducerCodecOptions codecOptions,
            bool extmapAllowMixed) : base(iceParameters, iceCandidates, dtlsParameters, planB)
        {
            _mediaObject.MediaDescription.Attributes.Mid = offerMediaObject.MediaDescription.Attributes.Mid;
            _mediaObject.MediaDescription.Media          = offerMediaObject.MediaDescription.Media;
            _mediaObject.MediaDescription.Proto          = offerMediaObject.MediaDescription.Proto;

            if (plainRtpParameters is null)
            {
                _mediaObject.MediaDescription.ConnectionData = new ConnectionData
                {
                    NetType           = NetType.Internet,
                    AddrType          = AddrType.Ip4,
                    ConnectionAddress = "127.0.0.1"
                };
                _mediaObject.MediaDescription.Port = 7;
            }
            else
            {
                _mediaObject.MediaDescription.ConnectionData = new ConnectionData
                {
                    NetType           = NetType.Internet,
                    AddrType          = plainRtpParameters.IpVersion,
                    ConnectionAddress = plainRtpParameters.Ip
                };
                _mediaObject.MediaDescription.Port = plainRtpParameters.Port;
            }


            switch (offerMediaObject.MediaDescription.Media)
            {
            case MediaType.Audio:
            case MediaType.Video:
            {
                _mediaObject.MediaDescription.Attributes.RecvOnly = true;
                _mediaObject.MediaDescription.Attributes.Rtpmaps  = new List <Rtpmap>();
                _mediaObject.MediaDescription.Attributes.RtcpFbs  = new List <RtcpFb>();
                _mediaObject.MediaDescription.Attributes.Fmtps    = new List <Fmtp>();

                foreach (var codec in answerRtpParameters.Codecs)
                {
                    var    xxx    = codec;
                    Rtpmap rtpmap = new()
                    {
                        PayloadType  = codec.PayloadType,
                        EncodingName = GetCodecName(codec),
                        ClockRate    = codec.ClockRate,
                        Channels     = codec.Channels > 1 ? codec.Channels : null
                    };
                    _mediaObject.MediaDescription.Attributes.Rtpmaps.Add(rtpmap);

                    var codecParameters = Utils.Clone(codec.Parameters, new Dictionary <string, object>()
                        {
                        });
                    if (codecOptions is not null)
                    {
                        var offerCodec = offerRtpParameters.Codecs
                                         .First(c => c.PayloadType == codec.PayloadType);
                        switch (codec.MimeType.ToLower())
                        {
                        case "audio/opus":
                        {
                            if (codecOptions.OpusStereo.HasValue)
                            {
                                offerCodec.Parameters["sprop-stereo"] =
                                    codecOptions.OpusStereo == true ? 1 : 0;
                                codecParameters["stereo"] =
                                    codecOptions.OpusStereo == true ? 1 : 0;
                            }
                            if (codecOptions.OpusFec.HasValue)
                            {
                                offerCodec.Parameters["useinbandfec"] =
                                    codecOptions.OpusFec == true ? 1 : 0;
                                codecParameters["useinbandfec"] =
                                    codecOptions.OpusFec == true ? 1 : 0;
                            }
                            if (codecOptions.OpusDtx.HasValue)
                            {
                                offerCodec.Parameters["usedtx"] =
                                    codecOptions.OpusDtx == true ? 1 : 0;
                                codecParameters["usedtx"] =
                                    codecOptions.OpusDtx == true ? 1 : 0;
                            }
                            if (codecOptions.OpusMaxPlaybackRate.HasValue)
                            {
                                codecParameters["maxplaybackrate"] =
                                    codecOptions.OpusMaxPlaybackRate;
                            }
                            if (codecOptions.OpusMaxAverageBitrate.HasValue)
                            {
                                codecParameters["maxaveragebitrate"] =
                                    codecOptions.OpusMaxAverageBitrate;
                            }
                            if (codecOptions.OpusPtime.HasValue)
                            {
                                codecParameters["ptime"] =
                                    codecOptions.OpusPtime;
                            }
                        }
                        break;

                        case "video/vp8":
                        case "video/vp9":
                        case "video/h264":
                        case "video/h265":
                        {
                            if (codecOptions.VideoGoogleStartBitrate.HasValue)
                            {
                                codecParameters["x-google-start-bitrate"] =
                                    codecOptions.VideoGoogleStartBitrate;
                            }
                            if (codecOptions.VideoGoogleMaxBitrate.HasValue)
                            {
                                codecParameters["x-google-max-bitrate"] =
                                    codecOptions.VideoGoogleMaxBitrate;
                            }
                            if (codecOptions.VideoGoogleMinBitrate.HasValue)
                            {
                                codecParameters["x-google-min-bitrate"] =
                                    codecOptions.VideoGoogleMinBitrate;
                            }
                        }
                        break;
                        }
                    }

                    Fmtp fmtp = codecParameters.ToFmtp(codec.PayloadType);

                    if (!string.IsNullOrEmpty(fmtp.Value))
                    {
                        _mediaObject.MediaDescription.Attributes.Fmtps.Add(fmtp);
                    }

                    foreach (var fb in codec.RtcpFeedback)
                    {
                        RtcpFb rtcpFb = new()
                        {
                            PayloadType = codec.PayloadType,
                            Type        = fb.Type,
                            SubType     = fb.Parameter
                        };
                        _mediaObject.MediaDescription.Attributes.RtcpFbs.Add(rtcpFb);
                    }
                }

                _mediaObject.MediaDescription.Fmts ??= new List <string>();
                ((List <string>)_mediaObject.MediaDescription.Fmts).AddRange(answerRtpParameters.Codecs
                                                                             .Select(codec => codec.PayloadType.ToString()));

                _mediaObject.MediaDescription.Attributes.Extmaps = new List <Extmap>();
                foreach (var headerExtension in answerRtpParameters.HeaderExtensions)
                {
                    // Don't add a header extension if not present in the offer.
                    var found = offerMediaObject.MediaDescription.Attributes.Extmaps
                                .Any(localExt => localExt.Uri.ToString() == headerExtension.Uri);
                    if (!found)
                    {
                        continue;
                    }

                    Extmap ext = new()
                    {
                        Uri   = new Uri(headerExtension.Uri),
                        Value = headerExtension.Id
                    };
                    _mediaObject.MediaDescription.Attributes.Extmaps.Add(ext);
                }

                // Allow both 1 byte and 2 bytes length header extensions.
                if (extmapAllowMixed && offerMediaObject.MediaDescription.Attributes.ExtmapAllowMixed.HasValue &&
                    offerMediaObject.MediaDescription.Attributes.ExtmapAllowMixed == true)
                {
                    _mediaObject.MediaDescription.Attributes.ExtmapAllowMixed = true;
                }

                // Simulcast.

                //// DEBUG HACK
                if (offerMediaObject.MediaDescription.Media == MediaType.Video)
                {
                    Console.WriteLine("PUT A BP HERE...");
                }
                if (offerMediaObject.MediaDescription.Attributes.Simulcast is not null)
                {
                    _mediaObject.MediaDescription.Attributes.Simulcast = new()
                    {
                        Direction = RidDirection.Recv,
                        IdList    = offerMediaObject.MediaDescription.Attributes.Simulcast.IdList
                    };

                    _mediaObject.MediaDescription.Attributes.Rids = new List <Rid>();
                    foreach (var rid in offerMediaObject.MediaDescription.Attributes.Rids)
                    {
                        if (rid.Direction != RidDirection.Send)
                        {
                            continue;
                        }

                        _mediaObject.MediaDescription.Attributes.Rids.Add(new Rid
                            {
                                Id        = rid.Id,
                                Direction = RidDirection.Recv
                            });
                    }
                }

                // Simulcast (draft version 03).
                else if (offerMediaObject.Simulcast03 is not null)
                {
                    _mediaObject.Simulcast03 = new()
                    {
                        Value = offerMediaObject.Simulcast03.Value.Replace(RidDirection.Send.DisplayName(),
                                                                           RidDirection.Recv.DisplayName())
                    };

                    _mediaObject.MediaDescription.Attributes.Rids = new List <Rid>();
                    foreach (var rid in offerMediaObject.MediaDescription.Attributes.Rids)
                    {
                        if (rid.Direction != RidDirection.Send)
                        {
                            continue;
                        }

                        _mediaObject.MediaDescription.Attributes.Rids.Add(new Rid
                            {
                                Id        = rid.Id,
                                Direction = RidDirection.Recv
                            });
                    }
                }

                _mediaObject.MediaDescription.Attributes.RtcpMux   = true;
                _mediaObject.MediaDescription.Attributes.RtcpRsize = true;

                if (_planB && _mediaObject.MediaDescription.Media == MediaType.Video)
                {
                    _mediaObject.XGoogleFlag = "conference";
                }

                break;
            }

            case MediaType.Application:
            {
                // New spec.
                if (offerMediaObject.MediaDescription.Attributes.SctpPort.Port.GetType() == typeof(int))
                {
                    _mediaObject.MediaDescription.Fmts = new List <string> {
                        "webrtc-datachannel"
                    };
                    _mediaObject.MediaDescription.Attributes.SctpPort = new SctpPort
                    {
                        Port = sctpParameters.Port
                    };
                    _mediaObject.MediaDescription.Attributes.MaxMessageSize = new MaxMessageSize
                    {
                        Size = sctpParameters.MaxMessageSize
                    };
                }
                // Old spec.
                else if (offerMediaObject.SctpMap is not null)
                {
                    _mediaObject.MediaDescription.Fmts = new List <string> {
                        sctpParameters.Port.ToString()
                    };
                    _mediaObject.SctpMap = new()
                    {
                        App            = "webrtc-datachannel",
                        SctpMapNumber  = sctpParameters.Port,
                        MaxMessageSize = sctpParameters.MaxMessageSize
                    };
                }

                break;
            }
            }
        }
Example #3
0
        public static RtpEncodingParameters[] GetRtpEncodings(MediaObject offerMediaObject)
        {
            List <uint> ssrcs = (offerMediaObject.MediaDescription.Attributes.Ssrcs ?? new List <Ssrc>())
                                .Select(s => s.Id)
                                .Distinct()
                                .ToList();

            if (ssrcs.Count == 0)
            {
                throw new Exception("XXXXno a=ssrc lines found");
            }

            Dictionary <uint, uint?> ssrcToRtxSsrc = new();

            // First assume RTX is used.
            foreach (var line in offerMediaObject.MediaDescription.Attributes.SsrcGroups ?? new List <SsrcGroup>())
            {
                if (line.Semantics != "FID")
                {
                    continue;
                }

                var ssrc    = uint.Parse(line.SsrcIds[0]);
                var rtxSsrc = uint.Parse(line.SsrcIds[1]);

                if (ssrcs.Contains(ssrc))
                {
                    // Remove both the SSRC and RTX SSRC from the set so later we know that they
                    // are already handled.
                    ssrcs.Remove(ssrc);
                    ssrcs.Remove(rtxSsrc);

                    // Add to the map.
                    ssrcToRtxSsrc.Add(ssrc, rtxSsrc);
                }
            }

            // If the set of SSRCs is not empty it means that RTX is not being used, so take
            // media SSRCs from there.
            foreach (var ssrc in ssrcs)
            {
                // Add to the map.
                ssrcToRtxSsrc.Add(ssrc, null);
            }

            List <RtpEncodingParameters> encodings = new();

            foreach (var item in ssrcToRtxSsrc)
            {
                RtpEncodingParameters encoding = new();
                encoding.Ssrc = item.Key;
                if (item.Value is not null)
                {
                    encoding.Rtx = new() { Ssrc = item.Value }
                }
                ;
                encodings.Add(encoding);
            }

            return(encodings.ToArray());
        }