using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace SipComponent { class DMR { private object _lockObject = new object(); // // DMR properties // /// /// The period in ms between sending PTT requests /// const int _T_request = 240; //const int _T_request = 120; /// /// The total number of PTT Requests that can be sent. /// const int _T_numreq = 3; /// /// The period in ms between sending PTT End /// const int _T_end = 20; /// /// The total number of PTT End that are to be sent /// const int _T_numend = 3; /// /// The time in seconds between sending of PTT Heartbeats /// const byte _T_hearbeat = 10; /// /// Returns the time in seconds between sending of PTT Heartbeats /// public static int T_heartbeat { get { return _T_hearbeat; } } /// /// Gets the period in ms between sending PTT End /// public static int T_end { get { return _T_end; } } /// /// Gets the total number of PTT End that are to be sent /// public static int T_numend { get { return _T_numend; } } /// /// Returns the period in ms between sending PTT requests /// public static int T_request { get { return _T_request; } } /// /// Gets the total number of PTT Requests that can be sent. /// public static int T_numreq { get { return _T_numreq; } } public byte[] GeneratePTTRequest(int destionationID, int sourceID, byte transactionNumber, ref ushort sequenceNumber, uint timestamp, byte[] ssrc) { // Headerul RTP fix 12 octeti // Headerul Extins DMR = 4 octeti (Code Type + extension length) + // 32 octeti (8 * 4) // = 36 octeti // 12 + 36 = 48 octeti // Se pare ca pachetele RTP care contin voce contin 160 octeti de voce byte[] PTTrequest = new byte[12 + 36]; // Adaug headerele standard RTP AddStandardRTPHeaders(PTTrequest, ref sequenceNumber, timestamp, ssrc); // PTTRequest // Adaug headerul extins DMR AddStandardDMRExtension(PTTrequest, destionationID, sourceID, RtpCallType.Private, StartReason.NormalStartOfCall, EndReason.OverContinues, Priority.Zero); // Adaug headerul extins specific Simoco AddManufacturerSpecificExtension(PTTrequest, PTT_Type.Request, transactionNumber, 0); // return PTTrequest; } public byte[] GeneratePTTStart(int destionationID, int sourceID, byte transactionNumber, ref ushort sequenceNumber, uint timestamp, byte[] ssrc) { // Scrie undeva ca ar trebui trimis fara voce byte[] PTTStart = new byte[12 + 36]; // Adaug header RTP AddStandardRTPHeaders(PTTStart, ref sequenceNumber, timestamp, ssrc); // Adaug extensia DMR AddStandardDMRExtension(PTTStart, destionationID, sourceID, RtpCallType.Private, StartReason.NormalStartOfCall, EndReason.OverContinues, Priority.Zero); // Adaug extensia SIMOCO AddManufacturerSpecificExtension(PTTStart, PTT_Type.Start, transactionNumber, 0); return PTTStart; } public byte[] GeneratePTTEnd(int destionationID, int sourceID, byte transactionNumber, ref ushort sequenceNumber, uint timestamp, byte[] ssrc) { // Primul pachet PTT End poate contine si voce // O sa le trimit pe toate fara byte[] PTTEnd = new byte[12 + 36]; // Adaug header RTP AddStandardRTPHeaders(PTTEnd, ref sequenceNumber, timestamp, ssrc); // Adaug extensia DMR AddStandardDMRExtension(PTTEnd, destionationID, sourceID, RtpCallType.Private, StartReason.OverContinues, EndReason.NormalEndOfCall, Priority.Zero); // Adaug extensia Simoco AddManufacturerSpecificExtension(PTTEnd, PTT_Type.End, transactionNumber, 0); return PTTEnd; } public byte[] GeneratePTTGrant(int destionationID, int sourceID, byte transactionNumber, ref ushort sequenceNumber, uint timestamp, byte[] ssrc) { byte[] PTTGrant = new byte[12 + 36]; // Adaug header RTP AddStandardRTPHeaders(PTTGrant, ref sequenceNumber, timestamp, ssrc); // Adaug extesia DMR AddStandardDMRExtension(PTTGrant, destionationID, sourceID, RtpCallType.Private, StartReason.NotTheStartOfACall, EndReason.OverContinues, Priority.Zero); // Adaug extensia Simoco AddManufacturerSpecificExtension(PTTGrant, PTT_Type.Grant, transactionNumber, 0); return PTTGrant; } public byte[] GeneratePTTDeny(int destionationID, int sourceID, byte transactionNumber, ref ushort sequenceNumber, uint timestamp, byte[] ssrc) { byte[] PTTDeny = new byte[12 + 36]; // Adaug header RTP AddStandardRTPHeaders(PTTDeny, ref sequenceNumber, timestamp, ssrc); // Adaug extensia DMR AddStandardDMRExtension(PTTDeny, destionationID, sourceID, RtpCallType.Private, StartReason.NotTheStartOfACall, EndReason.OverContinues, Priority.Zero); // Adaug extensia Simoco AddManufacturerSpecificExtension(PTTDeny, PTT_Type.Deny, transactionNumber, 0); return PTTDeny; } public byte[] GeneratePTTHeartbeatQuery(int destionationID, int sourceID, ref ushort sequenceNumber, uint timestamp, byte[] ssrc) { byte[] PTTHeartbeatQuery = new byte[48]; // Adaug header RTP AddStandardRTPHeaders(PTTHeartbeatQuery, ref sequenceNumber, timestamp, ssrc); // Adaug extensia DMR AddStandardDMRExtension(PTTHeartbeatQuery, destionationID, sourceID, RtpCallType.Private, StartReason.NotTheStartOfACall, EndReason.OverContinues, Priority.Zero); // Adaug extensia Simoco AddManufacturerSpecificExtension(PTTHeartbeatQuery, PTT_Type.HeartbeatQuery, 0, _T_hearbeat); return PTTHeartbeatQuery; } public byte[] GeneratePTTHeartbeat(int destionationID, int sourceID, ref ushort sequenceNumber, uint timestamp, byte[] ssrc) { // PTT Heartbeat/PTT Heartbeat Query shall always be sent in a packet with Start Reason = 3 (Not the start // of an over) and End Reason = 0 (Not the end of a call). // For Heartbeat/Heartbeat Query the TSN shall be set to 0 // Presupun ca se trimite fara voce byte[] PTTHeartbeat = new byte[48]; // 12 + 36 = 12 + (8 x 4 + 4) // Adaug header RTP AddStandardRTPHeaders(PTTHeartbeat, ref sequenceNumber, timestamp, ssrc); // Adaug extensia DMR AddStandardDMRExtension(PTTHeartbeat, destionationID, sourceID, RtpCallType.Private, StartReason.NotTheStartOfACall, EndReason.OverContinues, Priority.Zero); // Adaug extensia SIMOCO AddManufacturerSpecificExtension(PTTHeartbeat, PTT_Type.Heartbeat, 0, _T_hearbeat); return PTTHeartbeat; } public void AddStandardVoiceRTPHeaders(byte[] packet, ref ushort sequenceNumber, uint timestamp, byte[] ssrc) { // Put PCMA in RTP packet // RTP header is in big endian /* * Header: * Version (2 bits) - 2 adica 10 * Pading (1 bit) - 0 * X (Extension) (1 bit) - 1 => OCTETUL 0 = 1001 0000 = 0x90 * CC(CSRC Count) (4 bits) - 0 0 0 0 * * * * M (marker) (1 bit) - 0 * PT(payload type) (7 bits) - 8 adica 1000 => OCTETUL 1 = 0000 1000 = 0x08 * * Sequence NB (16 bits) - incremented by 1 for each packet. Initial value should be random => OCTETUL 2 si 3 * * Timestamp (32 bits) - initial value random * - incremented by "RTP packet duration" = audioDataSize / (samplingRate <1/s> * bitRate ) * - OCTETUL 4, 5, 6, 7 * * * * * SSRC (Synchronization source identifier - 32 bits) - unique identifier of the source stream = > OCTETUL 8, 9, 10, 11 * * CSRC (Contributing source identifier - 32 bits) - ignorat..??? (se pare ca e optional - folosit in cazul stream-ului generat din mai * multe surce) */ // OCTET 0 - 10 packet[0] = 0x90; // OCTET 1 packet[1] = 0x08; // OCTET 2, 3 MSB (Big Endian) packet[2] = (byte)(((sequenceNumber) >> 8) % 256); packet[3] = (byte)(sequenceNumber % 256); // OCTET 4, 5, 6, 7 packet[4] = (byte)((timestamp >> 24) % 256); packet[5] = (byte)((timestamp >> 16) % 256); packet[6] = (byte)((timestamp >> 8) % 256); packet[7] = (byte)(timestamp % 256); lock (_lockObject) { sequenceNumber++; //timestamp += (uint)(2 * nrOfAudioBytes * 8 / bitRate); } // OCTET 8, 9, 10, 11 Array.Copy(ssrc, 0, packet, 8, 4); } public void AddStandardVoiceRTPHeadersZoipper(byte[] packet, ref ushort sequenceNumber, uint timestamp, byte[] ssrc) { // Put PCMA in RTP packet // RTP header is in big endian /* * Header: * Version (2 bits) - 2 adica 10 * Pading (1 bit) - 0 * X (Extension) (1 bit) - 0 => OCTETUL 0 = 1000 0000 = 0x80 * CC(CSRC Count) (4 bits) - 0 0 0 0 * * * * M (marker) (1 bit) - 0 * PT(payload type) (7 bits) - 8 adica 1000 => OCTETUL 1 = 0000 1000 = 0x08 * * Sequence NB (16 bits) - incremented by 1 for each packet. Initial value should be random => OCTETUL 2 si 3 * * Timestamp (32 bits) - initial value random * - incremented by "RTP packet duration" = audioDataSize / (samplingRate <1/s> * bitRate ) * - OCTETUL 4, 5, 6, 7 * * * * * SSRC (Synchronization source identifier - 32 bits) - unique identifier of the source stream = > OCTETUL 8, 9, 10, 11 * * CSRC (Contributing source identifier - 32 bits) - ignorat..??? (se pare ca e optional - folosit in cazul stream-ului generat din mai * multe surce) */ // OCTET 0 - 10 packet[0] = 0x80; // OCTET 1 packet[1] = 0x08; // OCTET 2, 3 MSB (Big Endian) packet[2] = (byte)(((sequenceNumber) >> 8) % 256); packet[3] = (byte)(sequenceNumber % 256); // OCTET 4, 5, 6, 7 packet[4] = (byte)((timestamp >> 24) % 256); packet[5] = (byte)((timestamp >> 16) % 256); packet[6] = (byte)((timestamp >> 8) % 256); packet[7] = (byte)(timestamp % 256); lock (_lockObject) { sequenceNumber++; //timestamp += (uint)(2 * nrOfAudioBytes * 8 / bitRate); } // OCTET 8, 9, 10, 11 Array.Copy(ssrc, 0, packet, 8, 4); } public void AddStandardRTPHeaders(byte[] packet, ref ushort sequenceNumber, uint timestamp, byte[] ssrc) { // Put PCMA in RTP packet // RTP header is in big endian /* * Header: * Version (2 bits) - 2 adica 10 * Pading (1 bit) - 0 * X (Extension) (1 bit) - 1 => OCTETUL 0 = 1001 0000 = 0x90 * CC(CSRC Count) (4 bits) - 0 0 0 0 * * * * M (marker) (1 bit) - 0 * PT(payload type) (7 bits) - 8 adica 1000 => OCTETUL 1 = 0000 1000 = 0x08 * * Sequence NB (16 bits) - incremented by 1 for each packet. Initial value should be random => OCTETUL 2 si 3 * * Timestamp (32 bits) - initial value random * - incremented by "RTP packet duration" = audioDataSize / (samplingRate <1/s> * bitRate ) * - OCTETUL 4, 5, 6, 7 * * * * * SSRC (Synchronization source identifier - 32 bits) - unique identifier of the source stream = > OCTETUL 8, 9, 10, 11 * * CSRC (Contributing source identifier - 32 bits) - ignorat..??? (se pare ca e optional - folosit in cazul stream-ului generat din mai * multe surce) */ // OCTET 0 - 10 packet[0] = 0x90; // OCTET 1 packet[1] = 0x08; // OCTET 2, 3 MSB (Big Endian) packet[2] = (byte)(((sequenceNumber) >> 8) % 256); packet[3] = (byte)(sequenceNumber % 256); // OCTET 4, 5, 6, 7 packet[4] = (byte)((timestamp >> 24) % 256); packet[5] = (byte)((timestamp >> 16) % 256); packet[6] = (byte)((timestamp >> 8) % 256); packet[7] = (byte)(timestamp % 256); lock (_lockObject) { sequenceNumber++; } // OCTET 8, 9, 10, 11 Array.Copy(ssrc, 0, packet, 8, 4); } public void AddStandardDMRExtension(byte[] packet, int DST_DMR_ID, int SRC_DMR_ID, RtpCallType typeOfCall, StartReason startReason, EndReason endReason, Priority callPriority) { // CODE_TYPE (16 bit) - 0xe0 0x00 (pentru Simoco) => OCTET 12 = 0xe0; OCTET 13 = 0x00; // LENGTH (16 bit) - 0x00 0x08 => OCTET 14 = 0x00; OCTET 15 = 0x08; // Reserved (4 bit) - 0000 // Frame No (4 bit) - 0000 => OCTET 16 = 0x00; // DST_DMR_ID (24 bit) - => OCTET 17, 18, 19 = DST_DMR_ID (bigendian) // Reserved (8 bit) - 0x00 => OCTET 20 = 0x00; // DST_DMR_ID (24 bit) - => OCTET 21, 22, 23 = ......... // BS_ID (32 bit) - pare sa fie 0 pt simoco => OCTET 24, 25, 26, 27 // Proto Major (4 bit) - 0 // Proto Minor (4 bit) - 0 => OCTET 28 = 0x00; // Colour Code (4 bit) - 0 // Fading Control (4 bit) - 0 => OCTET 29 = 0x00; // Data Type (4 bit) - pare sa fie 0 pt simoco // Call Type (4 bit) - => OCTET 30 = ...... // Src (2 bit) - 0 // Start Reason (2 bit) - 0 - Over continues // - 1 - Normal start of call // - 2 - Late entry into over // - 3 - Not the start of a call // End Reason (4 bit) - 0 - Not the end of a call (over continues) // - 1 - Normal end of a call // - 2 - Fade (timer expired) // - 3 - Other (eg. receiver state change) => OCTET 31 = ......... // Service Options (8 bit) - 0000 | 00 | 2 bit priority ( 0 - 3 ) => Octet 32 = ........ // Signal Quality (8 bit) = 0 => Octet 33 = 0x00 // Reserved (4 bit) = 0 // Encryption (4 bit) = F (no ecryption) => Octet 34 = 0x0F; // Key ID (8 bit) = 0 => Octet 35 = 0x00; // Initialization Vector (32 bit) => OCTET 36, 37, 38, 39 = 0x00; // OCTET 12 = 0xe0; packet[12] = 0xe0; // OCTET 15 = 0x08; packet[15] = 0x08; // OCTET 17, 18, 19 = DST_DMR_ID (bigendian) packet[17] = (byte)((DST_DMR_ID >> 16) % 256); packet[18] = (byte)((DST_DMR_ID >> 8) % 256); packet[19] = (byte)(DST_DMR_ID % 256); // OCTET 21, 22, 23 = SRC_DMR_ID (bigendian) packet[21] = (byte)((SRC_DMR_ID >> 16) % 256); packet[22] = (byte)((SRC_DMR_ID >> 8) % 256); packet[23] = (byte)(SRC_DMR_ID % 256); // OCTET 30 = 0000 xxxx (xxxx - Call Type) packet[30] = (byte)typeOfCall; // OCTET 31 = 00ss eeee (ss - Start Reason, eeee - End Reason) packet[31] = (byte)((byte)(startReason) | (byte)(endReason)); // Octet 32 = 0000 00xx (xx - priority) packet[32] = (byte)callPriority; // Octet 34 = 0x0F (No encryption) packet[34] = 0x0F; } public void AddManufacturerSpecificExtension(byte[] packet, PTT_Type PTType, byte TSN, byte interval) { // ID = 1 // Len = 3 => OCTET 40 = 0x13 // PTT Type = /* * ptt Type Len Direction Description * 0 1 SMF -> MMF Request 1 1 MMF -> SMF Grant 2 1 MMF <-> SMF Progress 3 1 MMF <-> SMF End 4 1 MMF -> SMF Start 8 1 MMF -> SMF Deny 9 1 MMF <-> SMF Heartbeat 10 1 MMF <-> SMF Heartbeat Query */ // => OCTET 41 = PTT type // Service Options (8 bit) same as in RTP Header => OCTET 42 = OCTET 32 // TSN (7 bit) transaction nb // L (1 bit) - Se pare ca e mereu 0 pt Simoco => OCTET 43 = TSN << 1; // INTERVAL - (8 bits) => OCTET 44 = Interval // => OCTET 45, 46, 47 = 0x00 packet[40] = 0x13; packet[41] = (byte)PTType; packet[42] = packet[32]; packet[43] = (byte)(TSN << 1); packet[44] = interval; } } // Call Type (4 bit) // Bit 3 (MSB) 0 DMR call, 1 Analog call // Bit 2 0 Private call, 1 Group call // Bit 1 1 DMR CAll Alert/Analog Selective call // Bit 0 Reserved /// /// Contains the types of call used in Half duplex mode /// internal enum RtpCallType : byte { /// /// DMR call, private /// 0000 0000 /// /// Private = 0x00, /// /// DMR call, group /// 0000 0100 /// Group = 0x04 } internal enum StartReason : byte { // Start Reason (2 bit) - 0 - Over continues => 0x00 // - 1 - Normal start of call => 00 01 0000 = 0x10 // - 2 - Late entry into over => 00 10 0000 = 0x20 // - 3 - Not the start of a call => 00 11 0000 = 0x30 OverContinues = 0x00, NormalStartOfCall = 0x10, LateEntryIntoOver = 0x20, NotTheStartOfACall = 0x30 } internal enum EndReason : byte { // End Reason (4 bit) - 0 - Not the end of a call (over continues) // - 1 - Normal end of a call // - 2 - Fade (timer expired) // - 3 - Other (eg. receiver state change) OverContinues = 0x00, NormalEndOfCall = 0x01, TimerExpired = 0x02, Other = 0x03 } internal enum Priority : byte { Zero = 0, One, Two, Three } internal enum PTT_Type : byte { // PTT Type = /* * ptt Type Len Direction Description * 0 1 SMF -> MMF Request 1 1 MMF -> SMF Grant 2 1 MMF <-> SMF Progress 3 1 MMF <-> SMF End 4 1 MMF -> SMF Start 8 1 MMF -> SMF Deny 9 1 MMF <-> SMF Heartbeat 10 1 MMF <-> SMF Heartbeat Query */ Request = 0, Grant = 1, Progress = 2, End = 3, Start = 4, Deny = 8, Heartbeat = 9, HeartbeatQuery = 10 } internal enum AlarmType : uint { USER_GENERATED = 0x01000000, MAN_DOWN = 0x02000000, LONE_WORKER = 0x04000000, NO_ALARM = 0x00000000 } /// /// Contains a list of sip types of calls /// public enum TypeOfCall { /// /// Full duplex call, like a normal phone (or Zoipper) /// FULL_DUPLEX, /// /// Half duplex call, like a radio PTT /// HALF_DUPLEX } /// /// Provides data for the VoiceReceived event /// public class AudioEventArgs : EventArgs { byte[] _buffer; int _callSourceID; /// /// Voice buffer /// public byte[] Buffer { get { return _buffer; } } /// /// The sip ID of the user who sent the voice buffer /// public int CallSourceID { get { return _callSourceID; } } /// /// Gets the sip id that we are in dialog with /// public int SipIdInDialogWith { get; private set; } internal AudioEventArgs(byte[] data, int callSourceID, int sipIdInDialogWith) { _buffer = data; _callSourceID = callSourceID; SipIdInDialogWith = sipIdInDialogWith; } } internal class PTTEventArgs : EventArgs { PTT_Type _pttType; int _interval; long _radioID; byte _TSN; public PTT_Type PTTTypeEnum { get { return _pttType; } } public byte TSN { get { return _TSN; } } /// /// Interval, in seconds /// public int Interval { get { return _interval; } } /// /// Reprezinta SURSA streamului RTP in cazul Grant, Deny /// si DESTINATIA in cazul HeartbeatQuery si Heartbeat /// public long RadioID { get { return _radioID; } } /// /// Returns the id in dialog with /// public int SipIDinDialogWith { get; private set; } public PTTEventArgs(int idInDialogWith, long radioID, PTT_Type pttType, byte TSN, int interval) { SipIDinDialogWith = idInDialogWith; _radioID = radioID; _pttType = pttType; _interval = interval; _TSN = TSN; } } }