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 // 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; } /// /// Grabs a value from the RTP header in Big-Endian format /// /// The RTP packet /// Start bit of the data value /// End bit of the data value /// The value 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 VoiceReceived; internal event EventHandler PTTControlReceived; internal event EventHandler TimerAliveExpired; internal event EventHandler ExceptionThrown; protected virtual void OnVoiceReceived(AudioEventArgs e) { EventHandler handler = VoiceReceived; if (handler != null) { //handler.BeginInvoke(this, e, null, null); handler(this, e); } } private void OnPTTControlReceived(PTTEventArgs e) { EventHandler 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 } }