476 lines
17 KiB
C#
476 lines
17 KiB
C#
|
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
|
|||
|
|
|||
|
|
|||
|
|
|||
|
}
|
|||
|
}
|