SafeDispatch/SipComponent/DMR.cs

650 lines
25 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.Text;
using System.Threading.Tasks;
namespace SipComponent
{
class DMR
{
private object _lockObject = new object();
//
// DMR properties
//
/// <summary>
/// The period in ms between sending PTT requests
/// </summary>
const int _T_request = 240;
//const int _T_request = 120;
/// <summary>
/// The total number of PTT Requests that can be sent.
/// </summary>
const int _T_numreq = 3;
/// <summary>
/// The period in ms between sending PTT End
/// </summary>
const int _T_end = 20;
/// <summary>
/// The total number of PTT End that are to be sent
/// </summary>
const int _T_numend = 3;
/// <summary>
/// The time in seconds between sending of PTT Heartbeats
/// </summary>
const byte _T_hearbeat = 10;
/// <summary>
/// Returns the time in seconds between sending of PTT Heartbeats
/// </summary>
public static int T_heartbeat
{
get { return _T_hearbeat; }
}
/// <summary>
/// Gets the period in ms between sending PTT End
/// </summary>
public static int T_end
{
get { return _T_end; }
}
/// <summary>
/// Gets the total number of PTT End that are to be sent
/// </summary>
public static int T_numend
{
get { return _T_numend; }
}
/// <summary>
/// Returns the period in ms between sending PTT requests
/// </summary>
public static int T_request
{
get { return _T_request; }
}
/// <summary>
/// Gets the total number of PTT Requests that can be sent.
/// </summary>
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 <bit> / (samplingRate <1/s> * bitRate <bit>)
* - 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 <bit> / (samplingRate <1/s> * bitRate <bit>)
* - 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 <bit> / (samplingRate <1/s> * bitRate <bit>)
* - 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
/// <summary>
/// Contains the types of call used in Half duplex mode
/// </summary>
internal enum RtpCallType : byte
{
/// <summary>
/// DMR call, private
/// 0000 0000
///
/// </summary>
Private = 0x00,
/// <summary>
/// DMR call, group
/// 0000 0100
/// </summary>
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
}
/// <summary>
/// Contains a list of sip types of calls
/// </summary>
public enum TypeOfCall
{
/// <summary>
/// Full duplex call, like a normal phone (or Zoipper)
/// </summary>
FULL_DUPLEX,
/// <summary>
/// Half duplex call, like a radio PTT
/// </summary>
HALF_DUPLEX
}
/// <summary>
/// Provides data for the VoiceReceived event
/// </summary>
public class AudioEventArgs : EventArgs
{
byte[] _buffer;
int _callSourceID;
/// <summary>
/// Voice buffer
/// </summary>
public byte[] Buffer
{
get { return _buffer; }
}
/// <summary>
/// The sip ID of the user who sent the voice buffer
/// </summary>
public int CallSourceID
{
get { return _callSourceID; }
}
/// <summary>
/// Gets the sip id that we are in dialog with
/// </summary>
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; }
}
/// <summary>
/// Interval, in seconds
/// </summary>
public int Interval
{
get { return _interval; }
}
/// <summary>
/// <para>Reprezinta SURSA streamului RTP in cazul Grant, Deny</para>
/// <para>si DESTINATIA in cazul HeartbeatQuery si Heartbeat</para>
/// </summary>
public long RadioID
{
get { return _radioID; }
}
/// <summary>
/// Returns the id in dialog with
/// </summary>
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;
}
}
}