diff --git a/src/net/RTCP/RTCPCompoundPacket.cs b/src/net/RTCP/RTCPCompoundPacket.cs
index b839e3007..e9a6005b9 100644
--- a/src/net/RTCP/RTCPCompoundPacket.cs
+++ b/src/net/RTCP/RTCPCompoundPacket.cs
@@ -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)
{
@@ -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}.");
diff --git a/src/net/RTCP/RTCPFIR.cs b/src/net/RTCP/RTCPFIR.cs
new file mode 100644
index 000000000..bcae7fded
--- /dev/null
+++ b/src/net/RTCP/RTCPFIR.cs
@@ -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
+{
+ ///
+ /// RTCP Goodbye packet as defined in RFC3550. The BYE packet indicates
+ /// that one or more sources are no longer active.
+ ///
+ 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; }
+
+ ///
+ /// Creates a new RTCP Bye payload.
+ ///
+ /// The synchronisation source of the RTP stream being closed.
+ /// Optional reason for closing. Maximum length is 255 bytes
+ /// (note bytes not characters).
+ 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);
+ }
+ }
+ }
+
+ ///
+ /// Create a new RTCP Goodbye packet from a serialised byte array.
+ ///
+ /// The byte array holding the Goodbye packet.
+ 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);
+ }
+ }
+ }
+
+ ///
+ /// Gets the raw bytes for the Goodbye packet.
+ ///
+ /// A byte array.
+ 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;
+ }
+
+ ///
+ /// 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.
+ ///
+ /// The length of the optional reason string, can be 0.
+ /// The minimum length for the full packet to be able to fit within a 4 byte
+ /// boundary.
+ 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);
+ }
+ }
+ }
+}
diff --git a/src/net/RTCP/RTCPFeedback.cs b/src/net/RTCP/RTCPFeedback.cs
index d856926cc..31c2e4058 100644
--- a/src/net/RTCP/RTCPFeedback.cs
+++ b/src/net/RTCP/RTCPFeedback.cs
@@ -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()
diff --git a/src/net/RTCP/RTCPHeader.cs b/src/net/RTCP/RTCPHeader.cs
index a6ccf244d..c0f94f49e 100644
--- a/src/net/RTCP/RTCPHeader.cs
+++ b/src/net/RTCP/RTCPHeader.cs
@@ -42,6 +42,7 @@ namespace SIPSorcery.Net
///
public enum RTCPReportTypesEnum : byte
{
+ FIR = 192, // FIR
SR = 200, // Send Report.
RR = 201, // Receiver Report.
SDES = 202, // Session Description.
diff --git a/src/net/RTP/RTPSession.cs b/src/net/RTP/RTPSession.cs
index 73c106e58..fef95b2e6 100644
--- a/src/net/RTP/RTPSession.cs
+++ b/src/net/RTP/RTPSession.cs
@@ -19,6 +19,7 @@
//-----------------------------------------------------------------------------
using System;
+using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Net;
@@ -133,7 +134,7 @@ public enum SetDescriptionResultEnum
///
public class RTPSession : IMediaSession, IDisposable
{
- private const int RTP_MAX_PAYLOAD = 1400;
+ public int RTP_MAX_PAYLOAD { get; set; } = 1400;
///
/// From libsrtp: SRTP_MAX_TRAILER_LEN is the maximum length of the SRTP trailer
@@ -187,6 +188,8 @@ public class RTPSession : IMediaSession, IDisposable
internal Dictionary m_rtpChannels = new Dictionary();
private SrtpHandler m_srtpHandler = null;
+ private List videoChannelRTPPacketBuffer = new List();
+ public int NACKPayloadType { get; set; } = 101;
///
/// Track if current remote description is invalid (used in Renegotiation logic)
@@ -379,6 +382,12 @@ public bool HasVideo
///
public event Action OnRtcpBye;
+
+
+ public event Action OnFullIntraRequest;
+
+ public event Action OnPictureLossIndication;
+
///
/// 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.
@@ -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);
@@ -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
{
@@ -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;
@@ -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);
+ }
+ }
+ }
+ }
}
}
diff --git a/src/net/WebRTC/RTCPeerConnection.cs b/src/net/WebRTC/RTCPeerConnection.cs
index c97ca6c12..2c3797f2a 100644
--- a/src/net/WebRTC/RTCPeerConnection.cs
+++ b/src/net/WebRTC/RTCPeerConnection.cs
@@ -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";
///
/// The period to wait for the SCTP association to complete before giving up.
@@ -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;
@@ -379,12 +383,15 @@ public RTCPeerConnection() :
this(null)
{ }
+
///
/// Constructor to create a new RTC peer connection instance.
///
/// Optional.
- public RTCPeerConnection(RTCConfiguration configuration, int bindPort = 0) :
- base(true, true, true, configuration?.X_BindAddress, bindPort)
+ /// Optional. Default = 0
+ /// Optional. Default = true
+ 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 &&
@@ -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;
@@ -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)
{