SafeDispatch/SipComponent/RTPListener2.cs

476 lines
17 KiB
C#
Raw Permalink Normal View History

2024-02-22 16:43:59 +00:00
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace SipComponent
{
class RTPListener2 : IDisposable
{
#region Private Fields
private UdpClient _udpClient;
private const int _rtpHeaderLength = 48;
private AutoResetEvent _autoResetEvent = new AutoResetEvent(false);
private CancellationTokenSource _cancelTokenS_udpListen;
private System.Timers.Timer _timerAlive;
private VoiceBuffer2 _voiceBuffer;
bool _fireEventEnd = true;
volatile bool _canAddToVoiceBuffer = false;
private int _bufferMiliseconds;
private int _nbOfBytesToReturn;
private int _timeToSleep;
protected TSNinfo _receivedTsnInfo = new TSNinfo(0);
private TypeOfCall _typeOfCall;
Task _readBytesFromBufferTask = null;
//bool _forSimoco = false;
#endregion
#region Public Properties
public int SipIDinDialogWith
{
get;
private set;
}
public byte LastRequestedTSNReceived
{
get { return _receivedTsnInfo.TSN; }
}
public TSNstatus ReceivedTSNStatus
{
get { return _receivedTsnInfo.Status; }
}
public bool IsGroupCall
{
get;
private set;
}
#endregion
public RTPListener2(UdpClient udpClient, bool startAliveTimer, int bufferMiliseconds, int sipIDinDialogWith, bool isGroupCall, TypeOfCall typeOfCall)
{
_udpClient = udpClient;
_cancelTokenS_udpListen = new CancellationTokenSource();
this.IsGroupCall = isGroupCall;
_typeOfCall = typeOfCall;
//
// nbOfSamples = nrOfMiliseconds * 8 <kHz>
// A PCM sample has 16 bits = 2 byte
_bufferMiliseconds = _timeToSleep = bufferMiliseconds;
_nbOfBytesToReturn = _bufferMiliseconds * 8 * 2;
if (_typeOfCall == TypeOfCall.HALF_DUPLEX)
{
// Timer Alive
_timerAlive = new System.Timers.Timer(4 * DMR.T_heartbeat * 1000);
_timerAlive.AutoReset = false;
_timerAlive.Elapsed += _timerAlive_Elapsed;
if (startAliveTimer)
_timerAlive.Start();
}
else if (_typeOfCall == TypeOfCall.FULL_DUPLEX)
{
CreateVoiceBuffer();
}
SipIDinDialogWith = sipIDinDialogWith;
}
void _timerAlive_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
{
#if DEBUG
Console.WriteLine("Timer alive expired");
#endif
OnTimerAliveExpired();
}
#region Public methods
public void Start()
{
// TO DO - sa previn pornirea de mai multe ori
CancellationToken cancellToken = _cancelTokenS_udpListen.Token;
Task.Factory.StartNew(() =>
{
IPEndPoint endPoint = new IPEndPoint(IPAddress.Any, 0);
byte[] udpBuffer = null;
bool error = false;
while (true)
{
if (cancellToken.IsCancellationRequested)
break;
try
{
udpBuffer = _udpClient.Receive(ref endPoint);
}
catch (Exception)
{
if (cancellToken.IsCancellationRequested)
break;
else
{
// this error appears when I send a RTP packet and the other party (Simoco)
// has closed their receiving RTP port, probably with the intention of sending Bye
// In this case I should end the dialog just as if timer alive would have expired
error = true;
break;
}
}
ProcessUDPBuffer(udpBuffer);
}
if (error)
{
// I commented this when I decited to start again the rtpListener on this error
// This should be revised
//if (_timerAlive != null)
// _timerAlive.Stop();
#if DEBUG
Console.WriteLine("Error on UdpClient.Receive");
#endif
OnExceptionThrown();
}
}, cancellToken);
}
private void ProcessUDPBuffer(byte[] buffer)
{
byte[] PCMaudio;
// Aflu daca pachetul are extensie (DMR) sau nu (Zoiper)
// bitul X (extension) este al treilea
bool hasExtension = GetRTPHeaderValue(buffer, 3, 3) == 1;
if (hasExtension)
{
// PTT Type este in octetul 41
// Aflu ce tip de pachet este (RTP + voce, PTT heartbeat, etc.
PTT_Type pttType = (PTT_Type)GetRTPHeaderValue(buffer, 8 * 41, 8 * 41 + 7);
// Obtin ID-ul radioului sursa
// Octetii 21, 22, 23 (bigendian)
long radioID = GetRTPHeaderValue(buffer, 21 * 8, 23 * 8 + 7);
// Obtin TSN-ul
// TSN (7 bit) transaction nb
// L (1 bit) - Se pare ca e mereu 0 pt Simoco => OCTET 43 = TSN & 0xFE;
byte TSN = (byte)GetRTPHeaderValue(buffer, 8 * 43, 8 * 43 + 6);
// Extrag intervalul (octetul 44)
int interval = GetRTPHeaderValue(buffer, 8 * 44, 8 * 44 + 7);
switch (pttType)
{
case PTT_Type.Request:
if (_receivedTsnInfo.TSN != TSN)
{
_receivedTsnInfo = new TSNinfo(TSN);
_receivedTsnInfo.Status = TSNstatus.Requesting;
OnPTTControlReceived(new PTTEventArgs(SipIDinDialogWith, radioID, pttType, TSN, 0));
}
break;
case PTT_Type.Grant:
// TO DO - in cazul Simoco, in Grant radioID este setat gresit si folosesc _rtpSourceID in loc
OnPTTControlReceived(new PTTEventArgs(SipIDinDialogWith, SipIDinDialogWith, pttType, TSN, 0));
break;
case PTT_Type.Deny:
OnPTTControlReceived(new PTTEventArgs(SipIDinDialogWith, radioID, pttType, TSN, 0));
break;
case PTT_Type.End:
if (_receivedTsnInfo.TSN == TSN)
{
if (_fireEventEnd)
{
_receivedTsnInfo.Status = TSNstatus.Ended;
// No need for voice buffer any more
StopUsingVoiceBuffer();
_fireEventEnd = false;
OnPTTControlReceived(new PTTEventArgs(SipIDinDialogWith, radioID, pttType, TSN, 0));
}
}
break;
case PTT_Type.HeartbeatQuery:
// Start Alive Timer
_timerAlive.Interval = 4 * interval * 1000;
_timerAlive.Start();
// In cazul Simoco, in HeartbeatQuery radioID este setat gresit si folosesc _rtpSourceID in loc
OnPTTControlReceived(new PTTEventArgs(SipIDinDialogWith, SipIDinDialogWith, pttType, 0, interval));
break;
case PTT_Type.Heartbeat:
// Restart the Alive Timer
_timerAlive.Interval = 4 * interval * 1000;
// In cazul Simoco, in Heartbeat radioID este setat gresit si folosesc _rtpSourceID in loc
OnPTTControlReceived(new PTTEventArgs(SipIDinDialogWith, SipIDinDialogWith, pttType, 0, interval));
break;
case PTT_Type.Start:
HandlePTTStart(buffer, pttType, radioID, TSN);
break;
case PTT_Type.Progress:
if (_receivedTsnInfo.TSN == TSN && _receivedTsnInfo.Status == TSNstatus.Granted)
{
if (!_canAddToVoiceBuffer)
{
CreateVoiceBuffer();
}
PCMaudio = Decode(buffer, _rtpHeaderLength, buffer.Length - _rtpHeaderLength);
_voiceBuffer.AddBytes(PCMaudio);
}
break;
}
}
else
{
if (_typeOfCall == TypeOfCall.FULL_DUPLEX) // TO DO - this is just for DMR Group Call test, I will probably remove it
{
// Zoiper communication
if (_canAddToVoiceBuffer)
{
PCMaudio = Decode(buffer, 12, buffer.Length - 12);
_voiceBuffer.AddBytes(PCMaudio);
}
}
}
}
protected virtual void HandlePTTStart(byte[] buffer, PTT_Type pttType, long radioID, byte TSN)
{
/*
if (_receivedTsnInfo.TSN == TSN && _receivedTsnInfo.Status == TSNstatus.Granted)
{
_fireEventEnd = true;
if (!_receivingVoice)
{
CreateVoiceBuffer();
}
OnPTTControlReceived(new PTTEventArgs(SipIDinDialogWith, radioID, pttType, TSN, 0));
if (buffer.Length > 48)
{
PCMaudio = Decode(buffer, _rtpHeaderLength, buffer.Length - _rtpHeaderLength);
_voiceBuffer.AddBytes(PCMaudio);
}
}
*/
if (_receivedTsnInfo.TSN == TSN && _receivedTsnInfo.Status == TSNstatus.Granted)
{
_fireEventEnd = true;
if (!_canAddToVoiceBuffer)
{
CreateVoiceBuffer();
}
OnPTTControlReceived(new PTTEventArgs(SipIDinDialogWith, radioID, pttType, TSN, 0));
if (buffer.Length > 48)
{
byte[] pcmAudio = Decode(buffer, _rtpHeaderLength, buffer.Length - _rtpHeaderLength);
_voiceBuffer.AddBytes(pcmAudio);
}
}
}
public void SetRequestedTSNstatus(TSNinfo tsnInfo)
{
if (tsnInfo.TSN == _receivedTsnInfo.TSN)
{
if (tsnInfo.Status == TSNstatus.Granted || tsnInfo.Status == TSNstatus.Denied)
_receivedTsnInfo.Status = tsnInfo.Status;
}
}
#endregion
#region VoiceBuffer
private void CreateVoiceBuffer()
{
_voiceBuffer = new VoiceBuffer2(6000);
_voiceBuffer.HalfFull += _voiceBuffer_HalfFull;
_voiceBuffer.Empty += _voiceBuffer_Empty;
_voiceBuffer.Full += _voiceBuffer_Full;
_canAddToVoiceBuffer = true;
}
private void StopUsingVoiceBuffer()
{
_canAddToVoiceBuffer = false;
// No need for voiceBuffer any more
if (_voiceBuffer != null)
{
//_autoResetEvent.Set();
_voiceBuffer.StopAdding();
if (_readBytesFromBufferTask != null)
{
_readBytesFromBufferTask.Wait();
}
_voiceBuffer.HalfFull -= _voiceBuffer_HalfFull;
_voiceBuffer.Empty -= _voiceBuffer_Empty;
_voiceBuffer.Full -= _voiceBuffer_Full;
}
//
}
private void _voiceBuffer_HalfFull(object sender, EventArgs e)
{
//Task.Factory.StartNew(() =>
//{
// bool signaled = false;
// do
// {
// byte[] b = new byte[_nbOfBytesToReturn];
// if (_voiceBuffer.TakeBytes(b) == _nbOfBytesToReturn)
// {
// OnVoiceReceived(new AudioEventArgs(b, long.Parse(RTPSourceID)));
// signaled = _autoResetEvent.WaitOne(_timeToSleep);
// }
// }
// while (!signaled);
//});
_readBytesFromBufferTask = Task.Factory.StartNew(() =>
{
int nbOfBytesTaken = 0;
do
{
byte[] b = new byte[_nbOfBytesToReturn];
nbOfBytesTaken = _voiceBuffer.TakeBytes(b);
if (nbOfBytesTaken == _nbOfBytesToReturn)
{
OnVoiceReceived(new AudioEventArgs(b, SipIDinDialogWith, SipIDinDialogWith));
Thread.Sleep(_timeToSleep);
}
}
while (nbOfBytesTaken != 0);
});
}
void _voiceBuffer_Full(object sender, EventArgs e)
{
if (_timeToSleep > 0)
_timeToSleep--;
}
void _voiceBuffer_Empty(object sender, EventArgs e)
{
_timeToSleep++;
}
public void Stop()
{
// Stop timer alive
if (_timerAlive != null)
_timerAlive.Stop();
StopUsingVoiceBuffer();
// Stop thread listening for udp packets
_cancelTokenS_udpListen.Cancel();
}
#endregion
public void Dispose()
{
_cancelTokenS_udpListen.Dispose();
}
#region Private helper functions
private byte[] Decode(byte[] data, int offset, int length)
{
byte[] decoded = new byte[length * 2];
int outIndex = 0;
for (int n = 0; n < length; n++)
{
short decodedSample = NAudio.Codecs.ALawDecoder.ALawToLinearSample(data[n + offset]);
decoded[outIndex++] = (byte)(decodedSample & 0xFF);
decoded[outIndex++] = (byte)(decodedSample >> 8);
}
return decoded;
}
/// <summary>
/// Grabs a value from the RTP header in Big-Endian format
/// </summary>
/// <param name="packet">The RTP packet</param>
/// <param name="startBit">Start bit of the data value</param>
/// <param name="endBit">End bit of the data value</param>
/// <returns>The value</returns>
private int GetRTPHeaderValue(byte[] packet, int startBit, int endBit)
{
int result = 0;
// Number of bits in value
int length = endBit - startBit + 1;
// Values in RTP header are big endian, so need to do these conversions
for (int i = startBit; i <= endBit; i++)
{
int byteIndex = i / 8;
int bitShift = 7 - (i % 8);
result += ((packet[byteIndex] >> bitShift) & 1) * (int)Math.Pow(2, length - i + startBit - 1);
}
return result;
}
#endregion
#region Events
internal event EventHandler<AudioEventArgs> VoiceReceived;
internal event EventHandler<PTTEventArgs> PTTControlReceived;
internal event EventHandler TimerAliveExpired;
internal event EventHandler ExceptionThrown;
protected virtual void OnVoiceReceived(AudioEventArgs e)
{
EventHandler<AudioEventArgs> handler = VoiceReceived;
if (handler != null)
{
//handler.BeginInvoke(this, e, null, null);
handler(this, e);
}
}
private void OnPTTControlReceived(PTTEventArgs e)
{
EventHandler<PTTEventArgs> handler = PTTControlReceived;
if (handler != null)
{
handler(this, e);
}
}
private void OnTimerAliveExpired()
{
EventHandler handler = TimerAliveExpired;
if (handler != null)
handler(this, new EventArgs());
}
private void OnExceptionThrown()
{
EventHandler handler = ExceptionThrown;
if (handler != null)
handler(this, new EventArgs());
}
#endregion
}
}