Skip to content

Commit d33261e

Browse files
authored
Fix m-line ordering in SDP offers to match track insertion order (#1551)
1 parent 98a416f commit d33261e

File tree

3 files changed

+88
-37
lines changed

3 files changed

+88
-37
lines changed

src/SIPSorcery/net/RTP/RTPSession.cs

Lines changed: 16 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,7 @@ public class RTPSession : IMediaSession, IDisposable
111111
private string m_sdpSessionID = null; // Need to maintain the same SDP session ID for all offers and answers.
112112
private ulong m_sdpAnnouncementVersion = 0; // The SDP version needs to increase whenever the local SDP is modified (see https://tools.ietf.org/html/rfc6337#section-5.2.5).
113113
internal int m_rtpChannelsCount = 0; // Need to know the number of RTP Channels
114+
private int m_nextMediaInsertionOrder = 0; // Need to keep track of insertion order of different media for SDP renegotiation.
114115

115116
// The stream used for the underlying RTP session to create a single RTP channel that will
116117
// be used to multiplex all required media streams. (see addSingleTrack())
@@ -1033,6 +1034,7 @@ protected virtual AudioStream GetOrCreateAudioStream(int index)
10331034
else if (index == AudioStreamList.Count)
10341035
{
10351036
AudioStream audioStream = new AudioStream(rtpSessionConfig, index);
1037+
audioStream.MediaInsertionOrder = Interlocked.Increment(ref m_nextMediaInsertionOrder);
10361038
AudioStreamList.Add(audioStream);
10371039
return audioStream;
10381040
}
@@ -1049,6 +1051,7 @@ protected virtual VideoStream GetOrCreateVideoStream(int index)
10491051
else if (index == VideoStreamList.Count)
10501052
{
10511053
VideoStream videoStream = new VideoStream(rtpSessionConfig, index);
1054+
videoStream.MediaInsertionOrder = Interlocked.Increment(ref m_nextMediaInsertionOrder);
10521055
VideoStreamList.Add(videoStream);
10531056
return videoStream;
10541057
}
@@ -1064,6 +1067,7 @@ protected virtual TextStream GetOrCreateTextStream(int index)
10641067
else if (index == TextStreamList.Count)
10651068
{
10661069
TextStream textStream = new TextStream(rtpSessionConfig, index);
1070+
textStream.MediaInsertionOrder = Interlocked.Increment(ref m_nextMediaInsertionOrder);
10671071
TextStreamList.Add(textStream);
10681072
return textStream;
10691073
}
@@ -2256,51 +2260,26 @@ protected virtual RTPChannel CreateRtpChannel()
22562260
/// <returns>A list of the local tracks that have been added to this session.</returns>
22572261
protected List<MediaStream> GetMediaStreams()
22582262
{
2259-
List<MediaStream> mediaStream = new List<MediaStream>();
2263+
List<MediaStream> mediaStreams = new List<MediaStream>();
22602264

2261-
foreach (var audioStream in AudioStreamList)
2262-
{
2263-
if (audioStream.LocalTrack != null)
2264-
{
2265-
mediaStream.Add(audioStream);
2266-
}
2267-
else if (audioStream.RtcpSession != null && !audioStream.RtcpSession.IsClosed && audioStream.RemoteTrack != null)
2268-
{
2269-
var inactiveAudioTrack = new MediaStreamTrack(audioStream.MediaType, false, audioStream.RemoteTrack.Capabilities, MediaStreamStatusEnum.Inactive);
2270-
audioStream.LocalTrack = inactiveAudioTrack;
2271-
mediaStream.Add(audioStream);
2272-
}
2273-
}
2274-
2275-
foreach (var videoStream in VideoStreamList)
2276-
{
2277-
if (videoStream.LocalTrack != null)
2278-
{
2279-
mediaStream.Add(videoStream);
2280-
}
2281-
else if (videoStream.RtcpSession != null && !videoStream.RtcpSession.IsClosed && videoStream.RemoteTrack != null)
2282-
{
2283-
var inactiveAudioTrack = new MediaStreamTrack(videoStream.MediaType, false, videoStream.RemoteTrack.Capabilities, MediaStreamStatusEnum.Inactive);
2284-
videoStream.LocalTrack = inactiveAudioTrack;
2285-
mediaStream.Add(videoStream);
2286-
}
2287-
}
2288-
2289-
foreach (var textstram in TextStreamList)
2265+
foreach (var stream in AudioStreamList
2266+
.Concat<MediaStream>(VideoStreamList)
2267+
.Concat(TextStreamList))
22902268
{
2291-
if (textstram.LocalTrack != null)
2269+
if (stream.LocalTrack != null)
22922270
{
2293-
mediaStream.Add(textstram);
2271+
mediaStreams.Add(stream);
22942272
}
2295-
else if (textstram.RtcpSession != null && !textstram.RtcpSession.IsClosed && textstram.RemoteTrack != null)
2273+
else if (stream.RtcpSession != null && !stream.RtcpSession.IsClosed && stream.RemoteTrack != null)
22962274
{
2297-
var inactiveTextTrack = new MediaStreamTrack(textstram.MediaType, false, textstram.RemoteTrack.Capabilities, MediaStreamStatusEnum.Inactive);
2298-
textstram.LocalTrack = inactiveTextTrack;
2299-
mediaStream.Add(textstram);
2275+
var inactiveTrack = new MediaStreamTrack(stream.MediaType, false, stream.RemoteTrack.Capabilities, MediaStreamStatusEnum.Inactive);
2276+
stream.LocalTrack = inactiveTrack;
2277+
mediaStreams.Add(stream);
23002278
}
23012279
}
23022280

2303-
return mediaStream;
2281+
mediaStreams.Sort((a, b) => a.MediaInsertionOrder.CompareTo(b.MediaInsertionOrder));
2282+
return mediaStreams;
23042283
}
23052284

23062285
/// <summary>

src/SIPSorcery/net/RTP/Streams/MediaStream.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,12 @@ public PendingPackages(RTPHeader hdr, int localPort, IPEndPoint remoteEndPoint,
6868

6969
public int Index = -1;
7070

71+
/// <summary>
72+
/// Tracks the global order in which this stream was added to the session,
73+
/// across all media types. Used to preserve m-line ordering per RFC 3264 §8.
74+
/// </summary>
75+
public int MediaInsertionOrder = -1;
76+
7177
/// <summary>
7278
/// Fires when the connection for a media type is classified as timed out due to not
7379
/// receiving any RTP or RTCP packets within the given period.

test/unit/net/RTP/RTPSessionUnitTest.cs

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -606,5 +606,71 @@ public void ModifiedWellKnownFormatIDUnitTest()
606606

607607
rtpSession.Close("normal");
608608
}
609+
610+
/// <summary>
611+
/// Tests that when video is added before audio, the SDP offer preserves that order
612+
/// (video m-line first, audio m-line second) per RFC 3264 §8.
613+
/// </summary>
614+
[Fact]
615+
public void OfferMLineOrderVideoFirstThenAudio()
616+
{
617+
logger.LogDebug("--> {MethodName}", System.Reflection.MethodBase.GetCurrentMethod().Name);
618+
logger.BeginScope(System.Reflection.MethodBase.GetCurrentMethod().Name);
619+
620+
RTPSession rtpSession = new RTPSession(false, false, false);
621+
622+
// Add video first.
623+
MediaStreamTrack videoTrack = new MediaStreamTrack(SDPMediaTypesEnum.video, false,
624+
new List<SDPAudioVideoMediaFormat> { new SDPAudioVideoMediaFormat(SDPMediaTypesEnum.video, 96, "VP8", 90000) });
625+
rtpSession.addTrack(videoTrack);
626+
627+
// Add audio second.
628+
MediaStreamTrack audioTrack = new MediaStreamTrack(SDPMediaTypesEnum.audio, false,
629+
new List<SDPAudioVideoMediaFormat> { new SDPAudioVideoMediaFormat(SDPWellKnownMediaFormatsEnum.PCMU) });
630+
rtpSession.addTrack(audioTrack);
631+
632+
var offer = rtpSession.CreateOffer(IPAddress.Loopback);
633+
634+
logger.LogDebug("Offer SDP:\n{Sdp}", offer);
635+
636+
Assert.Equal(2, offer.Media.Count);
637+
Assert.Equal(SDPMediaTypesEnum.video, offer.Media[0].Media);
638+
Assert.Equal(SDPMediaTypesEnum.audio, offer.Media[1].Media);
639+
640+
rtpSession.Close("normal");
641+
}
642+
643+
/// <summary>
644+
/// Tests that the traditional audio-then-video order is preserved when tracks are added
645+
/// in that order (backward compatibility).
646+
/// </summary>
647+
[Fact]
648+
public void OfferMLineOrderAudioFirstThenVideo()
649+
{
650+
logger.LogDebug("--> {MethodName}", System.Reflection.MethodBase.GetCurrentMethod().Name);
651+
logger.BeginScope(System.Reflection.MethodBase.GetCurrentMethod().Name);
652+
653+
RTPSession rtpSession = new RTPSession(false, false, false);
654+
655+
// Add audio first.
656+
MediaStreamTrack audioTrack = new MediaStreamTrack(SDPMediaTypesEnum.audio, false,
657+
new List<SDPAudioVideoMediaFormat> { new SDPAudioVideoMediaFormat(SDPWellKnownMediaFormatsEnum.PCMU) });
658+
rtpSession.addTrack(audioTrack);
659+
660+
// Add video second.
661+
MediaStreamTrack videoTrack = new MediaStreamTrack(SDPMediaTypesEnum.video, false,
662+
new List<SDPAudioVideoMediaFormat> { new SDPAudioVideoMediaFormat(SDPMediaTypesEnum.video, 96, "VP8", 90000) });
663+
rtpSession.addTrack(videoTrack);
664+
665+
var offer = rtpSession.CreateOffer(IPAddress.Loopback);
666+
667+
logger.LogDebug("Offer SDP:\n{Sdp}", offer);
668+
669+
Assert.Equal(2, offer.Media.Count);
670+
Assert.Equal(SDPMediaTypesEnum.audio, offer.Media[0].Media);
671+
Assert.Equal(SDPMediaTypesEnum.video, offer.Media[1].Media);
672+
673+
rtpSession.Close("normal");
674+
}
609675
}
610676
}

0 commit comments

Comments
 (0)