Skip to content

Commit 276cc78

Browse files
authored
Merge pull request #24 from jimm98y/features/srtp
F Added SAVP profile support
2 parents 98e924e + 1a97d8b commit 276cc78

File tree

16 files changed

+356
-45
lines changed

16 files changed

+356
-45
lines changed

src/SharpRTSPClient/RTSPClient.cs

Lines changed: 155 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,15 @@
44
using Rtsp.Onvif;
55
using Rtsp.Rtp;
66
using Rtsp.Sdp;
7+
using SharpSRTP.SRTP;
78
using System;
89
using System.Collections.Generic;
910
using System.Diagnostics;
1011
using System.IO;
1112
using System.Linq;
1213
using System.Net;
1314
using System.Net.Security;
15+
using System.Numerics;
1416
using System.Text;
1517

1618
namespace SharpRTSPClient
@@ -111,6 +113,9 @@ private enum RtspStatus { WaitingToConnect, Connecting, ConnectFailed, Connected
111113
/// Audio SSRC.
112114
/// </summary>
113115
public uint AudioSSRC { get; set; } = (uint)_rand.Next(20000, 29999);
116+
117+
public SrtpSessionContext VideoContext { get; private set; }
118+
public SrtpSessionContext AudioContext { get; private set; }
114119

115120
static RTSPClient()
116121
{
@@ -505,6 +510,13 @@ private void StopClient()
505510
/// <param name="rtcp">RTCP message bytes.</param>
506511
public void SendVideoRTCP(byte[] rtcp)
507512
{
513+
if (VideoContext != null)
514+
{
515+
byte[] rtcpBuffer = new byte[VideoContext.EncodeRtcpContext.CalculateRequiredSrtcpPayloadLength(rtcp.Length)];
516+
VideoContext.EncodeRtcpContext.ProtectRtcp(rtcpBuffer, rtcp.Length, out int len);
517+
rtcp = rtcpBuffer.Take(len).ToArray();
518+
}
519+
508520
_videoRtpTransport.WriteToControlPort(rtcp);
509521
}
510522

@@ -514,6 +526,13 @@ public void SendVideoRTCP(byte[] rtcp)
514526
/// <param name="rtcp">RTCP message bytes.</param>
515527
public void SendAudioRTCP(byte[] rtcp)
516528
{
529+
if (AudioContext != null)
530+
{
531+
byte[] rtcpBuffer = new byte[AudioContext.EncodeRtcpContext.CalculateRequiredSrtcpPayloadLength(rtcp.Length)];
532+
AudioContext.EncodeRtcpContext.ProtectRtcp(rtcpBuffer, rtcp.Length, out int len);
533+
rtcp = rtcpBuffer.Take(len).ToArray();
534+
}
535+
517536
_audioRtpTransport.WriteToControlPort(rtcp);
518537
}
519538

@@ -546,7 +565,22 @@ private void VideoRtpDataReceived(object sender, RtspDataEventArgs e)
546565

547566
using (var data = e.Data)
548567
{
549-
var rtpPacket = new RtpPacket(data.Data.Span);
568+
var rtpData = data.Data;
569+
if (VideoContext != null)
570+
{
571+
byte[] decoded = rtpData.ToArray();
572+
if (VideoContext.DecodeRtpContext.UnprotectRtp(decoded, decoded.Length, out var len) == 0)
573+
{
574+
rtpData = decoded.Take(len).ToArray().AsMemory();
575+
}
576+
else
577+
{
578+
_logger.LogError("Unprotect RTP failed");
579+
return;
580+
}
581+
}
582+
583+
var rtpPacket = new RtpPacket(rtpData.Span);
550584

551585
if (rtpPacket.PayloadType != _videoPayload)
552586
{
@@ -557,7 +591,7 @@ private void VideoRtpDataReceived(object sender, RtspDataEventArgs e)
557591

558592
ReceivedRawVideoRTP?.Invoke(this,
559593
new RawRtpDataEventArgs(
560-
data.Data,
594+
rtpData,
561595
rtpPacket.CsrcCount,
562596
rtpPacket.ExtensionHeaderId,
563597
rtpPacket.HasPadding,
@@ -608,8 +642,23 @@ private void AudioRtpDataReceived(object sender, RtspDataEventArgs e)
608642

609643
using (var data = e.Data)
610644
{
645+
var rtpData = data.Data;
646+
if (AudioContext != null)
647+
{
648+
byte[] decoded = rtpData.ToArray();
649+
if (AudioContext.DecodeRtpContext.UnprotectRtp(decoded, decoded.Length, out var len) == 0)
650+
{
651+
rtpData = decoded.Take(len).ToArray().AsMemory();
652+
}
653+
else
654+
{
655+
_logger.LogError("Unprotect RTP failed");
656+
return;
657+
}
658+
}
659+
611660
// Received some Audio Data on the correct channel.
612-
var rtpPacket = new RtpPacket(data.Data.Span);
661+
var rtpPacket = new RtpPacket(rtpData.Span);
613662

614663
// Check the payload type in the RTP packet matches the Payload Type value from the SDP
615664
if (rtpPacket.PayloadType != _audioPayload)
@@ -620,7 +669,7 @@ private void AudioRtpDataReceived(object sender, RtspDataEventArgs e)
620669

621670
ReceivedRawAudioRTP?.Invoke(this,
622671
new RawRtpDataEventArgs(
623-
data.Data,
672+
rtpData,
624673
rtpPacket.CsrcCount,
625674
rtpPacket.ExtensionHeaderId,
626675
rtpPacket.HasPadding,
@@ -666,12 +715,27 @@ private void VideoRtcpControlDataReceived(object sender, RtspDataEventArgs e)
666715

667716
using (var data = e.Data)
668717
{
669-
ReceivedRawVideoRTCP?.Invoke(this, new RawRtcpDataEventArgs(data.Data));
718+
var rtcpData = data.Data;
719+
if (VideoContext != null)
720+
{
721+
byte[] decoded = rtcpData.ToArray();
722+
if (VideoContext.DecodeRtcpContext.UnprotectRtcp(decoded, decoded.Length, out var len) == 0)
723+
{
724+
rtcpData = decoded.Take(len).ToArray().AsMemory();
725+
}
726+
else
727+
{
728+
_logger.LogError("Unprotect RTCP failed");
729+
return;
730+
}
731+
}
732+
733+
ReceivedRawVideoRTCP?.Invoke(this, new RawRtcpDataEventArgs(rtcpData));
670734

671735
if (!ProcessRTCP)
672736
return;
673737

674-
var reports = ParseRTCPAndGenerateReponse(data, VideoSSRC);
738+
var reports = ParseRTCPAndGenerateReponse(rtcpData, VideoSSRC);
675739
foreach (var report in reports)
676740
{
677741
((IRtpTransport)sender).WriteToControlPort(report);
@@ -688,20 +752,35 @@ private void AudioRtcpControlDataReceived(object sender, RtspDataEventArgs e)
688752

689753
using (var data = e.Data)
690754
{
755+
var rtcpData = data.Data;
756+
if (AudioContext != null)
757+
{
758+
byte[] decoded = rtcpData.ToArray();
759+
if (AudioContext.DecodeRtcpContext.UnprotectRtcp(decoded, decoded.Length, out var len) == 0)
760+
{
761+
rtcpData = decoded.Take(len).ToArray().AsMemory();
762+
}
763+
else
764+
{
765+
_logger.LogError("Unprotect RTCP failed");
766+
return;
767+
}
768+
}
769+
691770
ReceivedRawAudioRTCP?.Invoke(this, new RawRtcpDataEventArgs(data.Data));
692771

693772
if (!ProcessRTCP)
694773
return;
695774

696-
var reports = ParseRTCPAndGenerateReponse(data, AudioSSRC);
775+
var reports = ParseRTCPAndGenerateReponse(rtcpData, AudioSSRC);
697776
foreach (var report in reports)
698777
{
699778
((IRtpTransport)sender).WriteToControlPort(report);
700779
}
701780
}
702781
}
703782

704-
private List<byte[]> ParseRTCPAndGenerateReponse(RtspData data, uint ssrc)
783+
private List<byte[]> ParseRTCPAndGenerateReponse(Memory<byte> data, uint ssrc)
705784
{
706785
List<byte[]> reports = new List<byte[]>();
707786

@@ -714,9 +793,9 @@ private List<byte[]> ParseRTCPAndGenerateReponse(RtspData data, uint ssrc)
714793

715794
// There can be multiple RTCP packets transmitted together. Loop ever each one
716795
int packetIndex = 0;
717-
var span = data.Data.Span;
796+
var span = data.Span;
718797

719-
while (packetIndex < data.Data.Length)
798+
while (packetIndex < data.Length)
720799
{
721800
//int rtcpVersion = (span[packetIndex + 0] >> 6);
722801
//int rtcpPadding = (span[packetIndex + 0] >> 5) & 0x01;
@@ -1061,7 +1140,7 @@ private void HandleDescribeResponse(RtspResponse message)
10611140
{
10621141
// found a valid codec
10631142
payloadName = rtpmap.EncodingName.ToUpperInvariant();
1064-
switch(payloadName)
1143+
switch (payloadName)
10651144
{
10661145
case "H264":
10671146
_videoPayloadProcessor = new H264Payload(_loggerFactory.CreateLogger<H264Payload>());
@@ -1093,7 +1172,7 @@ private void HandleDescribeResponse(RtspResponse message)
10931172
if (media.PayloadType < 96)
10941173
{
10951174
// PayloadType is a static value, so we can use it to determine the codec
1096-
switch(media.PayloadType)
1175+
switch (media.PayloadType)
10971176
{
10981177
case 26:
10991178
{
@@ -1166,7 +1245,7 @@ private void HandleDescribeResponse(RtspResponse message)
11661245
byte[] pps = vpsSpsPps[3];
11671246
byte[] sei = vpsSpsPps[4];
11681247
streamConfigurationData = new H266StreamConfigurationData(dci, vps, sps, pps, sei);
1169-
}
1248+
}
11701249
}
11711250
else if (_videoPayloadProcessor is AV1Payload && fmtp?.FormatParameter != null)
11721251
{
@@ -1189,9 +1268,12 @@ private void HandleDescribeResponse(RtspResponse message)
11891268
setupMessage.AddTransport(transport);
11901269
setupMessage.AddAuthorization(_authentication, _uri, _rtspSocket.NextCommandIndex());
11911270
if (_playbackSession) { setupMessage.AddRequireOnvifRequest(); }
1192-
1271+
11931272
// Add SETUP message to list of mesages to send
11941273
_setupMessages.Enqueue(setupMessage);
1274+
1275+
VideoContext = PrepareSrtpContext(media);
1276+
11951277
NewVideoStream?.Invoke(this, new NewStreamEventArgs(media.PayloadType, payloadName, streamConfigurationData));
11961278
}
11971279
break;
@@ -1292,6 +1374,9 @@ private void HandleDescribeResponse(RtspResponse message)
12921374
}
12931375
// Add SETUP message to list of mesages to send
12941376
_setupMessages.Enqueue(setupMessage);
1377+
1378+
AudioContext = PrepareSrtpContext(media);
1379+
12951380
NewAudioStream?.Invoke(this, new NewStreamEventArgs(media.PayloadType, _audioCodec, streamConfigurationData));
12961381
}
12971382
break;
@@ -1310,6 +1395,62 @@ private void HandleDescribeResponse(RtspResponse message)
13101395
_rtspClient?.SendMessage(_setupMessages.Dequeue());
13111396
}
13121397

1398+
public virtual SrtpSessionContext PrepareSrtpContext(Media media)
1399+
{
1400+
if (media.RtpType != null && media.RtpType.EndsWith("/SAVP") || media.RtpType.EndsWith("/SAVPF"))
1401+
{
1402+
var crypto = media.Attributs.FirstOrDefault(x => x.Key == "crypto");
1403+
if (crypto != null)
1404+
{
1405+
byte[] MKI = null;
1406+
byte[] masterKeySalt = null;
1407+
1408+
string[] cryptoParts = crypto.Value.Split(' ');
1409+
if (cryptoParts.Length == 3)
1410+
{
1411+
string cryptoSuite = cryptoParts[1];
1412+
1413+
if (cryptoParts[2].StartsWith("inline:"))
1414+
{
1415+
string[] inlineParts = cryptoParts[2].Substring(7).Split('|');
1416+
masterKeySalt = Convert.FromBase64String(inlineParts[0]);
1417+
if (inlineParts.Length > 1)
1418+
{
1419+
if (inlineParts.Length > 2)
1420+
{
1421+
string lifetime = inlineParts[1];
1422+
MKI = ParseMKI(inlineParts[2]);
1423+
}
1424+
else if (inlineParts[1].Contains(':'))
1425+
{
1426+
MKI = ParseMKI(inlineParts[1]);
1427+
}
1428+
}
1429+
1430+
SrtpKeys keys = SrtpProtocol.CreateMasterKeys(cryptoSuite, MKI, masterKeySalt);
1431+
return SrtpProtocol.CreateSrtpSessionContext(keys);
1432+
}
1433+
}
1434+
}
1435+
}
1436+
1437+
return null;
1438+
}
1439+
1440+
private static byte[] ParseMKI(string sdpMki)
1441+
{
1442+
string[] mkiParts = sdpMki.Split(':');
1443+
if (mkiParts.Length == 2)
1444+
{
1445+
byte[] mkiValue = new BigInteger(int.Parse(mkiParts[0])).ToByteArray();
1446+
byte[] MKI = new byte[int.Parse(mkiParts[1])];
1447+
Buffer.BlockCopy(mkiValue, 0, MKI, 0, mkiValue.Length);
1448+
return MKI;
1449+
}
1450+
1451+
return null;
1452+
}
1453+
13131454
private Uri GetControlUri(Media media)
13141455
{
13151456
Uri controlUri = null;

src/SharpRTSPClient/SharpRTSPClient.csproj

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,15 @@
77
</PropertyGroup>
88

99
<ItemGroup>
10-
<PackageReference Include="SharpRTSP" Version="1.10.0" />
11-
<PackageReference Include="Meziantou.Polyfill" Version="1.0.68" Condition="'$(TargetFramework)' == 'netstandard2.0' Or '$(TargetFramework)' == 'netstandard2.1'">
12-
<PrivateAssets>all</PrivateAssets>
13-
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
14-
</PackageReference>
10+
<ProjectReference Include="..\..\..\SharpSRTP\src\SharpSRTP\SharpSRTP.csproj" />
1511
</ItemGroup>
1612

13+
<ItemGroup>
14+
<PackageReference Include="SharpRTSP" Version="1.10.2" />
15+
<PackageReference Include="Meziantou.Polyfill" Version="1.0.68" Condition="'$(TargetFramework)' == 'netstandard2.0' Or '$(TargetFramework)' == 'netstandard2.1'">
16+
<PrivateAssets>all</PrivateAssets>
17+
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
18+
</PackageReference>
19+
</ItemGroup>
20+
1721
</Project>

src/SharpRTSPServer/ITrack.cs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,18 @@
55

66
namespace SharpRTSPServer
77
{
8+
public enum RtpProfiles
9+
{
10+
AVP,
11+
// AVPF,
12+
SAVP,
13+
// SAVPF
14+
}
15+
816
public interface ITrack
917
{
18+
RtpProfiles RtpProfile { get; set; }
19+
1020
uint SSRC { get; set; }
1121

1222
IRtpSender Sink { get; set; }

src/SharpRTSPServer/RTPStream.cs

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
using Rtsp;
2+
using SharpSRTP.SRTP;
3+
using System;
24

35
namespace SharpRTSPServer
46
{
@@ -7,6 +9,21 @@ namespace SharpRTSPServer
79
/// </summary>
810
public class RTPStream
911
{
12+
public SrtpSessionContext Context { get; set; } = null;
13+
public byte[] PrepareSrtpContext(string cryptoSuite, int mkiLen = 0)
14+
{
15+
if (string.IsNullOrEmpty(cryptoSuite))
16+
throw new ArgumentNullException("SRTP Crypto suite not selected!");
17+
18+
// derive the master key + master salt to be sent in SDP crypto: attribute as per RFC 4568
19+
byte[] MKI = SrtpProtocol.GenerateMki(mkiLen);
20+
21+
SrtpKeys keys = SrtpProtocol.CreateMasterKeys(cryptoSuite, MKI);
22+
Context = SrtpProtocol.CreateSrtpSessionContext(keys);
23+
24+
return keys.MasterKeySalt;
25+
}
26+
1027
/// <summary>
1128
/// When true will send out a RTCP packet to match Wall Clock Time to RTP Payload timestamps.
1229
/// </summary>

0 commit comments

Comments
 (0)