コード例 #1
0
ファイル: MediaSection.cs プロジェクト: melihercan/WebRTCme
        protected MediaSection(IceParameters iceParameters, IceCandidate[] iceCandidates, 
            DtlsParameters dtlsParameters, bool planB)
        {
            _iceParameters = iceParameters;
            _iceCandidates = iceCandidates;
            _dtlsParameters = dtlsParameters;
            _planB = planB;

            _mediaObject = new()
            {
                MediaDescription = new()
                {
                    Attributes = new()
                }
            };

            if (iceParameters is not null)
            {
                SetIceParameters(iceParameters);
            }
            
            if (iceCandidates is not null)
            {
                _mediaObject.MediaDescription.Attributes.Candidates = new List<Candidate>();
                foreach (var iceCandidate in iceCandidates)
                {
                    Candidate candidate = new()
                    {
                        Foundation = iceCandidate.Foundation,
                        ComponentId = 1,    // RTP
                        Transport = iceCandidate.Protocol.ToSdp(), 
                        Priority = iceCandidate.Priority,
                        ConnectionAddress = iceCandidate.Ip,
                        Port = iceCandidate.Port,
                        Type = iceCandidate.Type.ToSdp()
                    };
                    _mediaObject.MediaDescription.Attributes.Candidates.Add(candidate);
                }
            }

            _mediaObject.MediaDescription.Attributes.EndOfCandidates = true;
            _mediaObject.MediaDescription.Attributes.IceOptions = 
                new IceOptions { Tags = new string[] { "renomination" } };

            if (dtlsParameters is not null)
            {
                SetDtlsRole(dtlsParameters.Role);
            }
        }
コード例 #2
0
        private async void CallBackConnectServer2(IntPtr param1, IntPtr param2)
        {
            try
            {
                string         dtls           = Marshal.PtrToStringAnsi(param2);
                DtlsParameters dtlsParameters = JsonConvert.DeserializeObject <DtlsParameters>(dtls);

                ConnectWebRtcTransportRequest request = new ConnectWebRtcTransportRequest();
                request.TransportId    = Marshal.PtrToStringAnsi(param1);
                request.DtlsParameters = dtlsParameters;
                var result = await connection.InvokeAsync <dynamic>("ConnectWebRtcTransport", request);

                int i = 0;
            }
            catch (Exception ex)
            {
                messagesList.Items.Add(ex.ToString());
            }
        }
コード例 #3
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;
            }
            }
        }
コード例 #4
0
        //readonly SctpParameters _sctpParameters;
        //readonly PlainRtpParameters _plainRtpParameters;
        //readonly Mid _mid;
        //readonly MediaKind _kind;
        //RtpParameters

        public OfferMediaSection(
            IceParameters iceParameters,
            IceCandidate[] iceCandidates,
            DtlsParameters dtlsParameters,
            SctpParameters sctpParameters,
            PlainRtpParameters plainRtpParameters,
            bool planB,
            Mid mid,
            MediaKind kind,
            RtpParameters offerRtpParameters,
            string streamId,
            string trackId,
            bool oldDataChannelSpec) : base(iceParameters, iceCandidates, dtlsParameters, planB)
        {
            _mediaObject.MediaDescription.Attributes.Mid = mid;
            _mediaObject.MediaDescription.Media          = kind.ToSdp();

            if (plainRtpParameters is null)
            {
                _mediaObject.MediaDescription.ConnectionData = new ConnectionData
                {
                    NetType           = NetType.Internet,
                    AddrType          = AddrType.Ip4,
                    ConnectionAddress = "127.0.0.1"
                };
                if (sctpParameters is null)
                {
                    _mediaObject.MediaDescription.Proto = "UDP/TLS/RTP/SAVPF";
                }
                else
                {
                    _mediaObject.MediaDescription.Proto = "UDP/DTLS/SCTP";
                }
                _mediaObject.MediaDescription.Port = 7;
            }
            else
            {
                _mediaObject.MediaDescription.ConnectionData = new ConnectionData
                {
                    NetType           = NetType.Internet,
                    AddrType          = plainRtpParameters.IpVersion,
                    ConnectionAddress = plainRtpParameters.Ip
                };
                _mediaObject.MediaDescription.Proto = "RTP/AVP";
                _mediaObject.MediaDescription.Port  = plainRtpParameters.Port;
            }

            switch (kind)
            {
            case MediaKind.Audio:
            case MediaKind.Video:
            {
                _mediaObject.MediaDescription.Attributes.SendOnly = true;
                _mediaObject.MediaDescription.Attributes.Rtpmaps  = new List <Rtpmap>();
                _mediaObject.MediaDescription.Attributes.RtcpFbs  = new List <RtcpFb>();
                _mediaObject.MediaDescription.Attributes.Fmtps    = new List <Fmtp>();

                if (!_planB)
                {
                    _mediaObject.MediaDescription.Attributes.Msid = new()
                    {
                        Id      = streamId ?? "-",
                        AppData = trackId
                    }
                }
                ;

                foreach (var codec in offerRtpParameters.Codecs)
                {
                    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);

                    Fmtp fmtp = codec.Parameters.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(offerRtpParameters.Codecs
                                                                             .Select(codec => codec.PayloadType.ToString()));


                _mediaObject.MediaDescription.Attributes.Extmaps = new List <Extmap>();
                foreach (var headerExtension in offerRtpParameters.HeaderExtensions)
                {
                    Extmap ext = new()
                    {
                        Uri   = new Uri(headerExtension.Uri),
                        Value = headerExtension.Id
                    };
                    _mediaObject.MediaDescription.Attributes.Extmaps.Add(ext);
                }

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

                var encoding = offerRtpParameters.Encodings[0];
                var ssrc     = encoding.Ssrc;
                var rtxSsrc  = (encoding.Rtx is not null && encoding.Rtx.Ssrc.HasValue) ?
                               encoding.Rtx.Ssrc : null;

                _mediaObject.MediaDescription.Attributes.Ssrcs      = new List <Ssrc>();
                _mediaObject.MediaDescription.Attributes.SsrcGroups = new List <SsrcGroup>();
                if (offerRtpParameters.Rtcp.Cname is not null)
                {
                    _mediaObject.MediaDescription.Attributes.Ssrcs.Add(new Ssrc
                        {
                            Id        = (uint)ssrc,
                            Attribute = "cname",
                            Value     = offerRtpParameters.Rtcp.Cname
                        });
                }

                if (_planB)
                {
                    _mediaObject.MediaDescription.Attributes.Ssrcs.Add(new Ssrc
                        {
                            Id        = (uint)ssrc,
                            Attribute = "msid",
                            Value     = $"{streamId ?? "-"} {trackId}"
                        });
                }

                if (rtxSsrc.HasValue)
                {
                    if (offerRtpParameters.Rtcp.Cname is not null)
                    {
                        _mediaObject.MediaDescription.Attributes.Ssrcs.Add(new Ssrc
                            {
                                Id        = (uint)rtxSsrc,
                                Attribute = "cname",
                                Value     = offerRtpParameters.Rtcp.Cname
                            });
                    }

                    if (_planB)
                    {
                        _mediaObject.MediaDescription.Attributes.Ssrcs.Add(new Ssrc
                            {
                                Id        = (uint)rtxSsrc,
                                Attribute = "msid",
                                Value     = $"{streamId ?? "-"} {trackId}"
                            });
                    }

                    // Associate original and retransmission SSRCs.
                    _mediaObject.MediaDescription.Attributes.SsrcGroups.Add(new SsrcGroup
                        {
                            Semantics = "FID",
                            SsrcIds   = new string[] { $"{ssrc} {rtxSsrc}" }
                        });
                }

                break;
            }

            case MediaKind.Application:
            {
                // New spec.
                if (!oldDataChannelSpec)
                {
                    _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
                {
                    _mediaObject.MediaDescription.Fmts = new List <string> {
                        sctpParameters.Port.ToString()
                    };
                    _mediaObject.SctpMap = new()
                    {
                        App            = "webrtc-datachannel",
                        SctpMapNumber  = sctpParameters.Port,
                        MaxMessageSize = sctpParameters.MaxMessageSize
                    };
                }

                break;
            }
            }
        }

        void PlanBReceive(RtpParameters offerRtpParameters, string streamId, IMediaStream trackId)
        {
            var encoding = offerRtpParameters.Encodings[0];
            var ssrc     = encoding.Ssrc;
            var rtxSsrc  = (encoding.Rtx is not null && encoding.Rtx.Ssrc.HasValue) ?
                           encoding.Rtx.Ssrc : null;
            var payloads = _mediaObject.MediaDescription.Fmts.ToArray();


            foreach (var codec in offerRtpParameters.Codecs)
            {
                if (payloads.Any(payload => payload.Contains(codec.PayloadType.ToString())))
                {
                    continue;
                }

                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);

                Fmtp fmtp = codec.Parameters.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);
                }
            }

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


            if (offerRtpParameters.Rtcp.Cname is not null)
            {
                _mediaObject.MediaDescription.Attributes.Ssrcs.Add(new Ssrc
                {
                    Id        = (uint)ssrc,
                    Attribute = "cname",
                    Value     = offerRtpParameters.Rtcp.Cname
                });
            }

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

            if (rtxSsrc.HasValue)
            {
                if (offerRtpParameters.Rtcp.Cname is not null)
                {
                    _mediaObject.MediaDescription.Attributes.Ssrcs.Add(new Ssrc
                    {
                        Id        = (uint)rtxSsrc,
                        Attribute = "cname",
                        Value     = offerRtpParameters.Rtcp.Cname
                    });
                }

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

                // Associate original and retransmission SSRCs.
                _mediaObject.MediaDescription.Attributes.SsrcGroups.Add(new SsrcGroup
                {
                    Semantics = "FID",
                    SsrcIds   = new string[] { $"{ssrc} {rtxSsrc}" }
                });
            }
        }

        public void PlanBReceive(RtpParameters offerRtpParameters, string streamId, string trackId)
        {
            var encoding = offerRtpParameters.Encodings[0];
            var ssrc     = encoding.Ssrc;
            var rtxSsrc  = (encoding.Rtx is not null && encoding.Rtx.Ssrc.HasValue)
                ? encoding.Rtx.Ssrc
                : null;
            var payloads = _mediaObject.MediaDescription.Fmts.ToArray();

            foreach (var codec in offerRtpParameters.Codecs)
            {
                if (payloads.Contains(codec.PayloadType.ToString()))
                {
                    continue;
                }

                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);

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

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

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

            ((List <string>)_mediaObject.MediaDescription.Fmts).AddRange(offerRtpParameters.Codecs
                                                                         .Where(codec => !_mediaObject.MediaDescription.Fmts.Contains(codec.PayloadType.ToString()))
                                                                         .Select(codec => codec.PayloadType.ToString())
                                                                         .ToArray());

            if (offerRtpParameters.Rtcp.Cname is not null)
            {
                _mediaObject.MediaDescription.Attributes.Ssrcs.Add(new Ssrc
                {
                    Id        = (uint)ssrc,
                    Attribute = "cname",
                    Value     = offerRtpParameters.Rtcp.Cname
                });
            }

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

            if (rtxSsrc is not null)
            {
                if (offerRtpParameters.Rtcp.Cname is not null)
                {
                    _mediaObject.MediaDescription.Attributes.Ssrcs.Add(new Ssrc
                    {
                        Id        = (uint)rtxSsrc,
                        Attribute = "cname",
                        Value     = offerRtpParameters.Rtcp.Cname
                    });
                }

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

                // Associate original and retransmission SSRCs.
                _mediaObject.MediaDescription.Attributes.SsrcGroups.Add(new SsrcGroup
                {
                    Semantics = "FID",
                    SsrcIds   = new string[] { $"{ ssrc }${rtxSsrc}" }
                });
            }
        }

        void PlanBStopReceiving(RtpParameters offerRtpParameters)
        {
            var encoding = offerRtpParameters.Encodings[0];
            var ssrc     = encoding.Ssrc;
            var rtxSsrc  = (encoding.Rtx is not null && encoding.Rtx.Ssrc.HasValue) ?
                           encoding.Rtx.Ssrc : null;

            ((List <Ssrc>)_mediaObject.MediaDescription.Attributes.Ssrcs).RemoveAll(s => s.Id != ssrc && s.Id != rtxSsrc);

            if (rtxSsrc is not null)
            {
                ((List <SsrcGroup>)_mediaObject.MediaDescription.Attributes.SsrcGroups).RemoveAll(g => g.SsrcIds.Any(id => id != $"{ssrc} {rtxSsrc}"));
            }
        }

        public override void SetDtlsRole(DtlsRole?dtlsRole)