Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

FIR #1

Draft
wants to merge 5 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions src/net/RTCP/RTCPCompoundPacket.cs
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,10 @@ public class RTCPCompoundPacket
public RTCPSenderReport SenderReport { get; private set; }
public RTCPReceiverReport ReceiverReport { get; private set; }
public RTCPSDesReport SDesReport { get; private set; }
public RTCPHeader PLI { get; private set; }
public RTCPBye Bye { get; set; }
public RTCPFIR FIR { get; private set; }
public RTCPFeedback NACK { get; private set; }

public RTCPCompoundPacket(RTCPSenderReport senderReport, RTCPSDesReport sdesReport)
{
Expand Down Expand Up @@ -97,11 +100,24 @@ public RTCPCompoundPacket(byte[] packet)
// TODO: Interpret Generic RTP feedback reports.
var rtpfbHeader = new RTCPHeader(buffer);
offset += rtpfbHeader.Length * 4 + 4;
if(rtpfbHeader.FeedbackMessageType == RTCPFeedbackTypesEnum.NACK)
{
NACK = new RTCPFeedback(buffer);
}
break;
case (byte)RTCPReportTypesEnum.PSFB:
// TODO: Interpret Payload specific feedback reports.
var psfbHeader = new RTCPHeader(buffer);
offset += psfbHeader.Length * 4 + 4;
if(psfbHeader.PayloadFeedbackMessageType == PSFBFeedbackTypesEnum.PLI)
{
PLI = psfbHeader;
}
break;
case (byte)RTCPReportTypesEnum.FIR:
FIR = new RTCPFIR(buffer);
int FIRLength = (FIR != null) ? FIR.GetBytes().Length : Int32.MaxValue;
offset += FIRLength;
break;
default:
logger.LogWarning($"RTCPCompoundPacket did not recognise packet type ID {packetTypeID}.");
Expand Down
152 changes: 152 additions & 0 deletions src/net/RTCP/RTCPFIR.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
//-----------------------------------------------------------------------------
// Filename: RTCPFIR.cs
//
// Description: RTCP FIR packet as defined in RFC2032
//
// FIR RTCP Packet
// 0 1 2 3
// 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
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// |V=2|P| MBZ | PT=RTCP_FIR=196 | length |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// | SSRC/CSRC |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
//
//-----------------------------------------------------------------------------

using System;
using System.Text;
using SIPSorcery.Sys;

namespace SIPSorcery.Net
{
/// <summary>
/// RTCP Goodbye packet as defined in RFC3550. The BYE packet indicates
/// that one or more sources are no longer active.
/// </summary>
public class RTCPFIR
{
public const int MAX_REASON_BYTES = 255;
public const int SSRC_SIZE = 4; // 4 bytes for the SSRC.
public const int MIN_PACKET_SIZE = RTCPHeader.HEADER_BYTES_LENGTH + SSRC_SIZE;

public RTCPHeader Header;
public uint SSRC { get; private set; }
public string Reason { get; private set; }

/// <summary>
/// Creates a new RTCP Bye payload.
/// </summary>
/// <param name="ssrc">The synchronisation source of the RTP stream being closed.</param>
/// <param name="reason">Optional reason for closing. Maximum length is 255 bytes
/// (note bytes not characters).</param>
public RTCPFIR(uint ssrc, string reason)
{
Header = new RTCPHeader(RTCPReportTypesEnum.FIR, 1);
SSRC = ssrc;

if (reason != null)
{
Reason = (reason.Length > MAX_REASON_BYTES) ? reason.Substring(0, MAX_REASON_BYTES) : reason;

// Need to take account of multi-byte characters.
while (Encoding.UTF8.GetBytes(Reason).Length > MAX_REASON_BYTES)
{
Reason = Reason.Substring(0, Reason.Length - 1);
}
}
}

/// <summary>
/// Create a new RTCP Goodbye packet from a serialised byte array.
/// </summary>
/// <param name="packet">The byte array holding the Goodbye packet.</param>
public RTCPFIR(byte[] packet)
{
if (packet.Length < MIN_PACKET_SIZE)
{
throw new ApplicationException("The packet did not contain the minimum number of bytes for an RTCP Goodbye packet.");
}

Header = new RTCPHeader(packet);

if (BitConverter.IsLittleEndian)
{
SSRC = NetConvert.DoReverseEndian(BitConverter.ToUInt32(packet, 4));
}
else
{
SSRC = BitConverter.ToUInt32(packet, 4);
}

if (packet.Length > MIN_PACKET_SIZE)
{
int reasonLength = packet[8];

if (packet.Length - MIN_PACKET_SIZE - 1 >= reasonLength)
{
Reason = Encoding.UTF8.GetString(packet, 9, reasonLength);
}
}
}

/// <summary>
/// Gets the raw bytes for the Goodbye packet.
/// </summary>
/// <returns>A byte array.</returns>
public byte[] GetBytes()
{
byte[] reasonBytes = (Reason != null) ? Encoding.UTF8.GetBytes(Reason) : null;
int reasonLength = (reasonBytes != null) ? reasonBytes.Length : 0;
byte[] buffer = new byte[RTCPHeader.HEADER_BYTES_LENGTH + GetPaddedLength(reasonLength)];
Header.SetLength((ushort)(buffer.Length / 4 - 1));

Buffer.BlockCopy(Header.GetBytes(), 0, buffer, 0, RTCPHeader.HEADER_BYTES_LENGTH);
int payloadIndex = RTCPHeader.HEADER_BYTES_LENGTH;

if (BitConverter.IsLittleEndian)
{
Buffer.BlockCopy(BitConverter.GetBytes(NetConvert.DoReverseEndian(SSRC)), 0, buffer, payloadIndex, 4);
}
else
{
Buffer.BlockCopy(BitConverter.GetBytes(SSRC), 0, buffer, payloadIndex, 4);
}

if (reasonLength > 0)
{
buffer[payloadIndex + 4] = (byte)reasonLength;
Buffer.BlockCopy(reasonBytes, 0, buffer, payloadIndex + 5, reasonBytes.Length);
}

return buffer;
}

/// <summary>
/// The packet has to finish on a 4 byte boundary. This method calculates the minimum
/// packet length for the Goodbye fields to fit within a 4 byte boundary.
/// </summary>
/// <param name="reasonLength">The length of the optional reason string, can be 0.</param>
/// <returns>The minimum length for the full packet to be able to fit within a 4 byte
/// boundary.</returns>
private int GetPaddedLength(int reasonLength)
{
// Plus one is for the reason length field.
if (reasonLength > 0)
{
reasonLength += 1;
}

int nonPaddedSize = reasonLength + SSRC_SIZE;

if (nonPaddedSize % 4 == 0)
{
return nonPaddedSize;
}
else
{
return nonPaddedSize + 4 - (nonPaddedSize % 4);
}
}
}
}
15 changes: 15 additions & 0 deletions src/net/RTCP/RTCPFeedback.cs
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,22 @@ public RTCPFeedback(byte[] packet)
MediaSSRC = BitConverter.ToUInt32(packet, 8);
}


// TODO: Depending on the report type additional parameters will need to be deserialised.
if (Header.FeedbackMessageType == RTCPFeedbackTypesEnum.NACK)
{
int payloadIndex = RTCPHeader.HEADER_BYTES_LENGTH;
if (BitConverter.IsLittleEndian)
{
PID = NetConvert.DoReverseEndian(BitConverter.ToUInt16(packet, payloadIndex + 6));
BLP = NetConvert.DoReverseEndian(BitConverter.ToUInt16(packet, payloadIndex + 8));
}
else
{
SenderSSRC = BitConverter.ToUInt16(packet, payloadIndex + 6);
MediaSSRC = BitConverter.ToUInt16(packet, payloadIndex + 8);
}
}
}

public byte[] GetBytes()
Expand Down
1 change: 1 addition & 0 deletions src/net/RTCP/RTCPHeader.cs
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ namespace SIPSorcery.Net
/// </summary>
public enum RTCPReportTypesEnum : byte
{
FIR = 192, // FIR
SR = 200, // Send Report.
RR = 201, // Receiver Report.
SDES = 202, // Session Description.
Expand Down
65 changes: 64 additions & 1 deletion src/net/RTP/RTPSession.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
//-----------------------------------------------------------------------------

using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Net;
Expand Down Expand Up @@ -133,7 +134,7 @@ public enum SetDescriptionResultEnum
/// </remarks>
public class RTPSession : IMediaSession, IDisposable
{
private const int RTP_MAX_PAYLOAD = 1400;
public int RTP_MAX_PAYLOAD { get; set; } = 1400;

/// <summary>
/// From libsrtp: SRTP_MAX_TRAILER_LEN is the maximum length of the SRTP trailer
Expand Down Expand Up @@ -187,6 +188,8 @@ public class RTPSession : IMediaSession, IDisposable
internal Dictionary<SDPMediaTypesEnum, RTPChannel> m_rtpChannels = new Dictionary<SDPMediaTypesEnum, RTPChannel>();

private SrtpHandler m_srtpHandler = null;
private List<RTPPacket> videoChannelRTPPacketBuffer = new List<RTPPacket>();
public int NACKPayloadType { get; set; } = 101;

/// <summary>
/// Track if current remote description is invalid (used in Renegotiation logic)
Expand Down Expand Up @@ -379,6 +382,12 @@ public bool HasVideo
/// </summary>
public event Action<string> OnRtcpBye;



public event Action OnFullIntraRequest;

public event Action OnPictureLossIndication;

/// <summary>
/// Fires when the connection for a media type is classified as timed out due to not
/// receiving any RTP or RTCP packets within the given period.
Expand Down Expand Up @@ -2150,6 +2159,20 @@ protected void OnReceive(int localPort, IPEndPoint remoteEndPoint, byte[] buffer
VideoRemoteTrack.Ssrc = 0;
}
}
else if (rtcpPkt.FIR != null)
{
logger.LogDebug($"RTCP FIR received for SSRC {rtcpPkt.FIR.SSRC}");
OnFullIntraRequest?.Invoke();
}
else if (rtcpPkt.PLI != null)
{
logger.LogDebug($"RTCP PSFB received");
OnPictureLossIndication?.Invoke();
}
else if(rtcpPkt.NACK != null)
{
OnNackRecevied(rtcpPkt.NACK);
}
else if (!IsClosed)
{
var rtcpSession = GetRtcpSession(rtcpPkt);
Expand Down Expand Up @@ -2594,6 +2617,10 @@ private void SendRtpPacket(RTPChannel rtpChannel, IPEndPoint dstRtpSocket, byte[
if (m_srtpProtect == null)
{
rtpChannel.Send(RTPChannelSocketsEnum.RTP, dstRtpSocket, rtpBuffer);
if (ssrc == VideoLocalTrack.Ssrc)
{
PushToBufferVideoChannelPacketBuffer(rtpPacket);
}
}
else
{
Expand All @@ -2606,6 +2633,10 @@ private void SendRtpPacket(RTPChannel rtpChannel, IPEndPoint dstRtpSocket, byte[
else
{
rtpChannel.Send(RTPChannelSocketsEnum.RTP, dstRtpSocket, rtpBuffer.Take(outBufLen).ToArray());
if (ssrc == VideoLocalTrack.Ssrc)
{
PushToBufferVideoChannelPacketBuffer(rtpPacket);
}
}
}
m_lastRtpTimestamp = timestamp;
Expand Down Expand Up @@ -2708,5 +2739,37 @@ public virtual void Dispose()
{
Close("disposed");
}

private void PushToBufferVideoChannelPacketBuffer(RTPPacket packet)
{
if (videoChannelRTPPacketBuffer.Count < 60)
{
videoChannelRTPPacketBuffer.Add(packet);
}
else
{
videoChannelRTPPacketBuffer.RemoveAt(0);
videoChannelRTPPacketBuffer.Add(packet);
}
}

private void OnNackRecevied(RTCPFeedback NACK)
{
if(NACK.SenderSSRC == VideoLocalTrack.Ssrc || NACK.MediaSSRC == VideoLocalTrack.Ssrc)
{
var seq = NACK.PID;
var bitMask = new BitArray(BitConverter.GetBytes(NACK.BLP));
for (int i = 0; i < bitMask.Length; i++)
{
if (bitMask[i])
{
var packet = videoChannelRTPPacketBuffer.First(x => x.Header.SequenceNumber == seq + i + 1);
var videoChannel = GetRtpChannel(SDPMediaTypesEnum.video);

SendRtpPacket(videoChannel, VideoDestinationEndPoint, packet.Payload, packet.Header.Timestamp, packet.Header.MarkerBit, NACKPayloadType, VideoLocalTrack.Ssrc, packet.Header.SequenceNumber, VideoRtcpSession);
}
}
}
}
}
}
20 changes: 17 additions & 3 deletions src/net/WebRTC/RTCPeerConnection.cs
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,9 @@ public class RTCPeerConnection : RTPSession, IRTCPeerConnection
private const string NORMAL_CLOSE_REASON = "normal";
private const ushort SCTP_DEFAULT_PORT = 5000;
private const string UNKNOWN_DATACHANNEL_ERROR = "unknown";
private const string RTCP_FIR = "a=rtcp-fb:* ccm fir"; // Indicates the media announcement is using multiplexed RTCP.
private const string RTP_MAP_RETRANSMIT = "a=rtpmap:{0} rtx/90000";
private const string FMTP_RETRANSMIT = "a=fmtp:{0} apt={1};rtx-time=200";

/// <summary>
/// The period to wait for the SCTP association to complete before giving up.
Expand All @@ -176,6 +179,7 @@ public class RTCPeerConnection : RTPSession, IRTCPeerConnection
public string SessionID { get; private set; }
public string SdpSessionID { get; private set; }
public string LocalSdpSessionID { get; private set; }
public bool FIREnabled { get; set; }

private RtpIceChannel _rtpIceChannel;

Expand Down Expand Up @@ -379,12 +383,15 @@ public RTCPeerConnection() :
this(null)
{ }


/// <summary>
/// Constructor to create a new RTC peer connection instance.
/// </summary>
/// <param name="configuration">Optional.</param>
public RTCPeerConnection(RTCConfiguration configuration, int bindPort = 0) :
base(true, true, true, configuration?.X_BindAddress, bindPort)
/// <param name="bindPort">Optional. Default = 0</param>
/// <param name="isSecure">Optional. Default = true</param>
public RTCPeerConnection(RTCConfiguration configuration, int bindPort = 0, bool isSecure = true) :
base(true, true, isSecure, configuration?.X_BindAddress, bindPort)
{
if (_configuration != null &&
_configuration.iceTransportPolicy == RTCIceTransportPolicy.relay &&
Expand Down Expand Up @@ -472,7 +479,7 @@ public RTCPeerConnection(RTCConfiguration configuration, int bindPort = 0) :

OnRtpClosed += Close;
OnRtcpBye += Close;

//Cancel Negotiation Task Event to Prevent Duplicated Calls
onnegotiationneeded += CancelOnNegotiationNeededTask;

Expand Down Expand Up @@ -1102,6 +1109,13 @@ void AddIceCandidates(SDPMediaAnnouncement announcement)
announcement.IceOptions = ICE_OPTIONS;
announcement.IceRole = IceRole;
announcement.DtlsFingerprint = dtlsFingerprint;
if (FIREnabled)
{
announcement.AddExtra(RTCP_FIR);
announcement.AddExtra(string.Format(RTP_MAP_RETRANSMIT, NACKPayloadType));
announcement.AddExtra(String.Format(FMTP_RETRANSMIT, NACKPayloadType, videoCapabilities[0].ID));
}


if (iceCandidatesAdded == false && !excludeIceCandidates)
{
Expand Down