Skip to content

Commit befb54d

Browse files
authored
Fixed RTP event negotiation to set common ID if remote party is not using 101. Resolves #1251. (#1280)
1 parent dedc327 commit befb54d

7 files changed

Lines changed: 152 additions & 55 deletions

File tree

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
//-----------------------------------------------------------------------------
2+
// Filename: Program.cs
3+
//
4+
// Description: Sample program of how to receive DTMF tones using RTP events
5+
// as specified in RFC2833.
6+
//
7+
// Author(s):
8+
// Aaron Clauson ([email protected])
9+
//
10+
// History:
11+
// 12 Jan 2025 Aaron Clauson Created, Dublin, Ireland.
12+
//
13+
// License:
14+
// BSD 3-Clause "New" or "Revised" License, see included LICENSE.md file.
15+
//-----------------------------------------------------------------------------
16+
17+
using System;
18+
using System.Linq;
19+
using System.Net;
20+
using Microsoft.Extensions.Logging;
21+
using Microsoft.Extensions.Logging.Abstractions;
22+
using Serilog;
23+
using Serilog.Extensions.Logging;
24+
using SIPSorcery.Media;
25+
using SIPSorcery.Net;
26+
using SIPSorcery.SIP;
27+
using SIPSorcery.SIP.App;
28+
using TinyJson;
29+
30+
namespace demo
31+
{
32+
class Program
33+
{
34+
private static int SIP_LISTEN_PORT = 5060;
35+
36+
private static Microsoft.Extensions.Logging.ILogger Log = NullLogger.Instance;
37+
38+
private static SIPTransport _sipTransport;
39+
40+
static void Main()
41+
{
42+
Console.WriteLine("SIPSorcery Receive DTMF Demo");
43+
44+
Log = AddConsoleLogger();
45+
46+
_sipTransport = new SIPTransport();
47+
_sipTransport.AddSIPChannel(new SIPUDPChannel(new IPEndPoint(IPAddress.Any, SIP_LISTEN_PORT)));
48+
49+
var userAgent = new SIPUserAgent(_sipTransport, null, true);
50+
userAgent.ServerCallCancelled += (uas, cancelReq) => Log.LogDebug("Incoming call cancelled by remote party.");
51+
userAgent.OnCallHungup += (dialog) => Log.LogDebug("Call hungup.");
52+
userAgent.OnIncomingCall += async (ua, req) =>
53+
{
54+
Log.LogDebug($"SDP Offer:\r\n{req.Body}");
55+
56+
VoIPMediaSession voipMediaSession = new VoIPMediaSession();
57+
voipMediaSession.AcceptRtpFromAny = true;
58+
59+
var uas = userAgent.AcceptCall(req);
60+
61+
await userAgent.Answer(uas, voipMediaSession);
62+
63+
var sdpAnswer = voipMediaSession.CreateAnswer(IPAddress.Loopback);
64+
65+
Log.LogDebug($"SDP Answer:\r\n{sdpAnswer.ToString()}");
66+
};
67+
userAgent.OnDtmfTone += (tone, duration) => Log.LogInformation($"DTMF tone {tone} of duration {duration}ms received.");
68+
69+
Console.WriteLine("press any key to exit...");
70+
Console.Read();
71+
72+
// Clean up.
73+
_sipTransport.Shutdown();
74+
}
75+
76+
/// <summary>
77+
/// Adds a console logger. Can be omitted if internal SIPSorcery debug and warning messages are not required.
78+
/// </summary>
79+
private static Microsoft.Extensions.Logging.ILogger AddConsoleLogger()
80+
{
81+
var serilogLogger = new LoggerConfiguration()
82+
.Enrich.FromLogContext()
83+
.MinimumLevel.Is(Serilog.Events.LogEventLevel.Debug)
84+
.WriteTo.Console()
85+
.CreateLogger();
86+
var factory = new SerilogLoggerFactory(serilogLogger);
87+
SIPSorcery.LogFactory.Set(factory);
88+
return factory.CreateLogger<Program>();
89+
}
90+
}
91+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
3+
<PropertyGroup>
4+
<OutputType>Exe</OutputType>
5+
<TargetFramework>net8.0-windows10.0.17763.0</TargetFramework>
6+
</PropertyGroup>
7+
8+
<ItemGroup>
9+
<PackageReference Include="Microsoft.Extensions.Logging" Version="9.0.0" />
10+
<PackageReference Include="NAudio" Version="2.2.1" />
11+
<PackageReference Include="Serilog.Extensions.Logging" Version="9.0.0" />
12+
<PackageReference Include="Serilog.Sinks.Console" Version="6.0.0" />
13+
<PackageReference Include="System.Net.Security" Version="4.3.2" />
14+
</ItemGroup>
15+
16+
<ItemGroup>
17+
<ProjectReference Include="..\..\..\src\SIPSorcery.csproj" />
18+
</ItemGroup>
19+
20+
</Project>

examples/SIPExamples/SIPExamples.sln

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NotifierClient", "NotifierC
4343
EndProject
4444
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SIPSorcery", "..\..\src\SIPSorcery.csproj", "{DE20DB0F-AB95-47DD-B51F-A2A7EC12FA01}"
4545
EndProject
46+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ReceiveDtmf", "ReceiveDtmf\ReceiveDtmf.csproj", "{428BFA9A-F082-4444-9E04-30AD60B5E794}"
47+
EndProject
4648
Global
4749
GlobalSection(SolutionConfigurationPlatforms) = preSolution
4850
Debug|Any CPU = Debug|Any CPU
@@ -293,6 +295,18 @@ Global
293295
{DE20DB0F-AB95-47DD-B51F-A2A7EC12FA01}.Release|x64.Build.0 = Release|Any CPU
294296
{DE20DB0F-AB95-47DD-B51F-A2A7EC12FA01}.Release|x86.ActiveCfg = Release|Any CPU
295297
{DE20DB0F-AB95-47DD-B51F-A2A7EC12FA01}.Release|x86.Build.0 = Release|Any CPU
298+
{428BFA9A-F082-4444-9E04-30AD60B5E794}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
299+
{428BFA9A-F082-4444-9E04-30AD60B5E794}.Debug|Any CPU.Build.0 = Debug|Any CPU
300+
{428BFA9A-F082-4444-9E04-30AD60B5E794}.Debug|x64.ActiveCfg = Debug|Any CPU
301+
{428BFA9A-F082-4444-9E04-30AD60B5E794}.Debug|x64.Build.0 = Debug|Any CPU
302+
{428BFA9A-F082-4444-9E04-30AD60B5E794}.Debug|x86.ActiveCfg = Debug|Any CPU
303+
{428BFA9A-F082-4444-9E04-30AD60B5E794}.Debug|x86.Build.0 = Debug|Any CPU
304+
{428BFA9A-F082-4444-9E04-30AD60B5E794}.Release|Any CPU.ActiveCfg = Release|Any CPU
305+
{428BFA9A-F082-4444-9E04-30AD60B5E794}.Release|Any CPU.Build.0 = Release|Any CPU
306+
{428BFA9A-F082-4444-9E04-30AD60B5E794}.Release|x64.ActiveCfg = Release|Any CPU
307+
{428BFA9A-F082-4444-9E04-30AD60B5E794}.Release|x64.Build.0 = Release|Any CPU
308+
{428BFA9A-F082-4444-9E04-30AD60B5E794}.Release|x86.ActiveCfg = Release|Any CPU
309+
{428BFA9A-F082-4444-9E04-30AD60B5E794}.Release|x86.Build.0 = Release|Any CPU
296310
EndGlobalSection
297311
GlobalSection(SolutionProperties) = preSolution
298312
HideSolutionNode = FALSE

examples/SIPExamples/SendDtmf/Program.cs

100644100755
Lines changed: 8 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
//-----------------------------------------------------------------------------
22
// Filename: Program.cs
33
//
4-
// Description: An example of how top send DTMF tones in band (with specific RTP
4+
// Description: An example of how to send DTMF tones in band (with specific RTP
55
// packets) as specified in RFC2833.
66
//
77
// Author(s):
@@ -20,7 +20,7 @@
2020
//
2121
// This example calls a destination (represented by DEFAULT_DESTINATION_SIP_URI)
2222
// and expects a SIP agent capable of receiving DTMF with RFC2833 to be listening.
23-
// What the receiving SIP does with the received DTMF is up to it. A good example
23+
// What the receiving SIP ageent does with the received DTMF is up to it. A good example
2424
// is to playback the presses via speech synthesis. The dialplan below is an
2525
// example of how to do that with Asterisk.
2626
//
@@ -43,16 +43,16 @@
4343
using Serilog;
4444
using Serilog.Extensions.Logging;
4545
using SIPSorcery.Media;
46+
using SIPSorcery.Net;
4647
using SIPSorcery.SIP;
4748
using SIPSorcery.SIP.App;
48-
using SIPSorceryMedia.Windows;
4949

5050
namespace SIPSorcery
5151
{
5252
class Program
5353
{
5454
//private static readonly string DEFAULT_DESTINATION_SIP_URI = "sip:*[email protected]"; // Custom Asterisk dialplan to speak back DTMF tones.
55-
private static readonly string DEFAULT_DESTINATION_SIP_URI = "sip:[email protected]:5080";
55+
private static readonly string DEFAULT_DESTINATION_SIP_URI = "sip:[email protected]:5060";
5656

5757
private static Microsoft.Extensions.Logging.ILogger Log = NullLogger.Instance;
5858

@@ -68,8 +68,7 @@ static async Task Main()
6868

6969
var sipTransport = new SIPTransport();
7070
var userAgent = new SIPUserAgent(sipTransport, null);
71-
var winAudioEP = new WindowsAudioEndPoint(new AudioEncoder());
72-
var voipMediaSession = new VoIPMediaSession(winAudioEP.ToMediaEndPoints());
71+
var voipMediaSession = new VoIPMediaSession();
7372

7473
Console.WriteLine($"Calling {DEFAULT_DESTINATION_SIP_URI}.");
7574

@@ -91,10 +90,13 @@ static async Task Main()
9190
await Task.Delay(1000);
9291

9392
// Send the DTMF tones.
93+
Console.WriteLine("Sending DTMF tone 5.");
9494
await userAgent.SendDtmf(0x05);
9595
await Task.Delay(2000);
96+
Console.WriteLine("Sending DTMF tone 9.");
9697
await userAgent.SendDtmf(0x09);
9798
await Task.Delay(2000);
99+
Console.WriteLine("Sending DTMF tone 2.");
98100
await userAgent.SendDtmf(0x02);
99101
await Task.Delay(2000);
100102

@@ -116,46 +118,6 @@ static async Task Main()
116118
sipTransport.Shutdown();
117119
}
118120

119-
/// <summary>
120-
/// Enable detailed SIP log messages.
121-
/// </summary>
122-
private static void EnableTraceLogs(SIPTransport sipTransport)
123-
{
124-
sipTransport.SIPRequestInTraceEvent += (localEP, remoteEP, req) =>
125-
{
126-
Log.LogDebug($"Request received: {localEP}<-{remoteEP}");
127-
Log.LogDebug(req.ToString());
128-
};
129-
130-
sipTransport.SIPRequestOutTraceEvent += (localEP, remoteEP, req) =>
131-
{
132-
Log.LogDebug($"Request sent: {localEP}->{remoteEP}");
133-
Log.LogDebug(req.ToString());
134-
};
135-
136-
sipTransport.SIPResponseInTraceEvent += (localEP, remoteEP, resp) =>
137-
{
138-
Log.LogDebug($"Response received: {localEP}<-{remoteEP}");
139-
Log.LogDebug(resp.ToString());
140-
};
141-
142-
sipTransport.SIPResponseOutTraceEvent += (localEP, remoteEP, resp) =>
143-
{
144-
Log.LogDebug($"Response sent: {localEP}->{remoteEP}");
145-
Log.LogDebug(resp.ToString());
146-
};
147-
148-
sipTransport.SIPRequestRetransmitTraceEvent += (tx, req, count) =>
149-
{
150-
Log.LogDebug($"Request retransmit {count} for request {req.StatusLine}, initial transmit {DateTime.Now.Subtract(tx.InitialTransmit).TotalSeconds.ToString("0.###")}s ago.");
151-
};
152-
153-
sipTransport.SIPResponseRetransmitTraceEvent += (tx, resp, count) =>
154-
{
155-
Log.LogDebug($"Response retransmit {count} for response {resp.ShortDescription}, initial transmit {DateTime.Now.Subtract(tx.InitialTransmit).TotalSeconds.ToString("0.###")}s ago.");
156-
};
157-
}
158-
159121
/// <summary>
160122
/// Adds a console logger. Can be omitted if internal SIPSorcery debug and warning messages are not required.
161123
/// </summary>

examples/SIPExamples/SendDtmf/SendDtmf.csproj

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,13 @@
22

33
<PropertyGroup>
44
<OutputType>Exe</OutputType>
5-
<TargetFramework>net8.0-windows10.0.17763.0</TargetFramework>
5+
<TargetFramework>net8</TargetFramework>
66
</PropertyGroup>
77

88
<ItemGroup>
99
<PackageReference Include="Microsoft.Extensions.Logging" Version="9.0.0" />
1010
<PackageReference Include="Serilog.Extensions.Logging" Version="9.0.0" />
1111
<PackageReference Include="Serilog.Sinks.Console" Version="6.0.0" />
12-
<PackageReference Include="SIPSorceryMedia.Windows" Version="8.0.7" />
1312
<PackageReference Include="System.Net.Security" Version="4.3.2" />
1413
</ItemGroup>
1514

src/app/Media/VoIPMediaSession.cs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ public VideoTestPatternSource TestPatternSource
6969
/// Default constructor which creates the simplest possible send only audio session. It does not
7070
/// wire up any devices or video processing.
7171
/// </summary>
72-
public VoIPMediaSession(Func<AudioFormat, bool> restrictFormats = null) : base(false, false, false)
72+
public VoIPMediaSession(Func<AudioFormat, bool> restrictFormats = null, bool noDtmfSupport = false) : base(false, false, false)
7373
{
7474
_audioExtrasSource = new AudioExtrasSource();
7575
_audioExtrasSource.OnAudioSourceEncodedSample += SendAudio;
@@ -81,6 +81,7 @@ public VoIPMediaSession(Func<AudioFormat, bool> restrictFormats = null) : base(f
8181
}
8282

8383
var audioTrack = new MediaStreamTrack(_audioExtrasSource.GetAudioSourceFormats());
84+
audioTrack.NoDtmfSupport = noDtmfSupport;
8485
base.addTrack(audioTrack);
8586
base.OnAudioFormatsNegotiated += AudioFormatsNegotiated;
8687

@@ -185,6 +186,11 @@ private void AudioFormatsNegotiated(List<AudioFormat> audoFormats)
185186
logger.LogDebug($"Setting audio source format to {audioFormat.FormatID}:{audioFormat.Codec} {audioFormat.ClockRate} (RTP clock rate {audioFormat.RtpClockRate}).");
186187
Media.AudioSource?.SetAudioSourceFormat(audioFormat);
187188
_audioExtrasSource.SetAudioSourceFormat(audioFormat);
189+
190+
if(AudioStream != null && AudioStream.LocalTrack.NoDtmfSupport == false)
191+
{
192+
logger.LogDebug($"Audio track negotiated DTMF payload ID {AudioStream.NegotiatedRtpEventPayloadID}.");
193+
}
188194
}
189195

190196
private void VideoFormatsNegotiated(List<VideoFormat> videoFormats)

src/net/RTP/RTPSession.cs

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -175,7 +175,7 @@ public class RTPSession : IMediaSession, IDisposable
175175

176176
protected RtpSessionConfig rtpSessionConfig;
177177

178-
private Boolean m_acceptRtpFromAny = false;
178+
private bool m_acceptRtpFromAny = false;
179179
private string m_sdpSessionID = null; // Need to maintain the same SDP session ID for all offers and answers.
180180
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).
181181
internal int m_rtpChannelsCount = 0; // Need to know the number of RTP Channels
@@ -1050,17 +1050,17 @@ public virtual SetDescriptionResultEnum SetRemoteDescription(SdpType sdpType, SD
10501050
else
10511051
{
10521052
// As proved by Azure implementation, we need to send based on capabilities of remote track. Azure return SDP with only one possible Codec (H264 107)
1053-
// but we receive frames based on our LocalRemoteTracks, so its possiblet o receive a frame with ID 122, for exemple, even when remote annoucement only have 107
1053+
// but we receive frames based on our LocalRemoteTracks, so it's possible to receive a frame with ID 122, for exemple, even when remote annoucement only have 107
10541054
// Thats why we changed line below to keep local track capabilities untouched as we can always do it during send/receive moment
10551055
capabilities = SDPAudioVideoMediaFormat.GetCompatibleFormats(currentMediaStream.RemoteTrack?.Capabilities, currentMediaStream.LocalTrack?.Capabilities);
1056-
//Keep same order of LocalTrack priority to prevent incorrect sending format
1056+
// Keep same order of LocalTrack priority to prevent incorrect sending format
10571057
SDPAudioVideoMediaFormat.SortMediaCapability(capabilities, currentMediaStream.LocalTrack?.Capabilities);
10581058

10591059
currentMediaStream.RemoteTrack.Capabilities = capabilities;
10601060

1061-
// Keep the local track's RTP event capability
1061+
// Adjust the local track's RTP event capability if the remote party has specified a different payload ID.
10621062
var currentLocalTrackCapabilities = currentMediaStream.LocalTrack.Capabilities;
1063-
SDPAudioVideoMediaFormat localRTPEventCapabilities;
1063+
SDPAudioVideoMediaFormat? localRTPEventCapabilities = null;
10641064
if (currentLocalTrackCapabilities.Any(x => x.Name().ToLower() == SDP.TELEPHONE_EVENT_ATTRIBUTE))
10651065
{
10661066
localRTPEventCapabilities = currentLocalTrackCapabilities.First(x => x.Name().ToLower() == SDP.TELEPHONE_EVENT_ATTRIBUTE);
@@ -1071,7 +1071,10 @@ public virtual SetDescriptionResultEnum SetRemoteDescription(SdpType sdpType, SD
10711071
}
10721072

10731073
currentMediaStream.LocalTrack.Capabilities = capabilities.Where(x => x.Name().ToLower() != SDP.TELEPHONE_EVENT_ATTRIBUTE).ToList();
1074-
currentMediaStream.LocalTrack.Capabilities.Add(localRTPEventCapabilities);
1074+
if (localRTPEventCapabilities != null)
1075+
{
1076+
currentMediaStream.LocalTrack.Capabilities.Add(localRTPEventCapabilities.Value);
1077+
}
10751078

10761079
if (currentMediaStream.MediaType == SDPMediaTypesEnum.audio)
10771080
{
@@ -1080,6 +1083,8 @@ public virtual SetDescriptionResultEnum SetRemoteDescription(SdpType sdpType, SD
10801083
if (!commonEventFormat.IsEmpty())
10811084
{
10821085
currentMediaStream.NegotiatedRtpEventPayloadID = commonEventFormat.ID;
1086+
currentMediaStream.LocalTrack.Capabilities.RemoveAll(x => x.Name().ToLower() == SDP.TELEPHONE_EVENT_ATTRIBUTE);
1087+
currentMediaStream.LocalTrack.Capabilities.Add(commonEventFormat);
10831088
}
10841089
}
10851090

0 commit comments

Comments
 (0)