Skip to content

Commit d5198e5

Browse files
committed
perf(audio-stream): reduce memory allocation on send audio frame
1 parent 682a6f2 commit d5198e5

File tree

7 files changed

+116
-43
lines changed

7 files changed

+116
-43
lines changed

src/app/Media/VoIPMediaSession.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -338,13 +338,13 @@ protected void RtpMediaPacketReceived(IPEndPoint remoteEndPoint, SDPMediaTypesEn
338338
{
339339
logger.LogTrace(nameof(RtpMediaPacketReceived) + " audio RTP packet received from {RemoteEndPoint} ssrc {SyncSource} seqnum {SequenceNumber} timestamp {Timestamp} payload type {PayloadType}.", remoteEndPoint, hdr.SyncSource, hdr.SequenceNumber, hdr.Timestamp, hdr.PayloadType);
340340

341-
Media.AudioSink.GotAudioRtp(remoteEndPoint, hdr.SyncSource, hdr.SequenceNumber, hdr.Timestamp, hdr.PayloadType, marker, rtpPacket.Payload);
341+
Media.AudioSink.GotAudioRtp(remoteEndPoint, hdr.SyncSource, hdr.SequenceNumber, hdr.Timestamp, hdr.PayloadType, marker, rtpPacket.GetPayloadBytes());
342342
}
343343
else if (mediaType == SDPMediaTypesEnum.text && Media.TextSink != null)
344344
{
345345
logger.LogTrace(nameof(RtpMediaPacketReceived) + " text RTP packet received from {RemoteEndPoint} ssrc {SyncSource} seqnum {SequenceNumber} timestamp {Timestamp} payload type {PayloadType}.", remoteEndPoint, hdr.SyncSource, hdr.SequenceNumber, hdr.Timestamp, hdr.PayloadType);
346346

347-
Media.TextSink.GotTextRtp(remoteEndPoint, hdr.SyncSource, hdr.SequenceNumber, hdr.Timestamp, hdr.PayloadType, hdr.MarkerBit, rtpPacket.Payload);
347+
Media.TextSink.GotTextRtp(remoteEndPoint, hdr.SyncSource, hdr.SequenceNumber, hdr.Timestamp, hdr.PayloadType, hdr.MarkerBit, rtpPacket.GetPayloadBytes());
348348
}
349349
}
350350

src/net/RTCP/RTCPSession.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -234,7 +234,7 @@ public void RecordRtpPacketReceived(RTPPacket rtpPacket)
234234
LastActivityAt = DateTime.Now;
235235
IsTimedOut = false;
236236
PacketsReceivedCount++;
237-
OctetsReceivedCount += (uint)rtpPacket.Payload.Length;
237+
OctetsReceivedCount += rtpPacket.GetPayloadLength();
238238

239239
if (m_receptionReport == null)
240240
{
@@ -272,7 +272,7 @@ public void RecordRtpPacketSend(RTPPacket rtpPacket)
272272
}
273273

274274
PacketsSentCount++;
275-
OctetsSentCount += (uint)rtpPacket.Payload.Length;
275+
OctetsSentCount += rtpPacket.GetPayloadLength();
276276
LastSeqNum = rtpPacket.Header.SequenceNumber;
277277
LastRtpTimestampSent = rtpPacket.Header.Timestamp;
278278
LastNtpTimestampSent = DateTimeToNtpTimestamp(DateTime.Now);

src/net/RTP/AudioStream.cs

Lines changed: 35 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ public bool HasAudio
6161
/// <param name="durationRtpUnits">The duration in RTP timestamp units of the audio sample. This
6262
/// value is added to the previous RTP timestamp when building the RTP header.</param>
6363
/// <param name="sample">The audio sample to set as the RTP packet payload.</param>
64-
public void SendAudio(uint durationRtpUnits, byte[] sample)
64+
public void SendAudio(uint durationRtpUnits, ArraySegment<byte> sample)
6565
{
6666
if (!sendingFormatFound)
6767
{
@@ -71,14 +71,25 @@ public void SendAudio(uint durationRtpUnits, byte[] sample)
7171
SendAudioFrame(durationRtpUnits, NegotiatedFormat.ID, sample);
7272
}
7373

74+
/// <summary>
75+
/// Sends an audio sample to the remote peer.
76+
/// </summary>
77+
/// <param name="durationRtpUnits">The duration in RTP timestamp units of the audio sample. This
78+
/// value is added to the previous RTP timestamp when building the RTP header.</param>
79+
/// <param name="sample">The audio sample to set as the RTP packet payload.</param>
80+
public void SendAudio(uint durationRtpUnits, byte[] sample)
81+
{
82+
SendAudio(durationRtpUnits, new ArraySegment<byte>(sample));
83+
}
84+
7485
/// <summary>
7586
/// Sends an audio packet to the remote party.
7687
/// </summary>
7788
/// <param name="duration">The duration of the audio payload in timestamp units. This value
7889
/// gets added onto the timestamp being set in the RTP header.</param>
7990
/// <param name="payloadTypeID">The payload ID to set in the RTP header.</param>
80-
/// <param name="buffer">The audio payload to send.</param>
81-
public void SendAudioFrame(uint duration, int payloadTypeID, byte[] buffer)
91+
/// <param name="bufferSegment">The audio payload to send.</param>
92+
public void SendAudioFrame(uint duration, int payloadTypeID, ArraySegment<byte> bufferSegment)
8293
{
8394
if (CheckIfCanSendRtpRaw())
8495
{
@@ -99,29 +110,30 @@ public void SendAudioFrame(uint duration, int payloadTypeID, byte[] buffer)
99110
// See https://github.com/sipsorcery/sipsorcery/issues/394.
100111

101112
int maxPayload = RTPSession.RTP_MAX_PAYLOAD;
102-
int totalPackets = (buffer.Length + maxPayload - 1) / maxPayload;
113+
int totalPackets = (bufferSegment.Count + maxPayload - 1) / maxPayload;
103114

104115
uint totalIncrement = 0;
105116
uint startTimestamp = LocalTrack.Timestamp; // Keep track of where we started.
106117

107118
for (int index = 0; index < totalPackets; index++)
108119
{
109120
int offset = index * maxPayload;
110-
int payloadLength = Math.Min(maxPayload, buffer.Length - offset);
121+
int payloadLength = Math.Min(maxPayload, bufferSegment.Count - offset);
111122

112-
double fraction = (double)payloadLength / buffer.Length;
123+
double fraction = (double)payloadLength / bufferSegment.Count;
113124
uint packetDuration = (uint)Math.Round(fraction * duration);
114125

115-
byte[] payload = new byte[payloadLength];
116-
Buffer.BlockCopy(buffer, offset, payload, 0, payloadLength);
117-
118126
// RFC3551 specifies that for audio the marker bit should always be 0 except for when returning
119127
// from silence suppression. For video the marker bit DOES get set to 1 for the last packet
120128
// in a frame.
121129
int markerBit = 0;
122-
130+
#if NETCOREAPP2_1_OR_GREATER && !NETFRAMEWORK
131+
var memorySegment = bufferSegment.Slice(offset, payloadLength);
132+
#else
133+
var memorySegment = new ArraySegment<byte>(bufferSegment.Array!, offset, payloadLength);
134+
#endif
123135
// Send this packet at the current LocalTrack.Timestamp
124-
SendRtpRaw(payload, LocalTrack.Timestamp, markerBit, payloadTypeID, true);
136+
SendRtpRaw(memorySegment, LocalTrack.Timestamp, markerBit, payloadTypeID, true);
125137

126138
// After sending, increment the timestamp by this packet's portion.
127139
// This ensures the timestamp increments for the next packet, including the first one.
@@ -143,6 +155,18 @@ public void SendAudioFrame(uint duration, int payloadTypeID, byte[] buffer)
143155
}
144156
}
145157

158+
/// <summary>
159+
/// Sends an audio packet to the remote party.
160+
/// </summary>
161+
/// <param name="duration">The duration of the audio payload in timestamp units. This value
162+
/// gets added onto the timestamp being set in the RTP header.</param>
163+
/// <param name="payloadTypeID">The payload ID to set in the RTP header.</param>
164+
/// <param name="buffer">The audio payload to send.</param>
165+
public void SendAudioFrame(uint duration, int payloadTypeID, byte[] buffer)
166+
{
167+
SendAudioFrame(duration, payloadTypeID, new ArraySegment<byte>(buffer));
168+
}
169+
146170
/// <summary>
147171
/// Sends an RTP event for a DTMF tone as per RFC2833. Sending the event requires multiple packets to be sent.
148172
/// This method will hold onto the socket until all the packets required for the event have been sent. The send

src/net/RTP/MediaStream.cs

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -377,14 +377,15 @@ private static byte[] Combine(params byte[][] arrays)
377377
return rv;
378378
}
379379

380-
protected void SendRtpRaw(byte[] data, uint timestamp, int markerBit, int payloadType, Boolean checkDone, ushort? seqNum = null)
380+
protected void SendRtpRaw(ArraySegment<byte> data, uint timestamp, int markerBit, int payloadType, Boolean checkDone, ushort? seqNum = null)
381381
{
382382
if (checkDone || CheckIfCanSendRtpRaw())
383383
{
384384
ProtectRtpPacket protectRtpPacket = SecureContext?.ProtectRtpPacket;
385385
int srtpProtectionLength = (protectRtpPacket != null) ? RTPSession.SRTP_MAX_PREFIX_LENGTH : 0;
386386

387-
RTPPacket rtpPacket = new RTPPacket(data.Length + srtpProtectionLength);
387+
RTPPacket rtpPacket = new RTPPacket(data, srtpProtectionLength);
388+
388389
rtpPacket.Header.SyncSource = LocalTrack.Ssrc;
389390
rtpPacket.Header.SequenceNumber = seqNum ?? LocalTrack.GetNextSeqNum();
390391
rtpPacket.Header.Timestamp = timestamp;
@@ -452,8 +453,6 @@ 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
452453
rtpPacket.Header.HeaderExtensionFlag = 0;
453454
}
454455

455-
Buffer.BlockCopy(data, 0, rtpPacket.Payload, 0, data.Length);
456-
457456
var rtpBuffer = rtpPacket.GetBytes();
458457

459458
if (protectRtpPacket == null)
@@ -478,6 +477,11 @@ 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
478477
}
479478
}
480479

480+
protected void SendRtpRaw(byte[] data, uint timestamp, int markerBit, int payloadType, Boolean checkDone, ushort? seqNum = null)
481+
{
482+
SendRtpRaw(new ArraySegment<byte>(data), timestamp, markerBit, payloadType, checkDone, seqNum);
483+
}
484+
481485
/// <summary>
482486
/// To set a new value to a RTP Header extension.
483487
///
@@ -679,7 +683,7 @@ public void OnReceiveRTPPacket(RTPHeader hdr, int localPort, IPEndPoint remoteEn
679683
return;
680684
}
681685

682-
RaiseOnRtpEventByIndex(remoteEndPoint, new RTPEvent(rtpPacket.Payload), rtpPacket.Header);
686+
RaiseOnRtpEventByIndex(remoteEndPoint, new RTPEvent(rtpPacket.GetPayloadBytes()), rtpPacket.Header);
683687
return;
684688
}
685689

src/net/RTP/Packetisation/RtpVideoFramer.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ public RtpVideoFramer(VideoCodecsEnum codec, int maxFrameSize)
6363

6464
public byte[] GotRtpPacket(RTPPacket rtpPacket)
6565
{
66-
var payload = rtpPacket.Payload;
66+
var payload = rtpPacket.GetPayloadBytes();
6767

6868
var hdr = rtpPacket.Header;
6969

src/net/RTP/RTPPacket.cs

Lines changed: 53 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,9 @@ namespace SIPSorcery.Net
2121
public class RTPPacket
2222
{
2323
public RTPHeader Header;
24-
public byte[] Payload;
24+
private byte[] _payload;
25+
private ArraySegment<byte> _payloadSegment;
26+
private int _srtpProtectionLength = 0;
2527

2628
public RTPPacket()
2729
{
@@ -31,23 +33,66 @@ public RTPPacket()
3133
public RTPPacket(int payloadSize)
3234
{
3335
Header = new RTPHeader();
34-
Payload = new byte[payloadSize];
36+
_payload = new byte[payloadSize];
3537
}
3638

3739
public RTPPacket(byte[] packet)
3840
{
3941
Header = new RTPHeader(packet);
40-
Payload = new byte[Header.PayloadSize];
41-
Array.Copy(packet, Header.Length, Payload, 0, Payload.Length);
42+
_payload = new byte[Header.PayloadSize];
43+
Array.Copy(packet, Header.Length, _payload, 0, _payload.Length);
44+
}
45+
46+
public RTPPacket(ArraySegment<byte> packet, int srtpProtectionLength)
47+
{
48+
Header = new RTPHeader();
49+
_payloadSegment = packet;
50+
_srtpProtectionLength = srtpProtectionLength;
51+
}
52+
53+
public uint GetPayloadLength()
54+
{
55+
return (uint)(_payload?.Length ?? _payloadSegment.Count);
56+
}
57+
58+
public byte[] GetPayloadBytes()
59+
{
60+
_payload = _payload ?? _payloadSegment.ToArray();
61+
62+
return _payload;
63+
}
64+
65+
public byte GetPayloadByteAt(int index)
66+
{
67+
return _payload?[index] ?? _payloadSegment[index];
68+
}
69+
70+
public ArraySegment<byte> GetPayloadSegment(int offset, int length)
71+
{
72+
return _payload != null
73+
? new ArraySegment<byte>(_payload, offset, length)
74+
: _payloadSegment.Slice(offset, length);
4275
}
4376

4477
public byte[] GetBytes()
4578
{
4679
byte[] header = Header.GetBytes();
47-
byte[] packet = new byte[header.Length + Payload.Length];
80+
byte[] packet = new byte[header.Length + (_payload?.Length ?? _payloadSegment.Count) + _srtpProtectionLength];
4881

4982
Array.Copy(header, packet, header.Length);
50-
Array.Copy(Payload, 0, packet, header.Length, Payload.Length);
83+
84+
if (_payloadSegment != null)
85+
{
86+
_payloadSegment.CopyTo(packet, header.Length);
87+
}
88+
else if (_payload != null)
89+
{
90+
Array.Copy(_payload, 0, packet, header.Length, _payload.Length);
91+
}
92+
else
93+
{
94+
throw new ApplicationException("Either _payloadSegment or _payload should be defined");
95+
}
5196

5297
return packet;
5398
}
@@ -66,15 +111,15 @@ private byte[] GetNullPayload(int numBytes)
66111

67112
public static bool TryParse(
68113
ReadOnlySpan<byte> buffer,
69-
RTPPacket packet,
114+
RTPPacket packet,
70115
out int consumed)
71116
{
72117
consumed = 0;
73118
if (RTPHeader.TryParse(buffer, out var header, out var headerConsumed))
74119
{
75120
packet.Header = header;
76121
consumed += headerConsumed;
77-
packet.Payload = buffer.Slice(headerConsumed, header.PayloadSize).ToArray();
122+
packet._payload = buffer.Slice(headerConsumed, header.PayloadSize).ToArray();
78123
consumed += header.PayloadSize;
79124
return true;
80125
}

src/net/RTSP/Mjpeg.cs

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -414,8 +414,8 @@ public static byte[] ProcessMjpegFrame(List<RTPPacket> framePackets)
414414

415415
//Decode RtpJpeg Header
416416

417-
TypeSpecific = (uint)(packet.Payload[offset++]);
418-
FragmentOffset = (uint)(packet.Payload[offset++] << 16 | packet.Payload[offset++] << 8 | packet.Payload[offset++]);
417+
TypeSpecific = packet.GetPayloadByteAt(offset++);
418+
FragmentOffset = (uint)(packet.GetPayloadByteAt(offset++) << 16 | packet.GetPayloadByteAt(offset++) << 8 | packet.GetPayloadByteAt(offset++));
419419

420420
#region RFC2435 - The Type Field
421421

@@ -498,16 +498,16 @@ doubling the height of the image.
498498

499499
#endregion
500500

501-
Type = (uint)(packet.Payload[offset++]);
501+
Type = packet.GetPayloadByteAt(offset++);
502502
type = Type & 1;
503503
if (type > 3 || type > 6)
504504
{
505505
throw new ArgumentException("Type numbers 2-5 are reserved and SHOULD NOT be used. Applications on RFC 2035 should be updated to indicate the presence of restart markers with type 64 or 65 and the Restart Marker header.");
506506
}
507507

508-
Quality = (uint)packet.Payload[offset++];
509-
Width = (uint)(packet.Payload[offset++] * 8); // This should have been 128 or > and the standard would have worked for all resolutions
510-
Height = (uint)(packet.Payload[offset++] * 8);// Now in certain highres profiles you will need an OnVif extension before the RtpJpeg Header
508+
Quality = packet.GetPayloadByteAt(offset++);
509+
Width = (uint)(packet.GetPayloadByteAt(offset++) * 8); // This should have been 128 or > and the standard would have worked for all resolutions
510+
Height = (uint)(packet.GetPayloadByteAt(offset++) * 8); // Now in certain highres profiles you will need an OnVif extension before the RtpJpeg Header
511511
//It is worth noting Rtp does not care what you send and more tags such as comments and or higher resolution pictures may be sent and these values will simply be ignored.
512512

513513
if (Width == 0 || Height == 0)
@@ -529,8 +529,8 @@ 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
529529
| Restart Interval |F|L| Restart Count |
530530
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
531531
*/
532-
RestartInterval = (ushort)(packet.Payload[offset++] << 8 | packet.Payload[offset++]);
533-
RestartCount = (ushort)((packet.Payload[offset++] << 8 | packet.Payload[offset++]) & 0x3fff);
532+
RestartInterval = (ushort)(packet.GetPayloadByteAt(offset++) << 8 | packet.GetPayloadByteAt(offset++));
533+
RestartCount = (ushort)((packet.GetPayloadByteAt(offset++) << 8 | packet.GetPayloadByteAt(offset++)) & 0x3fff);
534534
}
535535

536536
//QTables Only occur in the first packet
@@ -539,7 +539,7 @@ 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
539539
//If the quality > 127 there are usually Quantization Tables
540540
if (Quality > 127)
541541
{
542-
if ((packet.Payload[offset++]) != 0)
542+
if ((packet.GetPayloadByteAt(offset++)) != 0)
543543
{
544544
//Must Be Zero is Not Zero
545545
if (System.Diagnostics.Debugger.IsAttached)
@@ -549,7 +549,7 @@ 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
549549
}
550550

551551
//Precision
552-
PrecisionTable = (packet.Payload[offset++]);
552+
PrecisionTable = (packet.GetPayloadByteAt(offset++));
553553

554554
#region RFC2435 Length Field
555555

@@ -584,15 +584,15 @@ those corresponding to the tables needed by the type in use MUST be
584584
#endregion
585585

586586
//Length of all tables
587-
ushort Length = (ushort)(packet.Payload[offset++] << 8 | packet.Payload[offset++]);
587+
ushort Length = (ushort)(packet.GetPayloadByteAt(offset++) << 8 | packet.GetPayloadByteAt(offset++));
588588

589589
//If there is Table Data Read it
590590
if (Length > 0)
591591
{
592-
tables = new ArraySegment<byte>(packet.Payload, offset, (int)Length);
592+
tables = packet.GetPayloadSegment(offset, Length);
593593
offset += (int)Length;
594594
}
595-
else if (Length > packet.Payload.Length - offset)
595+
else if (Length > packet.GetPayloadLength() - offset)
596596
{
597597
continue; // The packet must be discarded
598598
}
@@ -611,7 +611,7 @@ those corresponding to the tables needed by the type in use MUST be
611611
}
612612

613613
//Write the Payload data from the offset
614-
Buffer.Write(packet.Payload, offset, packet.Payload.Length - offset);
614+
Buffer.Write(packet.GetPayloadBytes(), offset, (int)packet.GetPayloadLength() - offset);
615615
}
616616

617617
//Check for EOI Marker

0 commit comments

Comments
 (0)