hawkeye camera support(not tested)

This commit is contained in:
meelstorm
2025-08-19 12:45:50 +02:00
parent f80b275ad8
commit 6ef6aced8d
170 changed files with 20190 additions and 72 deletions

View File

@@ -0,0 +1,159 @@
using System;
using System.IO;
using Mono.Unix.Native;
namespace Inspectron.Devices.Raspberry
{
public unsafe class I2CLinux
{
string device;
int fd = -1;
public I2CLinux(int index)
{
device = "/dev/i2c-" + index;
Open();
//Close();
}
public void Open()
{
fd = Syscall.open(device, OpenFlags.O_RDWR);
if (fd < 0)
throw new IOException(device);
}
void IoCtl(byte devAddr)
{
int ret = LunixNatives.ioctl(fd, LunixNatives.I2C_SLAVE, devAddr);
if (ret < 0)
throw new IOException(device + ": ioctl");
}
public byte readBytes(byte devAddr, byte regAddr, byte length, byte[] data, int offset, ushort timeout = 0)
{
if (length > 127)
throw new IOException(device + ": length > 127");
//Open();
IoCtl(devAddr);
//fixed(byte* p = &regAddr)
{
int ret = (int)Syscall.write(fd, &regAddr, 1);
if (ret != 1)
throw new IOException(device + ": write");
}
int count;
fixed (byte* p = &data[offset])
{
count = (int)Syscall.read(fd, p, (ulong)length);
if (count < 0)
throw new IOException(device + ": read");
else if (count != length)
throw new IOException(device + ": read short: length = " + length + " > " + count);
}
//Close();
return (byte)count;
}
public byte readBytes(byte devAddr, byte regAddr, byte length, byte[] data, ushort timeout = 0)
{
return readBytes(devAddr, regAddr, length, data, 0, timeout);
}
/** Write multiple bytes to an 8-bit device register.
* @param devAddr I2C slave device address
* @param regAddr First register address to write to
* @param length Number of bytes to write
* @param data Buffer to copy new data from
* @return Status of operation (true = success)
*/
public void writeBytes(byte devAddr, byte regAddr, byte length, byte[] data)
{
if (length > 127)
throw new IOException(device + ": length > 127");
//Open();
IoCtl(devAddr);
byte[] buffer = new byte[128];
buffer[0] = regAddr;
Array.Copy(data, 0, buffer, 1, length);
int count;
fixed (byte* p = buffer)
{
count = (int)Syscall.write(fd, p, (ulong)(length + 1));
}
if (count < 0)
{
throw new IOException(device + ": write = " + count);
}
else if (count != length + 1)
{
throw new IOException(device + ": write short = " + count);
}
//Close();
}
/** Write multiple words to a 16-bit device register.
* @param devAddr I2C slave device address
* @param regAddr First register address to write to
* @param length Number of words to write
* @param data Buffer to copy new data from
* @return Status of operation (true = success)
*/
public void writeWords(byte devAddr, byte regAddr, byte length, ushort[] data)
{
int count = 0;
byte[] buf = new byte[128];
int i;
// Should do potential byteswap and call writeBytes() really, but that
// messes with the callers buffer
if (length > 63)
{
throw new IOException(device + ": length > 63");
}
//Open();
IoCtl(devAddr);
buf[0] = regAddr;
for (i = 0; i < (int)length; i++)
{
buf[i * 2 + 1] = (byte)(data[i] >> 8);
buf[i * 2 + 2] = (byte)data[i];
}
fixed (byte* p = buf)
{
count = (int)Syscall.write(fd, p, (ulong)(length * 2 + 1));
}
if (count < 0)
{
throw new IOException(device + ": write");
}
else if (count != length * 2 + 1)
{
throw new IOException(device + ": write short");
}
//Close();
}
public void Close()
{
int ret = Syscall.close(fd);
if (ret != 0)
throw new IOException(device);
}
}
}

View File

@@ -0,0 +1,112 @@
using System;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Threading;
using Inspectron.Devices.Raspberry;
using Inspectron.HawkEye.Protocol;
using Inspectron.HawkEye.Protocol.Interfaces;
namespace Inspectron.HawkEye.Camera
{
public class InspectronCamera:ICameraControl,IImageSource,ILightControl
{
[DllImport("libVCLibProxy.so", CallingConvention = CallingConvention.Cdecl)]
static extern IntPtr init(Int32 captBuf);
[DllImport("libVCLibProxy.so", CallingConvention = CallingConvention.Cdecl)]
static extern int trigger(IntPtr cpt, byte[] addr, int lines, int captBuf,ref int cancelFlag);
[DllImport("libVCLibProxy.so", CallingConvention = CallingConvention.Cdecl)]
static extern void set_parameters(IntPtr cpt, ref ImageSettings imageSettings);
[DllImport("libVCLibProxy.so", CallingConvention = CallingConvention.Cdecl)]
public static extern int test();
byte[] _buffer = new byte[2048 * 250 * 4];
public InspectronCamera()
{
_i2c = new I2CLinux(0);
_i2c.Open();
}
private bool _isRunningContiniuos = false;
private IntPtr _cp= IntPtr.Zero;
private ImageSettings _imageSettings=new ImageSettings()
{
CaptureBuffer = 25,
Gain = 200,
Lines = 1000,
Shutter = 200,
SensorWidth = 1440
};
private I2CLinux _i2c;
private int _lastBuffer = -1;
public void SetParameters(CameraSettings cameraSettings)
{
_cameraSettings = cameraSettings;
ImageSettings imageSettings = cameraSettings.ImageSettings;
if (_lastBuffer ==-1)
{
_lastBuffer = imageSettings.CaptureBuffer;
_cp = init(_lastBuffer);
}
_imageSettings = imageSettings;
_buffer=new byte[imageSettings.SensorWidth*(imageSettings.Lines)];
var cpImageSettings = imageSettings;
cpImageSettings.UseExternalTrigger =imageSettings.UseExternalTrigger;
if(_lastBuffer==imageSettings.CaptureBuffer)
set_parameters(_cp, ref cpImageSettings);
else
Console.WriteLine("Warning! Buffer size changed. Needs restart");
_i2c.writeBytes(4, 2, 4, BitConverter.GetBytes(imageSettings.Divider));
Thread.Sleep(100);
_i2c.writeBytes(4,3,1, new byte[] { (byte)imageSettings.UseExternalTrigger });
Thread.Sleep(100);
}
private int _cancelFlag = 0;
private CameraSettings _cameraSettings;
public byte[] GetImage()
{
_cancelFlag = 0;
var sw = Stopwatch.StartNew();
_i2c.writeBytes(4, 4, 1,new byte[]{ (byte)(_cameraSettings.LaserTrigger?1:0)} );
//Thread.Sleep(20);
_i2c.writeBytes(4, 5, 4, BitConverter.GetBytes(_cameraSettings.LaserTriggerDelay));
//Thread.Sleep(20);
trigger(_cp, _buffer, _imageSettings.Lines, _imageSettings.CaptureBuffer,ref _cancelFlag);
sw.Stop();
Console.WriteLine("Trigger time: "+sw.ElapsedMilliseconds);
if(_cancelFlag==1)return new byte[0];
return _buffer;
}
public void CancelTrigger()
{
_cancelFlag = 1;
}
public void SetLight(int pwm1, int pwm2)
{
Console.WriteLine($"setting lights to {pwm1}/{pwm2}");
pwm1 = (int)(pwm1 / 100.0 * 255);
pwm2 = (int)(pwm2 / 100.0 * 255);
_i2c.writeBytes(4, 1, 2, new byte[] { (byte)pwm1,(byte)pwm2 });
Thread.Sleep(10);
}
}
}

View File

@@ -0,0 +1,21 @@
using System.Runtime.InteropServices;
namespace Inspectron.Devices.Raspberry
{
public static class LunixNatives
{
public const int O_RDWR = 2;
[DllImport("libc.so.6")]
extern public static int open(string file, int mode);
[DllImport("libc.so.6")]
extern public static int close(int fd);
[DllImport("libc.so.6")]
extern public static int ioctl(int fd, int request, byte x);
public const int I2C_SLAVE = 0x0703;
}
}

View File

@@ -0,0 +1,104 @@
using System;
using System.Collections;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Threading;
namespace Inspectron.HawkEye
{
public class DefragmentedPacket
{
private readonly uint _packetSize;
private byte[] _receivedParts = null;
//ConcurrentDictionary<uint,byte[]> _packetParts = new ConcurrentDictionary<uint, byte[]>();
private byte[][] _packetParts;
public DefragmentedPacket(uint packetSize)
{
_packetSize = packetSize;
_packetParts=new byte[30000][];
}
private int _uniquePackets=0;
public void Defragment(byte[] data)
{
var packetStart = -1;
for (int i = 0; i < data.Length; i++)
{
if (BitConverter.ToUInt32(data, i) == 114455)
{
packetStart = i;
break;
};
}
if (packetStart == -1) return;
MemoryStream ms = new MemoryStream(data,packetStart, data.Length - packetStart);
BinaryReader br = new BinaryReader(ms);
br.ReadUInt32();//packetStart
var packetType = br.ReadUInt32();
var sequenceId = br.ReadUInt32();
var packetNumber = br.ReadUInt32();
var totalPackets = br.ReadUInt32();
if(_receivedParts==null)_receivedParts=new byte[totalPackets];
if (_receivedParts[packetNumber] == 1) return;
_receivedParts[packetNumber] = 1;
Interlocked.Increment(ref _uniquePackets);
var dataLen = br.ReadInt32();
var dataBytes = br.ReadBytes(dataLen);
_packetParts[packetNumber] = dataBytes;
}
public byte[] Reconstruct()
{
var parts = _receivedParts.Length;
byte[] res = new byte[parts*_packetSize];
Stopwatch sw = Stopwatch.StartNew();
int resSize = 0;
for (int i = 0; i < parts; i++)
{
//if (_packetParts.ContainsKey((uint) i))
if (_packetParts[i]!=null)
{
var packetData = _packetParts[(uint) i];
Array.Copy(packetData, 0,res, resSize, packetData.Length);
resSize += packetData.Length;
}
else
{
resSize += (int)_packetSize-20/*headerSize*/;
}
}
sw.Stop();
Array.Resize(ref res,resSize);
return res;
}
public bool IsComplete
{
get
{
if (_receivedParts == null) return false;
return _uniquePackets == _receivedParts.Length;
//return _receivedParts.All(x => x == 1);
}
}
}
}

View File

@@ -0,0 +1,105 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
namespace Inspectron.HawkEye
{
public class FragmentedPacket
{
private readonly uint _packetSize;
private readonly EPacketType _packetType;
private readonly uint _sequnceId;
public FragmentedPacket(uint packetSize,EPacketType packetType,uint sequnceId)
{
_packetSize = packetSize;
_packetType = packetType;
_sequnceId = sequnceId;
}
public IEnumerable<byte[]> Fragment(byte[] packetData)
{
uint dataPtr = 0;
int packetNumber = 0;
uint headerSize = 20;
uint payloadSize = (_packetSize - headerSize);
var totalPackets = (uint)Math.Ceiling(((double)packetData.Length / payloadSize));
do
{
MemoryStream ms = new MemoryStream();
BinaryWriter bw = new BinaryWriter(ms);
bw.Write((uint) 114455); //packetStart //4
bw.Write((uint) _packetType); //8
bw.Write((uint) _sequnceId); //12
bw.Write((uint) packetNumber); //16
//total packets?
bw.Write(totalPackets); //20
byte[] data = new byte[payloadSize];
uint dataSize = Math.Min((uint)(packetData.Length-dataPtr), payloadSize);
Array.Copy(packetData, dataPtr, data, 0, dataSize);
dataPtr += dataSize;
bw.Write(dataSize);
bw.Write(data);
packetNumber += 1;
yield return ms.ToArray();
} while (packetNumber < totalPackets);
}
public IEnumerable<byte[]> FragmentTo(byte[] packetData,UDPSocket socket)
{
uint dataPtr = 0;
int packetNumber = 0;
uint headerSize = 20;
uint payloadSize = (_packetSize - headerSize);
var totalPackets = (uint)Math.Ceiling(((double)packetData.Length / payloadSize));
do
{
MemoryStream ms = new MemoryStream();
BinaryWriter bw = new BinaryWriter(ms);
bw.Write((uint)114455); //packetStart //4
bw.Write((uint)_packetType); //8
bw.Write((uint)_sequnceId); //12
bw.Write((uint)packetNumber); //16
//total packets?
bw.Write(totalPackets); //20
byte[] data = new byte[payloadSize];
uint dataSize = Math.Min((uint)(packetData.Length - dataPtr), payloadSize);
Array.Copy(packetData, dataPtr, data, 0, dataSize);
dataPtr += dataSize;
bw.Write(dataSize);
bw.Write(data);
packetNumber += 1;
yield return ms.ToArray();
} while (packetNumber < totalPackets);
}
}
}

View File

@@ -0,0 +1,21 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Mono.Posix.NETStandard" Version="1.0.0" />
<PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
<PackageReference Include="NLog" Version="4.7.3" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,9 @@
namespace Inspectron.HawkEye
{
public enum EPacketType
{
Test,
ImageData,
ImageRequest
}
}

View File

@@ -0,0 +1,7 @@
namespace Inspectron.HawkEye
{
public class ImageRequestPacket
{
}
}

View File

@@ -0,0 +1,10 @@
using System;
namespace Inspectron.HawkEye
{
public class Packet
{
public EPacketType PacketType { get; set; }
public byte[] Payload { get; set; }
}
}

View File

@@ -0,0 +1,12 @@
namespace Inspectron.HawkEye
{
public class PacketImage
{
public uint TriggerId { get; set; }
public uint PacketId { get; set; }
public uint TotalPackets { get; set; }
public uint StartIndex { get; set; }
public byte[] Data { get; set; }
}
}

View File

@@ -0,0 +1,22 @@
using System.Collections.Generic;
namespace Inspectron.HawkEye.Protocol
{
public class CameraSettings
{
public ImageSettings ImageSettings { get; set; }
public int LightPwm1 { get; set; }
public int LightPwm2 { get; set; }
public string Name { get; set; }
public int OffsetX { get; set; }
public int ImageWidth { get; set; }
public int RescaleWidth { get; set; }
public int MinorCutoff { get; set; }
public bool BayerFilter { get; set; }
public bool LaserTrigger { get; set; }
public int LaserTriggerDelay { get; set; }
public bool FlipLines { get; set; }
public bool MirrorX { get; set; }
public bool TriggerLights { get; set; }
}
}

View File

@@ -0,0 +1,12 @@
using System.Net;
using System.Net.NetworkInformation;
namespace Inspectron.HawkEye.Protocol.Discovery
{
public class CameraInfo
{
public string Mac { get; set; }
public IPAddress Address { get; set; }
public IPAddress AdapterAddress { get; set; }
}
}

View File

@@ -0,0 +1,84 @@
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Net;
using System.Net.NetworkInformation;
using System.Net.Sockets;
using System.Text;
using System.Threading.Tasks;
namespace Inspectron.HawkEye.Protocol.Discovery
{
public class DiscoveryClient
{
public ReadOnlyCollection<CameraInfo> Discovered => new ReadOnlyCollection<CameraInfo>(_discovered);
private readonly int _port;
private readonly List<CameraInfo> _discovered = new List<CameraInfo>();
private readonly object _discoveryLock = new object();
public DiscoveryClient(int port)
{
_port = port;
}
public event Action<CameraInfo> CameraFound = delegate { };
public void Discover()
{
var allInterfaces = NetworkInterface
.GetAllNetworkInterfaces()
.Where(nic => nic.OperationalStatus == OperationalStatus.Up);
Parallel.ForEach(allInterfaces, DiscoverOnInterface);
//foreach (NetworkInterface i in allInterfaces)
//{
// DiscoverOnInterface(i);
//}
}
private void DiscoverOnInterface(NetworkInterface iface)
{
var address = iface.GetIPProperties().UnicastAddresses
.First(x => x.Address.AddressFamily == AddressFamily.InterNetwork).Address;
UdpClient client;
lock (_discoveryLock)
{
client = new UdpClient(new IPEndPoint(address, 0));
var requestData = Encoding.ASCII.GetBytes("discovery");
client.Client.ReceiveTimeout = 2000;
var s = client.Client;
s.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.Broadcast, 1);
s.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.DontRoute, 1);
client.EnableBroadcast = true;
client.Send(requestData, requestData.Length, new IPEndPoint(IPAddress.Broadcast, _port));
client.Send(requestData, requestData.Length, new IPEndPoint(IPAddress.Broadcast, _port));
client.Send(requestData, requestData.Length, new IPEndPoint(IPAddress.Broadcast, _port));
}
var serverEp = new IPEndPoint(IPAddress.Any, 0);
byte[] serverResponseData;
try
{
serverResponseData = client.Receive(ref serverEp);
var serverResponse = Encoding.ASCII.GetString(serverResponseData);
Console.WriteLine("Recived {0} from {1}", serverResponse, serverEp.Address);
var found = new CameraInfo {Mac = serverResponse, Address = serverEp.Address,AdapterAddress = address};
lock (_discoveryLock)
{
if (_discovered.Any(x => x.Mac == found.Mac)) return;
}
CameraFound(found);
_discovered.Add(found);
}
catch
{
}
}
}
}

View File

@@ -0,0 +1,55 @@
using System;
using System.Linq;
using System.Net;
using System.Net.NetworkInformation;
using System.Net.Sockets;
using System.Text;
using System.Threading;
namespace Inspectron.HawkEye.Protocol.Discovery
{
public class DiscoveryServer
{
private UdpClient _server;
private byte[] _name;
public DiscoveryServer(int port,string name=null)
{
_server = new UdpClient(port);
if(name==null)
{ _name = Encoding.UTF8.GetBytes( NetworkInterface
.GetAllNetworkInterfaces()
.Where(nic => nic.OperationalStatus == OperationalStatus.Up && nic.NetworkInterfaceType != NetworkInterfaceType.Loopback)
.Select(nic => nic.GetPhysicalAddress().ToString())
.FirstOrDefault());
}
else
{
_name = Encoding.UTF8.GetBytes(name);
}
}
public void Start()
{
Thread th = new Thread(DiscoveryLoop);
th.Start();
}
private void DiscoveryLoop()
{
while (true)
{
var clientEp = new IPEndPoint(IPAddress.Any, 0);
var clientRequestData = _server.Receive(ref clientEp);
var clientRequest = Encoding.ASCII.GetString(clientRequestData);
Console.WriteLine("Recived {0} from {1}, sending response", clientRequest, clientEp.Address.ToString());
_server.Send(_name, _name.Length, clientEp);
_server.Send(_name, _name.Length, clientEp);
_server.Send(_name, _name.Length, clientEp);
}
}
}
}

View File

@@ -0,0 +1,13 @@
namespace Inspectron.HawkEye.Protocol
{
public enum ECommand
{
Connect,
Trigger, StartContinuous, StopContinuous,
Settings,SaveSettings,
OK,
SaveCalibration,
GetCalibration,
NotOK
}
}

View File

@@ -0,0 +1,9 @@
namespace Inspectron.HawkEye.Protocol
{
public enum EData
{
Image,
OK
}
}

View File

@@ -0,0 +1,114 @@
using System;
using System.Net;
using System.Text;
using System.Threading;
using Inspectron.HawkEye.UDPB;
using Newtonsoft.Json;
namespace Inspectron.HawkEye.Protocol
{
public class ImageClient : IDisposable
{
private static readonly object connectionLock = new object();
private readonly IPEndPoint _endpoint;
private readonly IPAddress _adapter;
private readonly UDPBSocket _imageSocket = new UDPBSocket();
private readonly UDPBSocket _commandSocket = new UDPBSocket();
private bool _isConnected = true;
public ImageClient(IPEndPoint endpoint,IPAddress adapter)
{
_endpoint = endpoint;
_adapter = adapter;
}
public void Dispose()
{
_isConnected = false;
_commandSocket.Dispose();
}
public void Connect()
{
lock (connectionLock)
{
_commandSocket.Connect(_endpoint,_adapter);
var port = UDPBSocket.FindFreePort(_adapter);
var bytesPort = BitConverter.GetBytes(port);
_commandSocket.SendData(new[]
{(byte) ECommand.Connect, bytesPort[0], bytesPort[1], bytesPort[2], bytesPort[3]});
var settingsData0 = _commandSocket.Receive();
var b = new byte[settingsData0.Length - 1];
Array.Copy(settingsData0, 1, b, 0, b.Length);
var settingsString = Encoding.UTF8.GetString(b);
SettingsReceived(JsonConvert.DeserializeObject<CameraSettings>(settingsString));
_imageSocket.Listen(_adapter,port);
var th = new Thread(ReceiveLoop);
th.Start();
}
}
public event Action<byte[]> ImageReceived = delegate { };
public event Action<CameraSettings> SettingsReceived = delegate { };
public void Trigger()
{
_commandSocket.SendData(new[] {(byte) ECommand.Trigger});
}
public void ApplySettings(CameraSettings cameraSettings)
{
var data = Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(cameraSettings));
var databytes = new byte[1000];
databytes[0] = (byte) ECommand.Settings;
Array.Copy(data, 0, databytes, 1, data.Length);
_commandSocket.SendData(databytes);
}
public void StartContinuous()
{
_commandSocket.SendData(new[] {(byte) ECommand.StartContinuous});
}
public void StopContinuous()
{
_commandSocket.SendData(new[] {(byte) ECommand.StopContinuous});
}
public void SaveSettingsOnCamera()
{
_commandSocket.SendData(new[] {(byte) ECommand.SaveSettings});
}
private void ReceiveLoop()
{
while (_isConnected)
{
var data = _imageSocket.Receive();
Process(data);
}
}
private void Process(byte[] data)
{
switch ((EData) data[0])
{
case EData.Image:
var b = new byte[data.Length - 1];
Array.Copy(data, 1, b, 0, b.Length);
ImageReceived(data);
break;
default:
throw new ArgumentOutOfRangeException();
}
}
}
}

View File

@@ -0,0 +1,192 @@
using System;
using System.Collections.Generic;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using Inspectron.HawkEye.UDPB;
using Newtonsoft.Json;
namespace Inspectron.HawkEye.Protocol
{
public class ImageClientTCP : IDisposable
{
private static readonly object connectionLock = new object();
private readonly IPEndPoint _endpoint;
private readonly IPAddress _adapter;
private TcpListener _imageSocket;
private TcpClient _commandSocket;
private bool _isConnected = true;
public ImageClientTCP(IPEndPoint endpoint,IPAddress adapter)
{
_endpoint = endpoint;
_adapter = adapter;
}
public void Dispose()
{
_isConnected = false;
_commandSocket.Dispose();
}
byte[] _commandBuffer = new byte[1500];
private TcpClient _lastClient;
public bool SupportsCalibration { get; set; }
public void Connect()
{
lock (connectionLock)
{
_commandSocket=new TcpClient(new IPEndPoint(_adapter, 0));
Console.WriteLine($"Bind on {_adapter?.ToString()}");
_commandSocket.Connect(_endpoint);
var port = UDPBSocket.FindFreePort(_adapter);
Console.WriteLine("Connected");
_imageSocket = new TcpListener(_adapter, port);
_imageSocket.Start();
Console.WriteLine("TCP started");
var bytesPort = BitConverter.GetBytes(port);
_commandSocket.Client.SendData(new[]
{(byte) ECommand.Connect, bytesPort[0], bytesPort[1], bytesPort[2], bytesPort[3]});
var receivedLen = _commandSocket.Client.Receive(_commandBuffer);
Console.WriteLine("received answer length:"+receivedLen);
var b = new byte[receivedLen - 1];
Array.Copy(_commandBuffer, 1, b, 0, b.Length);
var settingsString = Encoding.UTF8.GetString(b);
SettingsReceived(JsonConvert.DeserializeObject<CameraSettings>(settingsString));
if (SupportsCalibration)
{
_commandSocket.Client.SendData(new[] {(byte) ECommand.GetCalibration});
receivedLen = _commandSocket.Client.Receive(_commandBuffer);
if (_commandBuffer[0] == (byte) ECommand.OK)
{
Console.WriteLine($"calibration received {receivedLen} bytes");
var c = new byte[receivedLen - 1];
Array.Copy(_commandBuffer, 1, c, 0, c.Length);
var calibrationString = Encoding.UTF8.GetString(c);
CalibrationReceived(JsonConvert.DeserializeObject<List<Dictionary<double,double>>>(calibrationString));
}
else
{
Console.WriteLine("no calibration received");
}
}
var th = new Thread(ReceiveLoop);
th.Start();
}
}
public event Action<byte[]> ImageReceived = delegate { };
public event Action<CameraSettings> SettingsReceived = delegate { };
public event Action<List<Dictionary<double,double>>> CalibrationReceived = delegate { };
public void Trigger()
{
_commandSocket.Client.SendData(new[] {(byte) ECommand.Trigger});
}
public void ApplySettings(CameraSettings cameraSettings)
{
var data = Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(cameraSettings));
var databytes = new byte[1000];
databytes[0] = (byte) ECommand.Settings;
Array.Copy(data, 0, databytes, 1, data.Length);
_commandSocket.Client.SendData(databytes);
byte[] ok = new byte[1];
_commandSocket.Client.Receive(ok);
}
public void StartContinuous()
{
_commandSocket.Client.SendData(new[] {(byte) ECommand.StartContinuous});
}
public void StopContinuous()
{
_commandSocket.Client.SendData(new[] {(byte) ECommand.StopContinuous});
}
public void SaveSettingsOnCamera()
{
_commandSocket.Client.SendData(new[] {(byte) ECommand.SaveSettings});
}
private byte[] _imageBuffer = new byte[10*1024*1024];
private void ReceiveLoop()
{
while (_isConnected)
{
_lastClient = _imageSocket.AcceptTcpClient();
while (true)
{
int received=0;
try
{
_lastClient.Client.Receive(_imageBuffer, 0, 1,
SocketFlags.None);
Process(_imageBuffer);
}
catch
{
break;
}
}
}
}
public void SaveCalibration(List<Dictionary<double,double>> calibration)
{
var data = Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(calibration));
var databytes = new byte[1500];
databytes[0] = (byte)ECommand.SaveCalibration;
Array.Copy(data, 0, databytes, 1, data.Length);
_commandSocket.Client.SendData(databytes);
byte[] ok = new byte[1];
_commandSocket.Client.Receive(ok);
}
private void Process(byte[] data)
{
switch ((EData) data[0])
{
case EData.Image:
_lastClient.Client.Receive(_imageBuffer, 1, 4,
SocketFlags.None);
var imageSize = BitConverter.ToInt32(_imageBuffer,1);
var received = 0;
do
{
received += _lastClient.Client.Receive(_imageBuffer, 5+ received, imageSize- received,
SocketFlags.None);
} while (received < imageSize);
var b = new byte[imageSize];
Array.Copy(data, 5, b, 0, b.Length);
ImageReceived(b);
break;
default:
throw new ArgumentOutOfRangeException();
}
}
}
}

View File

@@ -0,0 +1,164 @@
using System;
using System.IO;
using System.Net;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Inspectron.HawkEye.Protocol.Interfaces;
using Inspectron.HawkEye.UDPB;
using Newtonsoft.Json;
namespace Inspectron.HawkEye.Protocol
{
public class ImageServer
{
private readonly IImageSource _imageSource;
private readonly ICameraControl _cameraControl;
private readonly ILightControl _lightControl;
private readonly UDPBSocket _commandSocket = new UDPBSocket();
private readonly UDPBSocket _imageSocket = new UDPBSocket();
private bool _autoTrigger;
private bool _applySettings;
private CameraSettings _imageSettingsToApply=new CameraSettings(){ImageSettings = new ImageSettings()};
public ImageServer(IImageSource imageSource, ICameraControl cameraControl, ILightControl lightControl)
{
_imageSource = imageSource;
_cameraControl = cameraControl;
_lightControl = lightControl;
_imageSocket.LossSimulation = 0;
}
public void Start()
{
_commandSocket.Listen(IPAddress.Any, 27001);
var th = new Thread(ReceiveLoop);
th.Start();
if (File.Exists("settings.json"))
{
var settings = JsonConvert.DeserializeObject<CameraSettings>(File.ReadAllText("settings.json"));
_imageSettingsToApply = settings;
ApplyParameters(settings);
}
}
public void ApplyParameters(CameraSettings cameraSettings)
{
_cameraControl.SetParameters(cameraSettings);
_lightControl.SetLight(cameraSettings.LightPwm1, cameraSettings.LightPwm2);
}
private void SaveSettingsLocally()
{
File.WriteAllText("settings.json", JsonConvert.SerializeObject(_imageSettingsToApply));
}
private void ReceiveLoop()
{
while (true)
{
var data = _commandSocket.Receive();
try
{
ProcessCommand(data);
}
catch (Exception e)
{
Console.WriteLine(e);
}
}
}
private void TriggerLoop()
{
while (_autoTrigger)
{
if (_applySettings)
{
ApplyParameters(_imageSettingsToApply);
_applySettings = false;
}
try
{
SendImage();
}
catch
{
return;
}
}
}
private void ProcessCommand(byte[] data)
{
Console.WriteLine(((ECommand) data[0]).ToString());
switch ((ECommand) data[0])
{
case ECommand.Connect:
{
var port = new byte[4];
Array.Copy(data, 1, port, 0, 4);
_imageSocket.Connect(new IPEndPoint((_commandSocket.LastConnection as IPEndPoint).Address, BitConverter.ToInt32(port,0)),null);
var databytes = new byte[1000];
var settings = Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(_imageSettingsToApply));
Array.Copy(settings, 0, databytes, 1, settings.Length);
databytes[0] = (byte) ECommand.Settings;
_commandSocket.SendData(databytes);
}
break;
case ECommand.Trigger:
{
Task.Run(() => { SendImage(); });
}
break;
case ECommand.StartContinuous:
{
_autoTrigger = true;
var th = new Thread(TriggerLoop);
th.Start();
}
break;
case ECommand.StopContinuous:
{
_autoTrigger = false;
}
break;
case ECommand.Settings:
{
var databytes = new byte[999];
Array.Copy(data, 1, databytes, 0, 999);
var settings = JsonConvert.DeserializeObject<CameraSettings>(Encoding.UTF8.GetString(databytes));
_imageSettingsToApply = settings;
if (_autoTrigger)
_applySettings = true;
else
ApplyParameters(_imageSettingsToApply);
}
break;
case ECommand.SaveSettings:
{
SaveSettingsLocally();
}
break;
default:
throw new ArgumentOutOfRangeException();
}
}
private void SendImage()
{
var image = _imageSource.GetImage();
var b = new byte[image.Length + 1];
b[0] = (byte) EData.Image;
Array.Copy(image, 0, b, 1, image.Length);
_imageSocket.SendData(b);
}
}
}

View File

@@ -0,0 +1,269 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Inspectron.HawkEye.Protocol.Interfaces;
using Inspectron.HawkEye.UDPB;
using Newtonsoft.Json;
namespace Inspectron.HawkEye.Protocol
{
public class ImageServerTCP
{
private readonly IImageSource _imageSource;
private readonly ICameraControl _cameraControl;
private readonly ILightControl _lightControl;
private TcpListener _commandSocket;
private TcpClient _imageSocket;
private bool _autoTrigger;
private bool _applySettings;
private CameraSettings _imageSettingsToApply=new CameraSettings(){ImageSettings = new ImageSettings()};
private TcpClient _lastClient;
public ImageServerTCP(IImageSource imageSource, ICameraControl cameraControl, ILightControl lightControl)
{
_imageSource = imageSource;
_cameraControl = cameraControl;
_lightControl = lightControl;
}
public void Start()
{
_commandSocket = new TcpListener(IPAddress.Any, 27001);
_commandSocket.Start();
Console.WriteLine("listen tcp");
var th = new Thread(ReceiveLoop);
th.Start();
if (File.Exists("settings.json"))
{
var settings = JsonConvert.DeserializeObject<CameraSettings>(File.ReadAllText("settings.json"));
_imageSettingsToApply = settings;
ApplyParameters(settings);
}
}
public void ApplyParameters(CameraSettings cameraSettings, bool setLight = false)
{
Console.WriteLine("Setting parameters:"+JsonConvert.SerializeObject(cameraSettings,Formatting.Indented));
_cameraControl.SetParameters(cameraSettings);
if(setLight&&!cameraSettings.TriggerLights) _lightControl.SetLight(cameraSettings.LightPwm1, cameraSettings.LightPwm2);
else _lightControl.SetLight(0, 0);
}
private void SaveSettingsLocally()
{
File.WriteAllText("settings.json", JsonConvert.SerializeObject(_imageSettingsToApply,Formatting.Indented));
}
byte[] _commandBuffer = new byte[1500];
private Task _lastTriggerTask=Task.CompletedTask;
private void ReceiveLoop()
{
while (true)
{
_lastClient=_commandSocket.AcceptTcpClient();
do
{
try
{
_lastClient.GetStream().Read(_commandBuffer, 0, 1500);
}
catch
{
break;
}
ProcessCommand(_commandBuffer);
} while (_lastClient.Connected);
CancelTrigger();
_lightControl.SetLight(0, 0);
}
}
private void TriggerLoop()
{
while (_autoTrigger)
{
if (_applySettings)
{
ApplyParameters(_imageSettingsToApply,true);
_applySettings = false;
}
try
{
SendImage();
}
catch(Exception e)
{
Console.WriteLine(e.ToString());
return;
}
}
}
private void ProcessCommand(byte[] data)
{
Console.WriteLine(((ECommand) data[0]).ToString());
switch ((ECommand) data[0])
{
case ECommand.Connect:
{
var port = new byte[4];
Array.Copy(data, 1, port, 0, 4);
_imageSocket=new TcpClient();
_imageSocket.Connect(new IPEndPoint((_lastClient.Client.RemoteEndPoint as IPEndPoint).Address, BitConverter.ToInt32(port,0)));
var databytes = new byte[1000];
var settings = Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(_imageSettingsToApply));
Array.Copy(settings, 0, databytes, 1, settings.Length);
databytes[0] = (byte) ECommand.Settings;
_lastClient.Client.SendData(databytes);
Console.WriteLine("data sent");
_lightControl.SetLight(_imageSettingsToApply.LightPwm1, _imageSettingsToApply.LightPwm2);
_autoTrigger = false;
}
break;
case ECommand.GetCalibration:
{
var databytes = new byte[1500];
if (File.Exists("calibration.calib"))
{
databytes[0] = (byte)ECommand.OK;
Array.Copy(File.ReadAllBytes("calibration.calib"),0,databytes,1,1500-1);
_lastClient.Client.SendData(databytes);
}
else
{
databytes[0] = (byte)ECommand.NotOK;
_lastClient.Client.SendData(databytes);
}
}
break;
case ECommand.Trigger:
{
CancelTrigger();
_lastTriggerTask=Task.Run(() =>
{
try
{
SendImage();
}
catch (Exception e)
{
Console.WriteLine(e.ToString());
}
});
}
break;
case ECommand.StartContinuous:
{
_autoTrigger = true;
var th = new Thread(TriggerLoop);
th.Start();
}
break;
case ECommand.StopContinuous:
{
_autoTrigger = false;
}
break;
case ECommand.Settings:
{
CancelTrigger();
var databytes = new byte[999];
Array.Copy(data, 1, databytes, 0, 999);
var settings = JsonConvert.DeserializeObject<CameraSettings>(Encoding.UTF8.GetString(databytes));
_imageSettingsToApply = settings;
if (_autoTrigger)
_applySettings = true;
else
ApplyParameters(_imageSettingsToApply,true);
_lastClient.Client.Send(new []{(byte)EData.OK});
}
break;
case ECommand.SaveCalibration:
{
var databytes = new byte[1500 - 1];
Array.Copy(data, 1, databytes, 0, 1500 - 1);
File.WriteAllBytes("calibration.calib",databytes);
_lastClient.Client.Send(new[] { (byte)EData.OK });
}
break;
case ECommand.SaveSettings:
{
SaveSettingsLocally();
}
break;
default:
throw new ArgumentOutOfRangeException();
}
}
private void CancelTrigger()
{
_imageSource.CancelTrigger();
_lastTriggerTask.Wait();
}
private void SendImage()
{
if (_imageSettingsToApply.TriggerLights)
{
_lightControl.SetLight(_imageSettingsToApply.LightPwm1, _imageSettingsToApply.LightPwm2);
}
var image = _imageSource.GetImage();
if (image.Length == 0) return;
if (_imageSettingsToApply.TriggerLights)
{
_lightControl.SetLight(0, 0);
}
var b = EncodeImage(image);
_imageSocket.Client.SendData(b);
}
private static byte[] EncodeImage(byte[] image)
{
var b = new byte[image.Length + 5];
b[0] = (byte) EData.Image;
var byteSize = BitConverter.GetBytes(image.Length);
Array.Copy(byteSize, 0, b, 1, 4);
Array.Copy(image, 0, b, 5, image.Length);
return b;
}
private static byte[] EncodeChanneledImage(byte[] image,byte channels)
{
var b = new byte[image.Length + 6];
b[0] = (byte)EData.Image;
b[1] = channels;
var byteSize = BitConverter.GetBytes(image.Length);
Array.Copy(byteSize, 0, b, 2, 4);
Array.Copy(image, 0, b, 6, image.Length);
return b;
}
}
}

View File

@@ -0,0 +1,17 @@
using System.Runtime.InteropServices;
namespace Inspectron.HawkEye.Protocol
{
[StructLayout(LayoutKind.Sequential, Pack = 0)]
public struct ImageSettings
{
public int Shutter { get; set; }
public int Gain { get; set; }
public int SensorWidth { get; set; }
public int Lines { get; set; }
public int CaptureBuffer { get; set; }
public int UseExternalTrigger { get; set; }
public int Divider { get; set; }
}
}

View File

@@ -0,0 +1,10 @@
namespace Inspectron.HawkEye.Protocol.Interfaces
{
public interface ICameraControl
{
void SetParameters(CameraSettings imageSettings);
}
}

View File

@@ -0,0 +1,8 @@
namespace Inspectron.HawkEye.Protocol.Interfaces
{
public interface IImageSource
{
byte[] GetImage();
void CancelTrigger();
}
}

View File

@@ -0,0 +1,7 @@
namespace Inspectron.HawkEye.Protocol.Interfaces
{
public interface ILightControl
{
void SetLight(int pwm1, int pwm2);
}
}

View File

@@ -0,0 +1,23 @@
using System;
using System.Net;
using System.Net.Sockets;
using Inspectron.HawkEye.RTSP;
namespace Inspectron.HawkEye.Protocol
{
public static class SocketExtensions
{
public static void Listen(this Socket self,IPAddress adapterAddress,int port)
{
self.Bind(new IPEndPoint(adapterAddress,port));
}
public static void SendData(this Socket self, byte[] data)
{
Console.WriteLine("send data");
self.Send(data);
}
}
}

View File

@@ -0,0 +1,136 @@
using System;
using System.Collections.Generic;
namespace Inspectron.HawkEye.RTSP
{
// This class handles the AAC-hbd (High Bitrate) Payload
// It has methods to process the RTP Payload
// (c) 2018 Roger Hardiman, RJH Technical Consultancy Ltd
/*
RFC 3640
3.3.6. High Bit-rate AAC
This mode is signaled by mode=AAC-hbr.This mode supports the
transportation of variable size AAC frames.In one RTP packet,
either one or more complete AAC frames are carried, or a single
fragment of an AAC frame is carried.In this mode, the AAC frames
are allowed to be interleaved and hence receivers MUST support de-
interleaving.The maximum size of an AAC frame in this mode is 8191
octets.
In this mode, the RTP payload consists of the AU Header Section,
followed by either one AAC frame, several concatenated AAC frames or
one fragmented AAC frame.The Auxiliary Section MUST be empty. For
each AAC frame contained in the payload, there MUST be an AU-header
in the AU Header Section to provide:
a) the size of each AAC frame in the payload and
b) index information for computing the sequence(and hence timing) of
each AAC frame.
To code the maximum size of an AAC frame requires 13 bits.
Therefore, in this configuration 13 bits are allocated to the AU-
size, and 3 bits to the AU-Index(-delta) field.Thus, each AU-header
has a size of 2 octets.Each AU-Index field MUST be coded with the
value 0. In the AU Header Section, the concatenated AU-headers MUST
be preceded by the 16-bit AU-headers-length field, as specified in
section 3.2.1.
In addition to the required MIME format parameters, the following
parameters MUST be present: sizeLength, indexLength, and
indexDeltaLength.AAC frames always have a fixed duration per Access
Unit; when interleaving in this mode, this specific duration MUST be
signaled by the MIME format parameter constantDuration.In addition,
the parameter maxDisplacement MUST be present when interleaving.
For example:
m= audio 49230 RTP/AVP 96
a= rtpmap:96 mpeg4-generic/48000/6
a= fmtp:96 streamtype= 5; profile-level-id= 16; mode= AAC-hbr;config= 11B0; sizeLength= 13; indexLength= 3;indexDeltaLength= 3; constantDuration= 1024
The hexadecimal value of the "config" parameter is the AudioSpecificConfig(), as defined in ISO/IEC 14496-3.
AudioSpecificConfig() specifies a 5.1 channel AAC stream with a sampling rate of 48 kHz.For the description of MIME parameters, see
section 4.1.
*/
public class AACPayload
{
public uint ObjectType = 0;
public uint FrequencyIndex = 0;
public uint ChannelConfiguration = 0;
// Constructor
public AACPayload(String config_string)
{
/***
5 bits: object type
if (object type == 31)
6 bits + 32: object type
4 bits: frequency index
if (frequency index == 15)
24 bits: frequency
4 bits: channel configuration
var bits: AOT Specific Config
***/
// config is a string in hex eg 1490 or 0x1210
// Read each ASCII character and add to a bit array
BitStream bs = new BitStream();
bs.AddHexString(config_string);
// Read 5 bits
ObjectType = bs.Read(5);
// Read 4 bits
FrequencyIndex = bs.Read(4);
// Read 4 bits
ChannelConfiguration = bs.Read(4);
}
public List<byte[]> Process_AAC_RTP_Packet(byte[] rtp_payload, int rtp_marker) {
// RTP Payload for MPEG4-GENERIC can consist of multple blocks.
// Each block has 3 parts
// Part 1 - Acesss Unit Header Length + Header
// Part 2 - Access Unit Auxiliary Data Length + Data (not used in AAC High Bitrate)
// Part 3 - Access Unit Audio Data
// The rest of the RTP packet is the AMR data
List<byte[]> audio_data = new List<byte[]>();
int ptr = 0;
while (true) {
if (ptr + 4 > rtp_payload.Length) break; // 2 bytes for AU Header Length, 2 bytes of AU Header payload
// Get Size of the AU Header
int au_headers_length_bits = (((rtp_payload[ptr] << 8) + (rtp_payload[ptr + 1] << 0))); // 16 bits
int au_headers_length = (int)Math.Ceiling((double)au_headers_length_bits / 8.0);
ptr += 2;
// Examine the AU Header. Get the size of the AAC data
int aac_frame_size = (((rtp_payload[ptr] << 8) + (rtp_payload[ptr+1] << 0)) >> 3); // 13 bits
int aac_index_delta = rtp_payload[ptr+1] & 0x03; // 3 bits
ptr += au_headers_length;
// extract the AAC block
if (ptr + aac_frame_size > rtp_payload.Length) break; // not enough data to copy
byte[] aac_data = new byte[aac_frame_size];
System.Array.Copy(rtp_payload, ptr, aac_data, 0, aac_frame_size);
audio_data.Add(aac_data);
ptr += aac_frame_size;
}
return audio_data;
}
}
}

View File

@@ -0,0 +1,34 @@
using System.Collections.Generic;
namespace Inspectron.HawkEye.RTSP
{
// This class handles the AMR Payload
// It has methods to process the RTP Payload
public class AMRPayload
{
// Constructor
public AMRPayload()
{
}
public List<byte[]> Process_AMR_RTP_Packet(byte[] rtp_payload, int rtp_marker) {
// Octet-Aligned Mode (RFC 4867 Section 4.4.1)
// First byte is the Payload Header
if (rtp_payload.Length < 1) return null;
byte payloadHeader = rtp_payload[0];
// The rest of the RTP packet is the AMR data
List<byte[]> audio_data = new List<byte[]>();
byte[] amr_data = new byte[rtp_payload.Length - 1];
System.Array.Copy(rtp_payload,1,amr_data,0,rtp_payload.Length-1);
audio_data.Add(amr_data);
return audio_data;
}
}
}

View File

@@ -0,0 +1,183 @@
using System;
using System.Security.Cryptography;
using System.Text;
using Inspectron.HawkEye.RTSP.Messages;
namespace Inspectron.HawkEye.RTSP
{
// WWW-Authentication and Authorization Headers
public class Authentication
{
private static NLog.Logger _logger = NLog.LogManager.GetCurrentClassLogger();
public enum Type {Basic, Digest};
private String username = null;
private String password = null;
private String realm = null;
private String nonce = null;
private Type authentication_type = Type.Digest;
private readonly MD5 md5 = System.Security.Cryptography.MD5.Create();
private const char quote = '\"';
// Constructor
public Authentication(String username, String password, String realm, Type authentication_type) {
this.username = username;
this.password = password;
this.realm = realm;
this.authentication_type = authentication_type;
this.nonce = new Random().Next(100000000,999999999).ToString(); // random 9 digit number
}
public String GetHeader() {
if (authentication_type == Type.Basic) {
return "Basic realm=" + quote + realm + quote;
}
if (authentication_type == Type.Digest) {
return "Digest realm=" + quote + realm + quote + ", nonce=" + quote + nonce + quote;
}
return null;
}
public bool IsValid(RtspMessage received_message) {
string authorization = received_message.Headers["Authorization"];
// Check Username and Password
if (authentication_type == Type.Basic && authorization.StartsWith("Basic ")) {
string base64_str = authorization.Substring(6); // remove 'Basic '
byte[] data = Convert.FromBase64String(base64_str);
string decoded = Encoding.UTF8.GetString(data);
int split_position = decoded.IndexOf(':');
string decoded_username = decoded.Substring(0, split_position);
string decoded_password = decoded.Substring(split_position + 1);
if ((decoded_username == username) && (decoded_password == password)) {
_logger.Debug("Basic Authorization passed");
return true;
} else {
_logger.Debug("Basic Authorization failed");
return false;
}
}
// Check Username, URI, Nonce and the MD5 hashed Response
if (authentication_type == Type.Digest && authorization.StartsWith("Digest ")) {
string value_str = authorization.Substring(7); // remove 'Digest '
string[] values = value_str.Split(',');
string auth_header_username = null;
string auth_header_realm = null;
string auth_header_nonce = null;
string auth_header_uri = null;
string auth_header_response = null;
string message_method = null;
string message_uri = null;
try {
message_method = received_message.Command.Split(' ')[0];
message_uri = received_message.Command.Split(' ')[1];
} catch {}
foreach (string value in values) {
string[] tuple = value.Trim().Split(new char[] {'='},2); // split on first '='
if (tuple.Length == 2 && tuple[0].Equals("username")) {
auth_header_username = tuple[1].Trim(new char[] {' ','\"'}); // trim space and quotes
}
else if (tuple.Length == 2 && tuple[0].Equals("realm")) {
auth_header_realm = tuple[1].Trim(new char[] {' ','\"'}); // trim space and quotes
}
else if (tuple.Length == 2 && tuple[0].Equals("nonce")) {
auth_header_nonce = tuple[1].Trim(new char[] {' ','\"'}); // trim space and quotes
}
else if (tuple.Length == 2 && tuple[0].Equals("uri")) {
auth_header_uri = tuple[1].Trim(new char[] {' ','\"'}); // trim space and quotes
}
else if (tuple.Length == 2 && tuple[0].Equals("response")) {
auth_header_response = tuple[1].Trim(new char[] {' ','\"'}); // trim space and quotes
}
}
// Create the MD5 Hash using all parameters passed in the Auth Header with the
// addition of the 'Password'
String hashA1 = CalculateMD5Hash(md5, auth_header_username+":"+auth_header_realm+":"+this.password);
String hashA2 = CalculateMD5Hash(md5, message_method + ":" + auth_header_uri);
String expected_response = CalculateMD5Hash(md5, hashA1 + ":" + auth_header_nonce + ":" + hashA2);
// Check if everything matches
// ToDo - extract paths from the URIs (ignoring SETUP's trackID)
if ((auth_header_username == this.username)
&& (auth_header_realm == this.realm)
&& (auth_header_nonce == this.nonce)
&& (auth_header_response == expected_response)
){
_logger.Debug("Digest Authorization passed");
return true;
} else {
_logger.Debug("Digest Authorization failed");
return false;
}
}
return false;
}
// Generate Basic or Digest Authorization
public string GenerateAuthorization(string username, string password,
string auth_type, string realm, string nonce, string url, string command) {
if (username == null || username.Length == 0) return null;
if (password == null || password.Length == 0) return null;
if (realm == null || realm.Length == 0) return null;
if (auth_type.Equals("Digest") && (nonce == null || nonce.Length == 0)) return null;
if (auth_type.Equals("Basic")) {
byte[] credentials = System.Text.Encoding.UTF8.GetBytes(username+":"+password);
String credentials_base64 = Convert.ToBase64String(credentials);
String basic_authorization = "Basic " + credentials_base64;
return basic_authorization;
}
else if (auth_type.Equals("Digest")) {
MD5 md5 = System.Security.Cryptography.MD5.Create();
String hashA1 = CalculateMD5Hash(md5, username+":"+realm+":"+password);
String hashA2 = CalculateMD5Hash(md5, command + ":" + url);
String response = CalculateMD5Hash(md5, hashA1 + ":" + nonce + ":" + hashA2);
const String quote = "\"";
String digest_authorization = "Digest username=" + quote + username + quote +", "
+ "realm=" + quote + realm + quote + ", "
+ "nonce=" + quote + nonce + quote + ", "
+ "uri=" + quote + url + quote + ", "
+ "response=" + quote + response + quote;
return digest_authorization;
}
else {
return null;
}
}
// MD5 (lower case)
private string CalculateMD5Hash(MD5 md5_session, string input)
{
byte[] inputBytes = System.Text.Encoding.UTF8.GetBytes(input);
byte[] hash = md5_session.ComputeHash(inputBytes);
StringBuilder output = new StringBuilder();
for (int i = 0; i < hash.Length; i++) {
output.Append(hash[i].ToString("x2"));
}
return output.ToString();
}
}
}

View File

@@ -0,0 +1,88 @@
using System;
using System.Collections.Generic;
// (c) 2018 Roger Hardiman, RJH Technical Consultancy Ltd
// Simple class to Read and Write bits in a bit stream.
// Data is written to the end of the bit stream and the bit stream can be returned as a Byte Array
// Data can be read from the head of the bit stream
// Example
// bitstream.AddValue(0xA,4); // Write 4 bit value
// bitstream.AddValue(0xB,4);
// bitstream.AddValue(0xC,4);
// bitstream.AddValue(0xD,4);
// bitstream.ToArray() -> {0xAB, 0xCD} // Return Byte Array
// bitstream.Read(8) -> 0xAB // Read 8 bit value
namespace Inspectron.HawkEye.RTSP
{
// Very simple bitstream
public class BitStream {
private List <byte> data = new List<byte>(); // List only stores 0 or 1 (one 'bit' per List item)
// Constructor
public BitStream() {
}
public void AddValue(int value, int num_bits) {
// Add each bit to the List
for (int i = num_bits-1; i >= 0; i--) {
data.Add((byte)((value>>i) & 0x01));
}
}
public void AddHexString(String hex_string) {
char[] hex_chars = hex_string.ToUpper().ToCharArray();
foreach (char c in hex_chars) {
if ((c.Equals('0'))) this.AddValue(0,4);
else if ((c.Equals('1'))) this.AddValue(1, 4);
else if ((c.Equals('2'))) this.AddValue(2, 4);
else if ((c.Equals('3'))) this.AddValue(3, 4);
else if ((c.Equals('4'))) this.AddValue(4, 4);
else if ((c.Equals('5'))) this.AddValue(5, 4);
else if ((c.Equals('6'))) this.AddValue(6, 4);
else if ((c.Equals('7'))) this.AddValue(7, 4);
else if ((c.Equals('8'))) this.AddValue(8, 4);
else if ((c.Equals('9'))) this.AddValue(9, 4);
else if ((c.Equals('A'))) this.AddValue(10, 4);
else if ((c.Equals('B'))) this.AddValue(11, 4);
else if ((c.Equals('C'))) this.AddValue(12, 4);
else if ((c.Equals('D'))) this.AddValue(13, 4);
else if ((c.Equals('E'))) this.AddValue(14, 4);
else if ((c.Equals('F'))) this.AddValue(15, 4);
}
}
public uint Read(int num_bits) {
// Read and remove items from the front of the list of bits
if (data.Count < num_bits) return 0;
uint result = 0;
for (int i = 0; i < num_bits; i++) {
result = result << 1;
result = result + data[0];
data.RemoveAt(0);
}
return result;
}
public byte[] ToArray() {
int num_bytes = (int)Math.Ceiling((double)data.Count/8.0);
byte[] array = new byte[num_bytes];
int ptr = 0;
int shift = 7;
for (int i = 0; i < data.Count; i++) {
array[ptr] += (byte)(data[i] << shift);
if (shift == 0) {
shift = 7;
ptr++;
}
else {
shift--;
}
}
return array;
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,64 @@
using System.Collections.Generic;
namespace Inspectron.HawkEye.RTSP
{
// This class handles the G711 Payload
// It has methods to process the RTP Payload
public class G711Payload
{
// Constructor
public G711Payload()
{
}
public List<byte[]> Process_G711_RTP_Packet(byte[] rtp_payload, int rtp_marker) {
List<byte[]> audio_data = new List<byte[]>();
audio_data.Add(rtp_payload);
return audio_data;
}
/* Untested - used with G711.1 and PCMA-WB and PCMU-WB Codec Names */
public List<byte[]> Process_G711_1_RTP_Packet(byte[] rtp_payload, int rtp_marker) {
// Look at the Header. This tells us the G711 mode being used
// Mode Index (MI) is
// 1 - R1 40 octets containg Layer 0 data
// 2 - R2a 50 octets containing Layer 0 plus Layer 1 data
// 3 - R2b 50 octets containing Layer 0 plus Layer 2 data
// 4 - R3 60 octets containing Layer 0 plus Layer 1 plus Layer 2 data
byte mode_index = (byte)(rtp_payload[0] & 0x07);
int size_of_one_frame = 0; // will be in bytes
switch (mode_index) {
case 1: size_of_one_frame = 40; break;
case 2: size_of_one_frame = 50; break;
case 3: size_of_one_frame = 50; break;
case 4: size_of_one_frame = 60; break;
default: return null; // invalid Mode Index
}
int number_frames = (rtp_payload.Length - 1) / size_of_one_frame;
// Return just the basic u-Law or A-Law audio (the Layer 0 audio)
List<byte[]> audio_data = new List<byte[]>();
// Extract each audio frame and place in the audio_data List
int frame_start = 1; // starts just after the MI header
while (frame_start + size_of_one_frame < rtp_payload.Length) {
byte[] layer_0_audio = new byte[40];
System.Array.Copy(rtp_payload,frame_start,layer_0_audio,0,40); // 40 octets in Layer 0 data
audio_data.Add(layer_0_audio);
frame_start += size_of_one_frame;
}
return audio_data;
}
}
}

View File

@@ -0,0 +1,184 @@
using System.Collections.Generic;
using System.IO;
namespace Inspectron.HawkEye.RTSP
{
// This class handles the H264 Payload
// It has methods to parse parameters in the SDP
// It has methods to process the RTP Payload
public class H264Payload
{
private static NLog.Logger _logger = NLog.LogManager.GetCurrentClassLogger();
int norm, fu_a, fu_b, stap_a, stap_b, mtap16, mtap24 = 0; // used for diagnostics stats
List<byte[]> temporary_rtp_payloads = new List<byte[]>(); // used to assemble the RTP packets that form one RTP Frame
// Eg all the RTP Packets from M=0 through to M=1
MemoryStream fragmented_nal = new MemoryStream(); // used to concatenate fragmented H264 NALs where NALs are split over RTP packets
// Constructor
public H264Payload()
{
}
public List<byte[]> Process_H264_RTP_Packet(byte[] rtp_payload, int rtp_marker) {
// Add to the list of payloads for the current Frame of video
temporary_rtp_payloads.Add(rtp_payload); // Todo Could optimise this and go direct to Process Frame if just 1 packet in frame
if (rtp_marker == 1)
{
// End Marker is set. Process the list of RTP Packets (forming 1 RTP frame) and save the NALs to a file
List<byte[]> nal_units = Process_H264_RTP_Frame(temporary_rtp_payloads);
temporary_rtp_payloads.Clear();
return nal_units;
}
return null; // we don't have a frame yet. Keep accumulating RTP packets
}
// Process a RTP Frame. A RTP Frame can consist of several RTP Packets which have the same Timestamp
// Returns a list of NAL Units (with no 00 00 00 01 header and with no Size header)
private List<byte[]> Process_H264_RTP_Frame(List<byte[]> rtp_payloads)
{
_logger.Debug("RTP Data comprised of " + rtp_payloads.Count + " rtp packets");
List<byte[]> nal_units = new List<byte[]>(); // Stores the NAL units for a Video Frame. May be more than one NAL unit in a video frame.
for (int payload_index = 0; payload_index < rtp_payloads.Count; payload_index++)
{
// Examine the first rtp_payload and the first byte (the NAL header)
int nal_header_f_bit = (rtp_payloads[payload_index][0] >> 7) & 0x01;
int nal_header_nri = (rtp_payloads[payload_index][0] >> 5) & 0x03;
int nal_header_type = (rtp_payloads[payload_index][0] >> 0) & 0x1F;
// If the Nal Header Type is in the range 1..23 this is a normal NAL (not fragmented)
// So write the NAL to the file
if (nal_header_type >= 1 && nal_header_type <= 23)
{
_logger.Debug("Normal NAL");
norm++;
nal_units.Add(rtp_payloads[payload_index]);
}
// There are 4 types of Aggregation Packet (split over RTP payloads)
else if (nal_header_type == 24)
{
_logger.Debug("Agg STAP-A");
stap_a++;
// RTP packet contains multiple NALs, each with a 16 bit header
// Read 16 byte size
// Read NAL
try
{
int ptr = 1; // start after the nal_header_type which was '24'
// if we have at least 2 more bytes (the 16 bit size) then consume more data
while (ptr + 2 < (rtp_payloads[payload_index].Length - 1))
{
int size = (rtp_payloads[payload_index][ptr] << 8) + (rtp_payloads[payload_index][ptr + 1] << 0);
ptr = ptr + 2;
byte[] nal = new byte[size];
System.Array.Copy(rtp_payloads[payload_index], ptr, nal, 0, size); // copy the NAL
nal_units.Add(nal); // Add to list of NALs for this RTP frame. Start Codes like 00 00 00 01 get added later
ptr = ptr + size;
}
}
catch
{
_logger.Debug("H264 Aggregate Packet processing error");
}
}
else if (nal_header_type == 25)
{
_logger.Debug("Agg STAP-B not supported");
stap_b++;
}
else if (nal_header_type == 26)
{
_logger.Debug("Agg MTAP16 not supported");
mtap16++;
}
else if (nal_header_type == 27)
{
_logger.Debug("Agg MTAP24 not supported");
mtap24++;
}
else if (nal_header_type == 28)
{
_logger.Debug("Frag FU-A");
fu_a++;
// Parse Fragmentation Unit Header
int fu_header_s = (rtp_payloads[payload_index][1] >> 7) & 0x01; // start marker
int fu_header_e = (rtp_payloads[payload_index][1] >> 6) & 0x01; // end marker
int fu_header_r = (rtp_payloads[payload_index][1] >> 5) & 0x01; // reserved. should be 0
int fu_header_type = (rtp_payloads[payload_index][1] >> 0) & 0x1F; // Original NAL unit header
_logger.Debug("Frag FU-A s=" + fu_header_s + "e=" + fu_header_e);
// Check Start and End flags
if (fu_header_s == 1 && fu_header_e == 0)
{
// Start of Fragment.
// Initiise the fragmented_nal byte array
// Build the NAL header with the original F and NRI flags but use the the Type field from the fu_header_type
byte reconstructed_nal_type = (byte)((nal_header_f_bit << 7) + (nal_header_nri << 5) + fu_header_type);
// Empty the stream
fragmented_nal.SetLength(0);
// Add reconstructed_nal_type byte to the memory stream
fragmented_nal.WriteByte(reconstructed_nal_type);
// copy the rest of the RTP payload to the memory stream
fragmented_nal.Write(rtp_payloads[payload_index], 2, rtp_payloads[payload_index].Length - 2);
}
if (fu_header_s == 0 && fu_header_e == 0)
{
// Middle part of Fragment
// Append this payload to the fragmented_nal
// Data starts after the NAL Unit Type byte and the FU Header byte
fragmented_nal.Write(rtp_payloads[payload_index], 2, rtp_payloads[payload_index].Length - 2);
}
if (fu_header_s == 0 && fu_header_e == 1)
{
// End part of Fragment
// Append this payload to the fragmented_nal
// Data starts after the NAL Unit Type byte and the FU Header byte
fragmented_nal.Write(rtp_payloads[payload_index], 2, rtp_payloads[payload_index].Length - 2);
// Add the NAL to the array of NAL units
nal_units.Add(fragmented_nal.ToArray());
}
}
else if (nal_header_type == 29)
{
_logger.Debug("Frag FU-B not supported");
fu_b++;
}
else
{
_logger.Debug("Unknown NAL header " + nal_header_type + " not supported");
}
}
// Output some statistics
_logger.Debug("Norm=" + norm + " ST-A=" + stap_a + " ST-B=" + stap_b + " M16=" + mtap16 + " M24=" + mtap24 + " FU-A=" + fu_a + " FU-B=" + fu_b);
// Output all the NALs that form one RTP Frame (one frame of video)
return nal_units;
}
}
}

View File

@@ -0,0 +1,224 @@
using System;
using System.Collections.Generic;
using System.IO;
namespace Inspectron.HawkEye.RTSP
{
// This class handles the H265 Payload
// It has methods to parse parameters in the SDP
// It has methods to process the RTP Payload
// By Roger Hardiman, RJH Technical Consultancy Ltd
public class H265Payload
{
// H265 / HEVC structure.
// An 'Access Unit' is the set of NAL Units that form one Picture
// NAL Units have a 2 byte header comprising of
// F Bit, Type, Layer ID and TID
int single, agg, frag = 0; // used for diagnostics stats
bool has_donl = false;
List<byte[]> temporary_rtp_payloads = new List<byte[]>(); // used to assemble the RTP packets that form one RTP Frame
// Eg all the RTP Packets from M=0 through to M=1
MemoryStream fragmented_nal = new MemoryStream(); // used to concatenate fragmented H264 NALs where NALs are split over RTP packets
// Constructor
public H265Payload(bool has_donl)
{
this.has_donl = has_donl;
}
public List<byte[]> Process_H265_RTP_Packet(byte[] rtp_payload, int rtp_marker) {
// Add payload to the List of payloads for the current Frame of Video
// ie all the payloads with M=0 up to the final payload where M=1
temporary_rtp_payloads.Add(rtp_payload); // Todo Could optimise this and go direct to Process Frame if just 1 packet in frame
if (rtp_marker == 1)
{
// End Marker is set. Process the list of RTP Packets (forming 1 RTP frame) and save the NALs to a file
List<byte[]> nal_units = Process_H265_RTP_Frame(temporary_rtp_payloads);
temporary_rtp_payloads.Clear();
return nal_units;
}
return null; // we don't have a frame yet. Keep accumulating RTP packets
}
// Process a RTP Frame. A RTP Frame can consist of several RTP Packets which have the same Timestamp
// Returns a list of NAL Units (with no 00 00 00 01 header and with no Size header)
private List<byte[]> Process_H265_RTP_Frame(List<byte[]> rtp_payloads)
{
Console.WriteLine("RTP Data comprised of " + rtp_payloads.Count + " rtp packets");
List<byte[]> nal_units = new List<byte[]>(); // Stores the NAL units for a Video Frame. May be more than one NAL unit in a video frame.
for (int payload_index = 0; payload_index < rtp_payloads.Count; payload_index++)
{
// Examine the first two bytes of the RTP data, the Payload Header
// F (Forbidden Bit),
// Type of NAL Unit (or VCL NAL Unit if Type is < 32),
// LayerId
// TID (TemporalID = TID - 1)
/*+---------------+---------------+
*|0|1|2|3|4|5|6|7|0|1|2|3|4|5|6|7|
*+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
*|F| Type | LayerId | TID |
*+-------------+-----------------+
*/
int payload_header = (rtp_payloads[payload_index][0] << 8) | (rtp_payloads[payload_index][1]);
int payload_header_f_bit = (payload_header >> 15) & 0x01;
int payload_header_type = (payload_header >> 9) & 0x3F;
int payload_header_layer_id = (payload_header >> 3) & 0x3F;
int payload_header_tid = payload_header & 0x7;
// There are three ways to Packetize NAL units into RTP Packets
// Single NAL Unit Packet
// Aggregation Packet (payload_header_type = 48)
// Fragmentation Unit (payload_header_type = 49)
// Single NAL Unit Packet
// 32=VPS
// 33=SPS
// 34=PPS
if (payload_header_type != 48 && payload_header_type != 49)
{
Console.WriteLine("Single NAL");
single++;
//TODO - Handle DONL
nal_units.Add(rtp_payloads[payload_index]);
}
// Aggregation Packet
else if (payload_header_type == 48)
{
Console.WriteLine("Aggregation Packet");
agg++;
// RTP packet contains multiple NALs, each with a 16 bit header
// Read 16 byte size
// Read NAL
// Use a Try/Catch to protect from bad RTP data where block sizes exceed the
// available data
try
{
int ptr = 2; // start after 16 bit Payload Header
// loop until the ptr has moved beyond the length of the data
while (ptr < (rtp_payloads[payload_index].Length - 1))
{
if (has_donl) ptr = ptr + 2; // step over the DONL data
int size = (rtp_payloads[payload_index][ptr] << 8) + (rtp_payloads[payload_index][ptr + 1] << 0);
ptr = ptr + 2;
byte[] nal = new byte[size];
System.Array.Copy(rtp_payloads[payload_index], ptr, nal, 0, size); // copy the NAL
nal_units.Add(nal); // Add to list of NALs for this RTP frame. Start Codes like 00 00 00 01 get added later
ptr = ptr + size;
}
}
catch
{
Console.WriteLine("H265 Aggregate Packet processing error");
}
}
// Fragmentation Unit
else if (payload_header_type == 49)
{
Console.WriteLine("Fragmentation Unit");
frag++;
// Parse Fragmentation Unit Header
int fu_header_s = (rtp_payloads[payload_index][2] >> 7) & 0x01; // start marker
int fu_header_e = (rtp_payloads[payload_index][2] >> 6) & 0x01; // end marker
int fu_header_type = (rtp_payloads[payload_index][2] >> 0) & 0x3F; // fu type
Console.WriteLine("Frag FU-A s=" + fu_header_s + "e=" + fu_header_e);
// Check Start and End flags
if (fu_header_s == 1 && fu_header_e == 0)
{
// Start of Fragment.
// Initiise the fragmented_nal byte array
// Empty the stream
fragmented_nal.SetLength(0);
// Reconstrut the NAL header from the rtp_payload_header, replacing the Type with FU Type
int nal_header = (payload_header & 0x81FF); // strip out existing 'type'
nal_header = nal_header | (fu_header_type << 9);
fragmented_nal.WriteByte((byte)((nal_header >> 8) & 0xFF));
fragmented_nal.WriteByte((byte)((nal_header >> 0) & 0xFF));
if (has_donl)
{
// start copying after the DONL data
fragmented_nal.Write(rtp_payloads[payload_index], 5, rtp_payloads[payload_index].Length - 5);
}
else
{
// there is no DONL data
fragmented_nal.Write(rtp_payloads[payload_index], 3, rtp_payloads[payload_index].Length - 3);
}
}
if (fu_header_s == 0 && fu_header_e == 0)
{
// Middle part of Fragment
// Append this payload to the fragmented_nal
if (has_donl) {
// start copying after the DONL data
fragmented_nal.Write(rtp_payloads[payload_index], 5, rtp_payloads[payload_index].Length - 5);
} else {
// there is no DONL data
fragmented_nal.Write(rtp_payloads[payload_index], 3, rtp_payloads[payload_index].Length - 3);
}
}
if (fu_header_s == 0 && fu_header_e == 1)
{
// End part of Fragment
// Append this payload to the fragmented_nal
if (has_donl)
{
// start copying after the DONL data
fragmented_nal.Write(rtp_payloads[payload_index], 5, rtp_payloads[payload_index].Length - 5);
}
else
{
// there is no DONL data
fragmented_nal.Write(rtp_payloads[payload_index], 3, rtp_payloads[payload_index].Length - 3);
}
// Add the NAL to the array of NAL units
nal_units.Add(fragmented_nal.ToArray());
}
}
else {
Console.WriteLine("Unknown Payload Header Type = " + payload_header_type);
}
}
// Output some statistics
Console.WriteLine("Single=" + single + " Agg=" + agg + " Frag=" + frag);
// Output all the NALs that form one RTP Frame (one frame of video)
return nal_units;
}
}
}

View File

@@ -0,0 +1,41 @@
namespace Inspectron.HawkEye.RTSP
{
/// <summary>
/// Interface for Transport of Rtsp (TCP, TCP+SSL,..)
/// </summary>
public interface IRtspTransport
{
/// <summary>
/// Gets the stream of the transport.
/// </summary>
/// <returns>A stream</returns>
System.IO.Stream GetStream();
/// <summary>
/// Gets the remote address.
/// </summary>
/// <value>The remote address.</value>
string RemoteAddress
{
get;
}
/// <summary>
/// Closes this instance.
/// </summary>
void Close();
/// <summary>
/// Gets a value indicating whether this <see cref="IRtspTransport"/> is connected.
/// </summary>
/// <value><c>true</c> if connected; otherwise, <c>false</c>.</value>
bool Connected { get; }
/// <summary>
/// Reconnect this instance.
/// <remarks>Must do nothing if already connected.</remarks>
/// </summary>
/// <exception cref="System.Net.Sockets.SocketException">Error during socket </exception>
void Reconnect();
}
}

View File

@@ -0,0 +1,103 @@
using System;
using System.Diagnostics.Contracts;
using System.Globalization;
namespace Inspectron.HawkEye.RTSP.Messages
{
/// <summary>
/// Describe a couple of port used to transfer video and command.
/// </summary>
public class PortCouple
{
/// <summary>
/// Gets or sets the first port number.
/// </summary>
/// <value>The first port.</value>
public int First { get; set; }
/// <summary>
/// Gets or sets the second port number.
/// </summary>
/// <remarks>If not present the value is 0</remarks>
/// <value>The second port.</value>
public int Second { get; set; }
/// <summary>
/// Initializes a new instance of the <see cref="PortCouple"/> class.
/// </summary>
public PortCouple()
{ }
/// <summary>
/// Initializes a new instance of the <see cref="PortCouple"/> class.
/// </summary>
/// <param name="first">The first port.</param>
public PortCouple(int first)
{
First = first;
Second = 0;
}
/// <summary>
/// Initializes a new instance of the <see cref="PortCouple"/> class.
/// </summary>
/// <param name="first">The first port.</param>
/// <param name="second">The second port.</param>
public PortCouple(int first, int second)
{
First = first;
Second = second;
}
/// <summary>
/// Gets a value indicating whether this instance has second port.
/// </summary>
/// <value>
/// <c>true</c> if this instance has second port; otherwise, <c>false</c>.
/// </value>
public bool IsSecondPortPresent
{
get { return Second != 0; }
}
/// <summary>
/// Parses the int values of port.
/// </summary>
/// <param name="stringValue">A string value.</param>
/// <returns>The port couple</returns>
public static PortCouple Parse(string stringValue)
{
if (stringValue == null)
throw new ArgumentNullException("stringValue");
Contract.Requires(!string.IsNullOrEmpty(stringValue));
string[] values = stringValue.Split('-');
int tempValue;
int.TryParse(values[0], out tempValue);
PortCouple result = new PortCouple(tempValue);
tempValue = 0;
if (values.Length > 1)
int.TryParse(values[1], out tempValue);
result.Second = tempValue;
return result;
}
/// <summary>
/// Returns a <see cref="System.String"/> that represents this instance.
/// </summary>
/// <returns>
/// A <see cref="System.String"/> that represents this instance.
/// </returns>
public override string ToString()
{
if (IsSecondPortPresent)
return First.ToString(CultureInfo.InvariantCulture) + "-" + Second.ToString(CultureInfo.InvariantCulture);
else
return First.ToString(CultureInfo.InvariantCulture);
}
}
}

View File

@@ -0,0 +1,49 @@
using System;
namespace Inspectron.HawkEye.RTSP.Messages
{
/// <summary>
/// Class wich represent each message echanged on Rtsp socket.
/// </summary>
public abstract class RtspChunk : ICloneable
{
/// <summary>
/// Logs the message to debug.
/// </summary>
public void LogMessage()
{
LogMessage(NLog.LogLevel.Debug);
}
/// <summary>
/// Logs the message.
/// </summary>
/// <param name="alevel">The log level.</param>
public abstract void LogMessage(NLog.LogLevel aLevel);
/// <summary>
/// Gets or sets the data associate with the message.
/// </summary>
/// <value>Array of byte transmit with the message.</value>
public byte[] Data
{ get; set; }
/// <summary>
/// Gets or sets the source port wich receive the message.
/// </summary>
/// <value>The source port.</value>
public RtspListener SourcePort { get; set; }
#region ICloneable Membres
/// <summary>
/// Crée un nouvel objet qui est une copie de l'instance en cours.
/// </summary>
/// <returns>
/// Nouvel objet qui est une copie de cette instance.
/// </returns>
public abstract object Clone();
#endregion
}
}

View File

@@ -0,0 +1,45 @@
namespace Inspectron.HawkEye.RTSP.Messages
{
/// <summary>
/// Message wich represent data. ($ limited message)
/// </summary>
public class RtspData : RtspChunk
{
private static NLog.Logger _logger = NLog.LogManager.GetCurrentClassLogger();
/// <summary>
/// Logs the message to debug.
/// </summary>
public override void LogMessage(NLog.LogLevel aLevel)
{
// Default value to debug
if (aLevel == null)
aLevel = NLog.LogLevel.Debug;
// if the level is not logged directly return
if (!_logger.IsEnabled(aLevel))
return;
_logger.Log(aLevel, "Data message");
if (Data == null)
_logger.Log(aLevel, "Data : null");
else
_logger.Log(aLevel, "Data length :-{0}-", Data.Length);
}
public int Channel { get; set; }
/// <summary>
/// Clones this instance.
/// <remarks>Listner is not cloned</remarks>
/// </summary>
/// <returns>a clone of this instance</returns>
public override object Clone()
{
RtspData result = new RtspData();
result.Channel = this.Channel;
if (this.Data != null)
result.Data = this.Data.Clone() as byte[];
result.SourcePort = this.SourcePort;
return result;
}
}
}

View File

@@ -0,0 +1,19 @@
namespace Inspectron.HawkEye.RTSP.Messages
{
/// <summary>
/// Class containing helper constant for general use headers.
/// </summary>
public static class RtspHeaderNames
{
public const string ContentBase = "Content-Base";
public const string ContentEncoding = "Content-Encoding";
public const string ContentType = "Content-Type";
public const string Public = "Public";
public const string Session = "Session";
public const string Transport = "Transport";
public const string WWWAuthenticate = "WWW-Authenticate";
public const string Authorization = "Authorization";
}
}

View File

@@ -0,0 +1,309 @@
using System;
using System.Collections.Generic;
using System.Diagnostics.Contracts;
using System.Globalization;
using System.IO;
using System.Text;
using System.Text.RegularExpressions;
namespace Inspectron.HawkEye.RTSP.Messages
{
public class RtspMessage : RtspChunk
{
private static NLog.Logger _logger = NLog.LogManager.GetCurrentClassLogger();
/// <summary>
/// The regex to validate the Rtsp message.
/// </summary>
private static readonly Regex _rtspVersionTest = new Regex(@"^RTSP/\d\.\d", RegexOptions.Compiled);
/// <summary>
/// Create the good type of Rtsp Message from the header.
/// </summary>
/// <param name="aRequestLine">A request line.</param>
/// <returns>An Rtsp message</returns>
public static RtspMessage GetRtspMessage(string aRequestLine)
{
// We can't determine the message
if (string.IsNullOrEmpty(aRequestLine))
return new RtspMessage();
string[] requestParts = aRequestLine.Split(new char[] { ' ' }, 3);
RtspMessage returnValue;
if (requestParts.Length == 3)
{
// A request is : Method SP Request-URI SP RTSP-Version
// A response is : RTSP-Version SP Status-Code SP Reason-Phrase
// RTSP-Version = "RTSP" "/" 1*DIGIT "." 1*DIGIT
if (_rtspVersionTest.IsMatch(requestParts[2]))
returnValue = RtspRequest.GetRtspRequest(requestParts);
else if (_rtspVersionTest.IsMatch(requestParts[0]))
returnValue = new RtspResponse();
else
{
_logger.Warn(CultureInfo.InvariantCulture, "Got a strange message {0}", aRequestLine);
returnValue = new RtspMessage();
}
}
else
{
_logger.Warn(CultureInfo.InvariantCulture, "Got a strange message {0}", aRequestLine);
returnValue = new RtspMessage();
}
returnValue.Command = aRequestLine;
return returnValue;
}
/// <summary>
/// Initializes a new instance of the <see cref="RtspMessage"/> class.
/// </summary>
public RtspMessage()
{
Data = new byte[0];
Creation = DateTime.Now;
}
private Dictionary<string, string> _headers = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
internal protected string[] commandArray;
/// <summary>
/// Gets or sets the creation time.
/// </summary>
/// <value>The creation time.</value>
public DateTime Creation { get; private set; }
/// <summary>
/// Gets or sets the command of the message (first line).
/// </summary>
/// <value>The command.</value>
public string Command
{
get
{
if (commandArray == null)
return string.Empty;
return string.Join(" ", commandArray);
}
set
{
if (value == null)
commandArray = new string[] { String.Empty };
else
commandArray = value.Split(new char[] {' '}, 3);
}
}
/// <summary>
/// Gets the Method of the message (eg OPTIONS, DESCRIBE, SETUP, PLAY).
/// </summary>
/// <value>The Method</value>
public string Method
{
get
{
if (commandArray == null)
return string.Empty;
return commandArray[0];
}
}
/// <summary>
/// Gets the headers of the message.
/// </summary>
/// <value>The headers.</value>
public Dictionary<string, string> Headers
{
get
{
return _headers;
}
}
/// <summary>
/// Adds one header from a string.
/// </summary>
/// <param name="line">The string containing header of format Header: Value.</param>
/// <exception cref="ArgumentNullException"><paramref name="line"/> is null</exception>
public void AddHeader(string line)
{
if (line == (string)null)
throw new ArgumentNullException("line");
//spliter
string[] elements = line.Split(new char[] { ':' }, 2);
if (elements.Length == 2)
{
_headers[elements[0].Trim()] = elements[1].TrimStart();
}
else
{
_logger.Warn(CultureInfo.InvariantCulture, "Invalid Header received : -{0}-", line);
}
}
/// <summary>
/// Gets or sets the Ccommande Seqquence number.
/// <remarks>If the header is not define or not a valid number it return 0</remarks>
/// </summary>
/// <value>The sequence number.</value>
public int CSeq
{
get
{
string returnStringValue;
int returnValue;
if (!(_headers.TryGetValue("CSeq", out returnStringValue) &&
int.TryParse(returnStringValue, out returnValue)))
returnValue = 0;
return returnValue;
}
set
{
_headers["CSeq"] = value.ToString(CultureInfo.InvariantCulture);
}
}
/// <summary>
/// Gets the session ID.
/// </summary>
/// <value>The session ID.</value>
public virtual string Session
{
get
{
if (!_headers.ContainsKey("Session"))
return null;
return _headers["Session"];
}
set
{
_headers["Session"] = value;
}
}
/// <summary>
/// Initialises the length of the data byte array from content lenth header.
/// </summary>
public void InitialiseDataFromContentLength()
{
int dataLength;
if (!(_headers.ContainsKey("Content-Length")
&& int.TryParse(_headers["Content-Length"], out dataLength)))
{
dataLength = 0;
}
this.Data = new byte[dataLength];
}
/// <summary>
/// Adjusts the content length header.
/// </summary>
public void AdjustContentLength()
{
if (Data.Length > 0)
{
_headers["Content-Length"] = Data.Length.ToString(CultureInfo.InvariantCulture);
}
else
{
_headers.Remove("Content-Length");
}
}
/// <summary>
/// Sends to the message to a stream.
/// </summary>
/// <param name="stream">The stream.</param>
/// <exception cref="ArgumentNullException"><paramref name="stream"/> is empty</exception>
/// <exception cref="ArgumentException"><paramref name="stream"/> can't be written.</exception>
public void SendTo(Stream stream)
{
// <pex>
if (stream == null)
throw new ArgumentNullException("stream");
if (!stream.CanWrite)
throw
new ArgumentException("Stream CanWrite == false, can't send message to it", "stream");
// </pex>
Contract.EndContractBlock();
Encoding encoder = ASCIIEncoding.UTF8;
StringBuilder outputString = new StringBuilder();
AdjustContentLength();
// output header
outputString.Append(Command);
outputString.Append("\r\n");
foreach (KeyValuePair<string, string> item in _headers)
{
outputString.AppendFormat("{0}: {1}\r\n", item.Key, item.Value);
}
outputString.Append("\r\n");
byte[] buffer = encoder.GetBytes(outputString.ToString());
lock(stream) {
stream.Write(buffer, 0, buffer.Length);
// Output data
if (Data.Length > 0)
stream.Write(Data, 0, Data.Length);
}
stream.Flush();
}
/// <summary>
/// Logs the message.
/// </summary>
/// <param name="aLevel">A log level.</param>
public override void LogMessage(NLog.LogLevel aLevel)
{
// Default value to debug
if (aLevel == null)
aLevel = NLog.LogLevel.Debug;
// if the level is not logged directly return
if (!_logger.IsEnabled(aLevel))
return;
_logger.Log(aLevel, "Commande : {0}", Command);
foreach (KeyValuePair<string, string> item in _headers)
{
_logger.Log(aLevel, "Header : {0}: {1}", item.Key, item.Value);
}
if (Data.Length > 0)
{
_logger.Log(aLevel, "Data :-{0}-", ASCIIEncoding.ASCII.GetString(Data));
}
}
/// <summary>
/// Crée un nouvel objet qui est une copie de l'instance en cours.
/// </summary>
/// <returns>
/// Nouvel objet qui est une copie de cette instance.
/// </returns>
public override object Clone()
{
RtspMessage returnValue = GetRtspMessage(this.Command);
foreach (var item in this.Headers)
{
if (item.Value == null)
returnValue.Headers.Add(item.Key.Clone() as string, null);
else
returnValue.Headers.Add(item.Key.Clone() as string, item.Value.Clone() as string);
}
returnValue.Data = this.Data.Clone() as byte[];
returnValue.SourcePort = this.SourcePort;
return returnValue;
}
}
}

View File

@@ -0,0 +1,191 @@
using System;
using System.Diagnostics;
namespace Inspectron.HawkEye.RTSP.Messages
{
/// <summary>
/// An Rtsp Request
/// </summary>
public class RtspRequest : RtspMessage
{
/// <summary>
/// Request type.
/// </summary>
public enum RequestType
{
UNKNOWN,
DESCRIBE,
ANNOUNCE,
GET_PARAMETER,
OPTIONS,
PAUSE,
PLAY,
RECORD,
REDIRECT,
SETUP,
SET_PARAMETER,
TEARDOWN,
}
/// <summary>
/// Parses the request command.
/// </summary>
/// <param name="aStringRequest">A string request command.</param>
/// <returns>The typed request.</returns>
internal static RequestType ParseRequest(string aStringRequest)
{
RequestType returnValue;
if (!Enum.TryParse<RequestType>(aStringRequest, true, out returnValue))
returnValue = RequestType.UNKNOWN;
return returnValue;
}
/// <summary>
/// Gets the Rtsp request.
/// </summary>
/// <param name="aRequestParts">A request parts.</param>
/// <returns>the parsed request</returns>
internal static RtspMessage GetRtspRequest(string[] aRequestParts)
{
// <pex>
Debug.Assert(aRequestParts != (string[])null, "aRequestParts");
Debug.Assert(aRequestParts.Length != 0, "aRequestParts.Length == 0");
// </pex>
// we already know this is a Request
RtspRequest returnValue;
switch (ParseRequest(aRequestParts[0]))
{
case RequestType.OPTIONS:
returnValue = new RtspRequestOptions();
break;
case RequestType.DESCRIBE:
returnValue = new RtspRequestDescribe();
break;
case RequestType.SETUP:
returnValue = new RtspRequestSetup();
break;
case RequestType.PLAY:
returnValue = new RtspRequestPlay();
break;
case RequestType.PAUSE:
returnValue = new RtspRequestPause();
break;
case RequestType.TEARDOWN:
returnValue = new RtspRequestTeardown();
break;
case RequestType.GET_PARAMETER:
returnValue = new RtspRequestGetParameter();
break;
case RequestType.ANNOUNCE:
returnValue = new RtspRequestAnnounce();
break;
case RequestType.RECORD:
returnValue = new RtspRequestRecord();
break;
/*
case RequestType.REDIRECT:
break;
case RequestType.SET_PARAMETER:
break;
*/
case RequestType.UNKNOWN:
default:
returnValue = new RtspRequest();
break;
}
return returnValue;
}
/// <summary>
/// Initializes a new instance of the <see cref="RtspRequest"/> class.
/// </summary>
public RtspRequest()
{
Command = "OPTIONS * RTSP/1.0";
}
/// <summary>
/// Gets the request.
/// </summary>
/// <value>The request in string format.</value>
public string Request
{
get
{
return commandArray[0];
}
}
/// <summary>
/// Gets the request.
/// <remarks>The return value is typed with <see cref="Rtsp.RequestType"/> if the value is not
/// reconise the value is sent. The string value can be get by <see cref="Request"/></remarks>
/// </summary>
/// <value>The request.</value>
public RequestType RequestTyped
{
get
{
return ParseRequest(commandArray[0]);
}
set
{
if (Enum.IsDefined(typeof(RequestType), value))
commandArray[0] = value.ToString();
else
commandArray[0] = RequestType.UNKNOWN.ToString();
}
}
private Uri _RtspUri;
/// <summary>
/// Gets or sets the Rtsp asked URI.
/// </summary>
/// <value>The Rtsp asked URI.</value>
/// <remarks>The request with uri * is return with null URI</remarks>
public Uri RtspUri
{
get
{
if (commandArray.Length < 2 || commandArray[1]=="*")
return null;
if (_RtspUri == null)
Uri.TryCreate(commandArray[1], UriKind.Absolute, out _RtspUri);
return _RtspUri;
}
set
{
_RtspUri = value;
if (commandArray.Length < 2)
{
Array.Resize(ref commandArray, 3);
}
commandArray[1] = (value != null ? value.ToString().TrimEnd('/') : "*");
}
}
/// <summary>
/// Gets the assiociate OK response with the request.
/// </summary>
/// <returns>an Rtsp response correcponding to request.</returns>
public virtual RtspResponse CreateResponse()
{
RtspResponse returnValue = new RtspResponse();
returnValue.ReturnCode = 200;
returnValue.CSeq = this.CSeq;
if (this.Headers.ContainsKey(RtspHeaderNames.Session))
{
returnValue.Headers[RtspHeaderNames.Session] = this.Headers[RtspHeaderNames.Session];
}
return returnValue;
}
public Object ContextData { get; set; }
}
}

View File

@@ -0,0 +1,12 @@
namespace Inspectron.HawkEye.RTSP.Messages
{
public class RtspRequestAnnounce : RtspRequest
{
// constructor
public RtspRequestAnnounce()
{
Command = "ANNOUNCE * RTSP/1.0";
}
}
}

View File

@@ -0,0 +1,13 @@
namespace Inspectron.HawkEye.RTSP.Messages
{
public class RtspRequestDescribe : RtspRequest
{
// constructor
public RtspRequestDescribe()
{
Command = "DESCRIBE * RTSP/1.0";
}
}
}

View File

@@ -0,0 +1,12 @@
namespace Inspectron.HawkEye.RTSP.Messages
{
public class RtspRequestGetParameter : RtspRequest
{
// Constructor
public RtspRequestGetParameter()
{
Command = "GET_PARAMETER * RTSP/1.0";
}
}
}

View File

@@ -0,0 +1,28 @@
namespace Inspectron.HawkEye.RTSP.Messages
{
public class RtspRequestOptions : RtspRequest
{
// Constructor
public RtspRequestOptions()
{
Command = "OPTIONS * RTSP/1.0";
}
/// <summary>
/// Gets the assiociate OK response with the request.
/// </summary>
/// <returns>
/// an Rtsp response corresponding to request.
/// </returns>
public override RtspResponse CreateResponse()
{
RtspResponse response = base.CreateResponse();
// Add genric suported operations.
response.Headers.Add(RtspHeaderNames.Public, "OPTIONS,DESCRIBE,ANNOUNCE,SETUP,PLAY,PAUSE,TEARDOWN,GET_PARAMETER,SET_PARAMETER,REDIRECT");
return response;
}
}
}

View File

@@ -0,0 +1,12 @@
namespace Inspectron.HawkEye.RTSP.Messages
{
public class RtspRequestPause : RtspRequest
{
// Constructor
public RtspRequestPause()
{
Command = "PAUSE * RTSP/1.0";
}
}
}

View File

@@ -0,0 +1,12 @@
namespace Inspectron.HawkEye.RTSP.Messages
{
public class RtspRequestPlay : RtspRequest
{
// Constructor
public RtspRequestPlay()
{
Command = "PLAY * RTSP/1.0";
}
}
}

View File

@@ -0,0 +1,10 @@
namespace Inspectron.HawkEye.RTSP.Messages
{
public class RtspRequestRecord : RtspRequest
{
public RtspRequestRecord()
{
Command = "RECORD * RTSP/1.0";
}
}
}

View File

@@ -0,0 +1,43 @@
using System;
namespace Inspectron.HawkEye.RTSP.Messages
{
public class RtspRequestSetup : RtspRequest
{
// Constructor
public RtspRequestSetup()
{
Command = "SETUP * RTSP/1.0";
}
/// <summary>
/// Gets the transports associate with the request.
/// </summary>
/// <value>The transport.</value>
public RtspTransport[] GetTransports()
{
if (!Headers.ContainsKey(RtspHeaderNames.Transport))
return new RtspTransport[] { new RtspTransport() };
string[] items = Headers[RtspHeaderNames.Transport].Split(',');
return Array.ConvertAll<string, RtspTransport>(items,
new Converter<string, RtspTransport>(RtspTransport.Parse));
}
public void AddTransport(RtspTransport newTransport)
{
string actualTransport = string.Empty;
if(Headers.ContainsKey(RtspHeaderNames.Transport))
actualTransport = Headers[RtspHeaderNames.Transport] + ",";
Headers[RtspHeaderNames.Transport] = actualTransport + newTransport.ToString();
}
}
}

View File

@@ -0,0 +1,12 @@
namespace Inspectron.HawkEye.RTSP.Messages
{
public class RtspRequestTeardown : RtspRequest
{
// Constructor
public RtspRequestTeardown()
{
Command = "TEARDOWN * RTSP/1.0";
}
}
}

View File

@@ -0,0 +1,228 @@
using System;
using System.Globalization;
using System.Linq;
namespace Inspectron.HawkEye.RTSP.Messages
{
public class RtspResponse : RtspMessage
{
public const int DEFAULT_TIMEOUT = 60;
/// <summary>
/// Gets the default error message for an error code.
/// </summary>
/// <param name="aErrorCode">An error code.</param>
/// <returns>The default error message associate</returns>
private static string GetDefaultError(int aErrorCode)
{
switch (aErrorCode)
{
case 100: return "Continue";
case 200: return "OK";
case 201: return "Created";
case 250: return "Low on Storage Space";
case 300: return "Multiple Choices";
case 301: return "Moved Permanently";
case 302: return "Moved Temporarily";
case 303: return "See Other";
case 305: return "Use Proxy";
case 400: return "Bad Request";
case 401: return "Unauthorized";
case 402: return "Payment Required";
case 403: return "Forbidden";
case 404: return "Not Found";
case 405: return "Method Not Allowed";
case 406: return "Not Acceptable";
case 407: return "Proxy Authentication Required";
case 408: return "Request Timeout";
case 410: return "Gone";
case 411: return "Length Required";
case 412: return "Precondition Failed";
case 413: return "Request Entity Too Large";
case 414: return "Request-URI Too Long";
case 415: return "Unsupported Media Type";
case 451: return "Invalid parameter";
case 452: return "Illegal Conference Identifier";
case 453: return "Not Enough Bandwidth";
case 454: return "Session Not Found";
case 455: return "Method Not Valid In This State";
case 456: return "Header Field Not Valid";
case 457: return "Invalid Range";
case 458: return "Parameter Is Read-Only";
case 459: return "Aggregate Operation Not Allowed";
case 460: return "Only Aggregate Operation Allowed";
case 461: return "Unsupported Transport";
case 462: return "Destination Unreachable";
case 500: return "Internal Server Error";
case 501: return "Not Implemented";
case 502: return "Bad Gateway";
case 503: return "Service Unavailable";
case 504: return "Gateway Timeout";
case 505: return "RTSP Version Not Supported";
case 551: return "Option not support";
default:
return "Return: " + aErrorCode.ToString(CultureInfo.InvariantCulture);
}
}
/// <summary>
/// Initializes a new instance of the <see cref="RtspResponse"/> class.
/// </summary>
public RtspResponse()
: base()
{
// Initialise with a default result code.
Command = "RTSP/1.0 200 OK";
}
private int _returnCode;
/// <summary>
/// Gets or sets the return code of the response.
/// </summary>
/// <value>The return code.</value>
/// <remarks>On change the error message is set to the default one associate with the code</remarks>
public int ReturnCode
{
get
{
if (_returnCode == 0 && commandArray.Length >= 2)
{
int.TryParse(commandArray[1], out _returnCode);
}
return _returnCode;
}
set
{
if (ReturnCode != value)
{
_returnCode = value;
// make sure we have the room
if (commandArray.Length < 3)
{
Array.Resize(ref commandArray, 3);
}
commandArray[1] = value.ToString(CultureInfo.InvariantCulture);
commandArray[2] = GetDefaultError(value);
}
}
}
/// <summary>
/// Gets or sets the error/return message.
/// </summary>
/// <value>The return message.</value>
public string ReturnMessage
{
get
{
if (commandArray.Length < 3)
return String.Empty;
return commandArray[2];
}
set
{
// Make sure we have the room
if (commandArray.Length < 3)
{
Array.Resize(ref commandArray, 3);
}
commandArray[2] = value;
}
}
/// <summary>
/// Gets a value indicating whether this instance correspond to an OK response.
/// </summary>
/// <value><c>true</c> if this instance is OK; otherwise, <c>false</c>.</value>
public bool IsOk
{
get
{
if (ReturnCode > 0 && ReturnCode < 400)
return true;
return false;
}
}
/// <summary>
/// Gets the timeout in second.
/// <remarks>The default timeout is 60.</remarks>
/// </summary>
/// <value>The timeout.</value>
public int Timeout
{
get
{
int returnValue = DEFAULT_TIMEOUT;
if (Headers.ContainsKey(RtspHeaderNames.Session))
{
string[] parts = Headers[RtspHeaderNames.Session].Split(';');
if (parts.Length > 1)
{
string[] subParts = parts[1].Split('=');
if (subParts.Length > 1 &&
subParts[0].ToUpperInvariant() == "TIMEOUT")
if (!int.TryParse(subParts[1], out returnValue))
returnValue = DEFAULT_TIMEOUT;
}
}
return returnValue;
}
set
{
if(Headers.ContainsKey(RtspHeaderNames.Session))
if (value != DEFAULT_TIMEOUT)
{
Headers[RtspHeaderNames.Session] = Headers[RtspHeaderNames.Session].Split(';').First()
+ ";timeout=" + value.ToString(CultureInfo.InvariantCulture);
}
else
{
//remove timeout part
Headers[RtspHeaderNames.Session] = Headers[RtspHeaderNames.Session].Split(';').First();
}
}
}
/// <summary>
/// Gets the session ID.
/// </summary>
/// <value>The session ID.</value>
public override string Session
{
get
{
if (!Headers.ContainsKey(RtspHeaderNames.Session))
return null;
return Headers[RtspHeaderNames.Session].Split(';')[0];
}
set
{
if(Timeout != DEFAULT_TIMEOUT)
{
Headers[RtspHeaderNames.Session] = value + ";timeout=" + Timeout.ToString(CultureInfo.InvariantCulture);
}
else
{
Headers[RtspHeaderNames.Session] = value;
}
}
}
/// <summary>
/// Gets or sets the original request associate with the response.
/// </summary>
/// <value>The original request.</value>
public RtspRequest OriginalRequest
{ get; set; }
}
}

View File

@@ -0,0 +1,367 @@
using System;
using System.Diagnostics.Contracts;
using System.Text;
namespace Inspectron.HawkEye.RTSP.Messages
{
public class RtspTransport
{
public RtspTransport()
{
// Default value is true in RFC
IsMulticast = true;
LowerTransport = LowerTransportType.UDP;
Mode = "PLAY";
}
/*
RFC
Transport = "Transport" ":"
1\#transport-spec
transport-spec = transport-protocol/profile[/lower-transport]
*parameter
transport-protocol = "RTP"
profile = "AVP"
lower-transport = "TCP" | "UDP"
parameter = ( "unicast" | "multicast" )
| ";" "destination" [ "=" address ]
| ";" "interleaved" "=" channel [ "-" channel ]
| ";" "append"
| ";" "ttl" "=" ttl
| ";" "layers" "=" 1*DIGIT
| ";" "port" "=" port [ "-" port ]
| ";" "client_port" "=" port [ "-" port ]
| ";" "server_port" "=" port [ "-" port ]
| ";" "ssrc" "=" ssrc
| ";" "mode" = <"> 1\#mode <">
ttl = 1*3(DIGIT)
port = 1*5(DIGIT)
ssrc = 8*8(HEX)
channel = 1*3(DIGIT)
address = host
mode = <"> *Method <"> | Method
*/
/// <summary>
/// List of transport
/// </summary>
[Serializable]
public enum TransportType
{
/// <summary>
/// RTP for now
/// </summary>
RTP,
}
/// <summary>
/// Profile type
/// </summary>
[Serializable]
public enum ProfileType
{
/// <summary>
/// RTP/AVP of now
/// </summary>
AVP,
}
/// <summary>
/// Transport type.
/// </summary>
[Serializable]
public enum LowerTransportType
{
/// <summary>
/// UDP transport.
/// </summary>
UDP,
/// <summary>
/// TCP transport.
/// </summary>
TCP,
}
/// <summary>
/// Gets or sets the transport.
/// </summary>
/// <value>The transport.</value>
public TransportType Transport { get; set; }
/// <summary>
/// Gets or sets the profile.
/// </summary>
/// <value>The profile.</value>
public ProfileType Profile { get; set; }
/// <summary>
/// Gets or sets the lower transport.
/// </summary>
/// <value>The lower transport.</value>
public LowerTransportType LowerTransport { get; set; }
/// <summary>
/// Gets or sets a value indicating whether this instance is multicast.
/// </summary>
/// <value>
/// <c>true</c> if this instance is multicast; otherwise, <c>false</c>.
/// </value>
public bool IsMulticast { get; set; }
/// <summary>
/// Gets or sets the destination.
/// </summary>
/// <value>The destination.</value>
public string Destination { get; set; }
/// <summary>
/// Gets or sets the source.
/// </summary>
/// <value>The source.</value>
public string Source { get; set; }
/// <summary>
/// Gets or sets the interleaved.
/// </summary>
/// <value>The interleaved.</value>
public PortCouple Interleaved { get; set; }
/// <summary>
/// Gets or sets a value indicating whether this instance is append.
/// </summary>
/// <value><c>true</c> if this instance is append; otherwise, <c>false</c>.</value>
public bool IsAppend { get; set; }
/// <summary>
/// Gets or sets the TTL.
/// </summary>
/// <value>The TTL.</value>
public int TTL { get; set; }
/// <summary>
/// Gets or sets the layers.
/// </summary>
/// <value>The layers.</value>
public int Layers { get; set; }
/// <summary>
/// Gets or sets the port.
/// </summary>
/// <value>The port.</value>
public PortCouple Port { get; set; }
/// <summary>
/// Gets or sets the client port.
/// </summary>
/// <value>The client port.</value>
public PortCouple ClientPort { get; set; }
/// <summary>
/// Gets or sets the server port.
/// </summary>
/// <value>The server port.</value>
public PortCouple ServerPort { get; set; }
/// <summary>
/// Gets or sets the S SRC.
/// </summary>
/// <value>The S SRC.</value>
public string SSrc { get; set; }
/// <summary>
/// Gets or sets the mode.
/// </summary>
/// <value>The mode.</value>
public string Mode { get; set; }
/// <summary>
/// Parses the specified transport string.
/// </summary>
/// <param name="aTransportString">A transport string.</param>
/// <returns>The transport class.</returns>
/// <exception cref="ArgumentNullException"><paramref name="aTransportString"/> is null.</exception>
public static RtspTransport Parse(string aTransportString)
{
if (aTransportString == null)
throw new ArgumentNullException("aTransportString");
Contract.EndContractBlock();
RtspTransport returnValue = new RtspTransport();
string[] transportPart = aTransportString.Split(';');
string[] transportProtocolPart = transportPart[0].Split('/');
ReadTransport(returnValue, transportProtocolPart);
ReadProfile(returnValue, transportProtocolPart);
ReadLowerTransport(returnValue, transportProtocolPart);
foreach (string part in transportPart)
{
string[] subPart = part.Split('=');
switch (subPart[0].ToUpperInvariant())
{
case "UNICAST":
returnValue.IsMulticast = false;
break;
case "MULTICAST":
returnValue.IsMulticast = true;
break;
case "DESTINATION":
if (subPart.Length == 2)
returnValue.Destination = subPart[1];
break;
case "SOURCE":
if (subPart.Length == 2)
returnValue.Source = subPart[1];
break;
case "INTERLEAVED":
returnValue.IsMulticast = false;
if (subPart.Length < 2)
throw new ArgumentException("interleaved value invalid", "aTransportString");
returnValue.Interleaved = PortCouple.Parse(subPart[1]);
break;
case "APPEND":
returnValue.IsAppend = true;
break;
case "TTL":
int ttl = 0;
if (subPart.Length < 2 || !int.TryParse(subPart[1], out ttl))
throw new ArgumentException("TTL value invalid", "aTransportString");
returnValue.TTL = ttl;
break;
case "LAYERS":
int layers = 0;
if (subPart.Length < 2 || !int.TryParse(subPart[1], out layers))
throw new ArgumentException("Layers value invalid", "aTransportString");
returnValue.TTL = layers;
break;
case "PORT":
if (subPart.Length < 2)
throw new ArgumentException("Port value invalid", "aTransportString");
returnValue.Port = PortCouple.Parse(subPart[1]);
break;
case "CLIENT_PORT":
if (subPart.Length < 2)
throw new ArgumentException("client_port value invalid", "aTransportString");
returnValue.ClientPort = PortCouple.Parse(subPart[1]);
break;
case "SERVER_PORT":
if (subPart.Length < 2)
throw new ArgumentException("server_port value invalid", "aTransportString");
returnValue.ServerPort = PortCouple.Parse(subPart[1]);
break;
case "SSRC":
if (subPart.Length < 2)
throw new ArgumentException("ssrc value invalid", "aTransportString");
returnValue.SSrc = subPart[1];
break;
case "MODE":
if (subPart.Length < 2)
throw new ArgumentException("mode value invalid", "aTransportString");
returnValue.Mode = subPart[1];
break;
default:
// TODO log invalid part
break;
}
}
return returnValue;
}
private static void ReadLowerTransport(RtspTransport returnValue, string[] transportProtocolPart)
{
if (transportProtocolPart.Length == 3)
{
LowerTransportType lowerTransport;
if (!Enum.TryParse<LowerTransportType>(transportProtocolPart[2], out lowerTransport))
throw new ArgumentException("Lower transport type invalid", "aTransportString");
returnValue.LowerTransport = lowerTransport;
}
}
private static void ReadProfile(RtspTransport returnValue, string[] transportProtocolPart)
{
ProfileType profile;
if (transportProtocolPart.Length < 2 || !Enum.TryParse<ProfileType>(transportProtocolPart[1], out profile))
throw new ArgumentException("Transport profile type invalid", "aTransportString");
returnValue.Profile = profile;
}
private static void ReadTransport(RtspTransport returnValue, string[] transportProtocolPart)
{
TransportType transport;
if (!Enum.TryParse<TransportType>(transportProtocolPart[0], out transport))
throw new ArgumentException("Transport type invalid", "aTransportString");
returnValue.Transport = transport;
}
/// <summary>
/// Returns a <see cref="System.String"/> that represents this instance.
/// </summary>
/// <returns>
/// A <see cref="System.String"/> that represents this instance.
/// </returns>
public override string ToString()
{
StringBuilder transportString = new StringBuilder();
transportString.Append(Transport.ToString());
transportString.Append('/');
transportString.Append(Profile.ToString());
transportString.Append('/');
transportString.Append(LowerTransport.ToString());
if (LowerTransport == LowerTransportType.TCP)
{
transportString.Append(";unicast");
}
if (LowerTransport == LowerTransportType.UDP)
{
transportString.Append(';');
transportString.Append(IsMulticast ? "multicast" : "unicast");
}
if (Destination != null)
{
transportString.Append(";destination=");
transportString.Append(Destination);
}
if (Source != null)
{
transportString.Append(";source=");
transportString.Append(Source);
}
if (Interleaved != null)
{
transportString.Append(";interleaved=");
transportString.Append(Interleaved.ToString());
}
if (IsAppend)
{
transportString.Append(";append");
}
if (TTL > 0)
{
transportString.Append(";ttl=");
transportString.Append(TTL);
}
if (Layers > 0)
{
transportString.Append(";layers=");
transportString.Append(Layers);
}
if (Port != null)
{
transportString.Append(";port=");
transportString.Append(Port.ToString());
}
if (ClientPort != null)
{
transportString.Append(";client_port=");
transportString.Append(ClientPort.ToString());
}
if (ServerPort != null)
{
transportString.Append(";server_port=");
transportString.Append(ServerPort.ToString());
}
if (SSrc != null)
{
transportString.Append(";ssrc=");
transportString.Append(SSrc);
}
if (Mode != null && Mode != "PLAY")
{
transportString.Append(";mode=");
transportString.Append(Mode);
}
return transportString.ToString();
}
}
}

View File

@@ -0,0 +1,557 @@
using System;
using System.Collections.Generic;
using System.Diagnostics.Contracts;
using System.Globalization;
using System.IO;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using Inspectron.HawkEye.RTSP.Messages;
namespace Inspectron.HawkEye.RTSP
{
/// <summary>
/// Rtsp lister
/// </summary>
public class RtspListener : IDisposable
{
private static NLog.Logger _logger = NLog.LogManager.GetCurrentClassLogger();
private IRtspTransport _transport;
private Thread _listenTread;
private Stream _stream;
private int _sequenceNumber;
private Dictionary<int, RtspRequest> _sentMessage = new Dictionary<int, RtspRequest>();
/// <summary>
/// Initializes a new instance of the <see cref="RtspListener"/> class from a TCP connection.
/// </summary>
/// <param name="connection">The connection.</param>
public RtspListener(IRtspTransport connection)
{
if (connection == null)
throw new ArgumentNullException("connection");
Contract.EndContractBlock();
_transport = connection;
_stream = connection.GetStream();
}
/// <summary>
/// Gets the remote address.
/// </summary>
/// <value>The remote adress.</value>
public string RemoteAdress
{
get
{
return _transport.RemoteAddress;
}
}
/// <summary>
/// Starts this instance.
/// </summary>
public void Start()
{
_listenTread = new Thread(new ThreadStart(DoJob));
_listenTread.Name = "DoJob";
_listenTread.Start();
}
/// <summary>
/// Stops this instance.
/// </summary>
public void Stop()
{
// brutally close the TCP socket....
// I hope the teardown was sent elsewhere
_transport.Close();
}
/// <summary>
/// Enable auto reconnect.
/// </summary>
public bool AutoReconnect { get; set; }
/// <summary>
/// Occurs when message is received.
/// </summary>
public event EventHandler<RtspChunkEventArgs> MessageReceived;
/// <summary>
/// Raises the <see cref="E:MessageReceived"/> event.
/// </summary>
/// <param name="e">The <see cref="Rtsp.RtspChunkEventArgs"/> instance containing the event data.</param>
protected void OnMessageReceived(RtspChunkEventArgs e)
{
EventHandler<RtspChunkEventArgs> handler = MessageReceived;
if (handler != null)
handler(this, e);
}
/// <summary>
/// Occurs when Data is received.
/// </summary>
public event EventHandler<RtspChunkEventArgs> DataReceived;
/// <summary>
/// Raises the <see cref="E:DataReceived"/> event.
/// </summary>
/// <param name="rtspChunkEventArgs">The <see cref="Rtsp.RtspChunkEventArgs"/> instance containing the event data.</param>
protected void OnDataReceived(RtspChunkEventArgs rtspChunkEventArgs)
{
EventHandler<RtspChunkEventArgs> handler = DataReceived;
if (handler != null)
handler(this, rtspChunkEventArgs);
}
/// <summary>
/// Does the reading job.
/// </summary>
/// <remarks>
/// This method read one message from TCP connection.
/// If it a response it add the associate question.
/// The stopping is made by the closing of the TCP connection.
/// </remarks>
private void DoJob()
{
try
{
_logger.Debug("Connection Open");
while (_transport.Connected)
{
// La lectuer est blocking sauf si la connection est coupé
RtspChunk currentMessage = ReadOneMessage(_stream);
if (currentMessage != null)
{
if (!(currentMessage is RtspData))
{
// on logue le tout
if (currentMessage.SourcePort != null)
_logger.Debug(CultureInfo.InvariantCulture, "Receive from {0}", currentMessage.SourcePort.RemoteAdress);
currentMessage.LogMessage();
}
if (currentMessage is RtspResponse)
{
RtspResponse response = currentMessage as RtspResponse;
lock (_sentMessage)
{
// add the original question to the response.
RtspRequest originalRequest;
if (_sentMessage.TryGetValue(response.CSeq, out originalRequest))
{
_sentMessage.Remove(response.CSeq);
response.OriginalRequest = originalRequest;
}
else
{
_logger.Warn(CultureInfo.InvariantCulture, "Receive response not asked {0}", response.CSeq);
}
}
OnMessageReceived(new RtspChunkEventArgs(response));
}
else if (currentMessage is RtspRequest)
{
OnMessageReceived(new RtspChunkEventArgs(currentMessage));
}
else if (currentMessage is RtspData)
{
OnDataReceived(new RtspChunkEventArgs(currentMessage));
}
}
else
{
_stream.Close();
_transport.Close();
}
}
}
catch (IOException error)
{
_logger.Warn("IO Error", error);
_stream.Close();
_transport.Close();
}
catch (SocketException error)
{
_logger.Warn("Socket Error", error);
_stream.Close();
_transport.Close();
}
catch (ObjectDisposedException error)
{
_logger.Warn("Object Disposed", error);
}
catch (Exception error)
{
_logger.Warn("Unknow Error", error);
// throw;
}
_logger.Debug("Connection Close");
}
[Serializable]
private enum ReadingState
{
NewCommand,
Headers,
Data,
End,
InterleavedData,
MoreInterleavedData,
}
/// <summary>
/// Sends the message.
/// </summary>
/// <param name="message">A message.</param>
/// <returns><see cref="true"/> if it is Ok, otherwise <see cref="false"/></returns>
public bool SendMessage(RtspMessage message)
{
if (message == null)
throw new ArgumentNullException("message");
Contract.EndContractBlock();
if (!_transport.Connected)
{
if(!AutoReconnect)
return false;
_logger.Warn("Reconnect to a client, strange !!");
try
{
Reconnect();
}
catch (SocketException)
{
// on a pas put se connecter on dit au manager de plus compter sur nous
return false;
}
}
// if it it a request we store the original message
// and we renumber it.
//TODO handle lost message (for example every minute cleanup old message)
if (message is RtspRequest)
{
RtspMessage originalMessage = message;
// Do not modify original message
message = message.Clone() as RtspMessage;
_sequenceNumber++;
message.CSeq = _sequenceNumber;
lock (_sentMessage)
{
_sentMessage.Add(message.CSeq, originalMessage as RtspRequest);
}
}
_logger.Debug("Send Message");
message.LogMessage();
message.SendTo(_stream);
return true;
}
/// <summary>
/// Reconnect this instance of RtspListener.
/// </summary>
/// <exception cref="System.Net.Sockets.SocketException">Error during socket </exception>
public void Reconnect()
{
//if it is already connected do not reconnect
if (_transport.Connected)
return;
// If it is not connected listenthread should have die.
if (_listenTread != null && _listenTread.IsAlive)
_listenTread.Join();
if (_stream != null)
_stream.Dispose();
// reconnect
_transport.Reconnect();
_stream = _transport.GetStream();
// If listen thread exist restart it
if (_listenTread != null)
Start();
}
/// <summary>
/// Reads one message.
/// </summary>
/// <param name="commandStream">The Rtsp stream.</param>
/// <returns>Message readen</returns>
public RtspChunk ReadOneMessage(Stream commandStream)
{
if (commandStream == null)
throw new ArgumentNullException("commandStream");
Contract.EndContractBlock();
ReadingState currentReadingState = ReadingState.NewCommand;
// current decode message , create a fake new to permit compile.
RtspChunk currentMessage = null;
int size = 0;
int byteReaden = 0;
List<byte> buffer = new List<byte>(256);
string oneLine = String.Empty;
while (currentReadingState != ReadingState.End)
{
// if the system is not reading binary data.
if (currentReadingState != ReadingState.Data && currentReadingState != ReadingState.MoreInterleavedData)
{
oneLine = String.Empty;
bool needMoreChar = true;
// I do not know to make readline blocking
while (needMoreChar)
{
int currentByte = commandStream.ReadByte();
switch (currentByte)
{
case -1:
// the read is blocking, so if we got -1 it is because the client close;
currentReadingState = ReadingState.End;
needMoreChar = false;
break;
case '\n':
oneLine = ASCIIEncoding.UTF8.GetString(buffer.ToArray());
buffer.Clear();
needMoreChar = false;
break;
case '\r':
// simply ignore this
break;
case '$': // if first caracter of packet is $ it is an interleaved data packet
if (currentReadingState == ReadingState.NewCommand && buffer.Count == 0)
{
currentReadingState = ReadingState.InterleavedData;
needMoreChar = false;
}
else
goto default;
break;
default:
buffer.Add((byte)currentByte);
break;
}
}
}
switch (currentReadingState)
{
case ReadingState.NewCommand:
currentMessage = RtspMessage.GetRtspMessage(oneLine);
currentReadingState = ReadingState.Headers;
break;
case ReadingState.Headers:
string line = oneLine;
if (string.IsNullOrEmpty(line))
{
currentReadingState = ReadingState.Data;
((RtspMessage)currentMessage).InitialiseDataFromContentLength();
}
else
{
((RtspMessage)currentMessage).AddHeader(line);
}
break;
case ReadingState.Data:
if (currentMessage.Data.Length > 0)
{
// Read the remaning data
int byteCount = commandStream.Read(currentMessage.Data, byteReaden,
currentMessage.Data.Length - byteReaden);
if (byteCount <= 0) {
currentReadingState = ReadingState.End;
break;
}
byteReaden += byteCount;
_logger.Debug(CultureInfo.InvariantCulture, "Readen {0} byte of data", byteReaden);
}
// if we haven't read all go there again else go to end.
if (byteReaden >= currentMessage.Data.Length)
currentReadingState = ReadingState.End;
break;
case ReadingState.InterleavedData:
currentMessage = new RtspData();
int channelByte = commandStream.ReadByte();
if (channelByte == -1) {
currentReadingState = ReadingState.End;
break;
}
((RtspData)currentMessage).Channel = channelByte;
int sizeByte1 = commandStream.ReadByte();
if (sizeByte1 == -1) {
currentReadingState = ReadingState.End;
break;
}
int sizeByte2 = commandStream.ReadByte();
if (sizeByte2 == -1) {
currentReadingState = ReadingState.End;
break;
}
size = (sizeByte1 << 8) + sizeByte2;
currentMessage.Data = new byte[size];
currentReadingState = ReadingState.MoreInterleavedData;
break;
case ReadingState.MoreInterleavedData:
// apparently non blocking
{
int byteCount = commandStream.Read(currentMessage.Data, byteReaden, size - byteReaden);
if (byteCount <= 0) {
currentReadingState = ReadingState.End;
break;
}
byteReaden += byteCount;
if (byteReaden < size)
currentReadingState = ReadingState.MoreInterleavedData;
else
currentReadingState = ReadingState.End;
break;
}
default:
break;
}
}
if (currentMessage != null)
currentMessage.SourcePort = this;
return currentMessage;
}
/// <summary>
/// Begins the send data.
/// </summary>
/// <param name="aRtspData">A Rtsp data.</param>
/// <param name="asyncCallback">The async callback.</param>
/// <param name="aState">A state.</param>
public IAsyncResult BeginSendData(RtspData aRtspData, AsyncCallback asyncCallback, object state)
{
if (aRtspData == null)
throw new ArgumentNullException("aRtspData");
Contract.EndContractBlock();
return BeginSendData(aRtspData.Channel, aRtspData.Data, asyncCallback, state);
}
/// <summary>
/// Begins the send data.
/// </summary>
/// <param name="channel">The channel.</param>
/// <param name="frame">The frame.</param>
/// <param name="asyncCallback">The async callback.</param>
/// <param name="aState">A state.</param>
public IAsyncResult BeginSendData(int channel, byte[] frame, AsyncCallback asyncCallback, object state)
{
if (frame == null)
throw new ArgumentNullException("frame");
if (frame.Length > 0xFFFF)
throw new ArgumentException("frame too large", "frame");
Contract.EndContractBlock();
if (!_transport.Connected)
{
if(!AutoReconnect)
return null; // cannot write when transport is disconnected
_logger.Warn("Reconnect to a client, strange !!");
Reconnect();
}
byte[] data = new byte[4 + frame.Length]; // add 4 bytes for the header
data[0] = 36; // '$' character
data[1] = (byte)channel;
data[2] = (byte)((frame.Length & 0xFF00) >> 8);
data[3] = (byte)((frame.Length & 0x00FF));
System.Array.Copy(frame,0,data,4,frame.Length);
return _stream.BeginWrite(data, 0, data.Length, asyncCallback, state);
}
/// <summary>
/// Ends the send data.
/// </summary>
/// <param name="result">The result.</param>
public void EndSendData(IAsyncResult result)
{
try
{
_stream.EndWrite(result);
} catch (Exception e)
{
// Error, for example stream has already been Disposed
_logger.Debug("Error during end send (can be ignored) " + e);
result = null;
}
}
/// <summary>
/// Send data (Synchronous)
/// </summary>
/// <param name="channel">The channel.</param>
/// <param name="frame">The frame.</param>
public void SendData(int channel, byte[] frame)
{
if (frame == null)
throw new ArgumentNullException("frame");
if (frame.Length > 0xFFFF)
throw new ArgumentException("frame too large", "frame");
Contract.EndContractBlock();
if (!_transport.Connected)
{
if(!AutoReconnect)
throw new Exception("Connection is lost");
_logger.Warn("Reconnect to a client, strange !!");
Reconnect();
}
byte[] data = new byte[4 + frame.Length]; // add 4 bytes for the header
data[0] = 36; // '$' character
data[1] = (byte)channel;
data[2] = (byte)((frame.Length & 0xFF00) >> 8);
data[3] = (byte)((frame.Length & 0x00FF));
System.Array.Copy(frame, 0, data, 4, frame.Length);
lock (_stream) {
_stream.Write(data, 0, data.Length);
}
}
#region IDisposable Membres
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing)
{
if (disposing)
{
Stop();
if (_stream != null)
_stream.Dispose();
}
}
#endregion
}
}

View File

@@ -0,0 +1,27 @@
using System;
using Inspectron.HawkEye.RTSP.Messages;
namespace Inspectron.HawkEye.RTSP
{
/// <summary>
/// Event args containing information for message events.
/// </summary>
public class RtspChunkEventArgs :EventArgs
{
/// <summary>
/// Initializes a new instance of the <see cref="RtspChunkEventArgs"/> class.
/// </summary>
/// <param name="aMessage">A message.</param>
public RtspChunkEventArgs(RtspChunk aMessage)
{
Message = aMessage;
}
/// <summary>
/// Gets or sets the message.
/// </summary>
/// <value>The message.</value>
public RtspChunk Message { get; set; }
}
}

View File

@@ -0,0 +1,118 @@
using System;
using System.Diagnostics.Contracts;
using System.Globalization;
using System.IO;
using System.Net;
using System.Net.Sockets;
namespace Inspectron.HawkEye.RTSP
{
/// <summary>
/// TCP Connection for Rtsp
/// </summary>
public class RtspTcpTransport : IRtspTransport, IDisposable
{
private IPEndPoint _currentEndPoint;
private TcpClient _RtspServerClient;
/// <summary>
/// Initializes a new instance of the <see cref="RtspTcpTransport"/> class.
/// </summary>
/// <param name="tcpConnection">The underlying TCP connection.</param>
public RtspTcpTransport(TcpClient tcpConnection)
{
if (tcpConnection == null)
throw new ArgumentNullException("tcpConnection");
Contract.EndContractBlock();
_currentEndPoint = (IPEndPoint)tcpConnection.Client.RemoteEndPoint;
_RtspServerClient = tcpConnection;
}
/// <summary>
/// Initializes a new instance of the <see cref="RtspTcpTransport"/> class.
/// </summary>
/// <param name="aHost">A host.</param>
/// <param name="aPortNumber">A port number.</param>
public RtspTcpTransport(string aHost, int aPortNumber)
: this(new TcpClient(aHost, aPortNumber))
{
}
#region IRtspTransport Membres
/// <summary>
/// Gets the stream of the transport.
/// </summary>
/// <returns>A stream</returns>
public Stream GetStream()
{
return _RtspServerClient.GetStream();
}
/// <summary>
/// Gets the remote address.
/// </summary>
/// <value>The remote address.</value>
public string RemoteAddress
{
get
{
return string.Format(CultureInfo.InvariantCulture,"{0}:{1}", _currentEndPoint.Address, _currentEndPoint.Port);
}
}
/// <summary>
/// Closes this instance.
/// </summary>
public void Close()
{
Dispose(true);
}
/// <summary>
/// Gets a value indicating whether this <see cref="IRtspTransport"/> is connected.
/// </summary>
/// <value><c>true</c> if connected; otherwise, <c>false</c>.</value>
public bool Connected
{
get { return _RtspServerClient.Client != null && _RtspServerClient.Connected; }
}
/// <summary>
/// Reconnect this instance.
/// <remarks>Must do nothing if already connected.</remarks>
/// </summary>
/// <exception cref="System.Net.Sockets.SocketException">Error during socket </exception>
public void Reconnect()
{
if (Connected)
return;
_RtspServerClient = new TcpClient();
_RtspServerClient.Connect(_currentEndPoint);
}
#endregion
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing)
{
if (disposing)
{
_RtspServerClient.Close();
/* // free managed resources
if (managedResource != null)
{
managedResource.Dispose();
managedResource = null;
}*/
}
}
}
}

View File

@@ -0,0 +1,16 @@
using System;
namespace Inspectron.HawkEye.RTSP
{
public static class RtspUtils
{
/// <summary>
/// Registers the URI.
/// </summary>
public static void RegisterUri()
{
if (!UriParser.IsKnownScheme("rtsp"))
UriParser.Register(new HttpStyleUriParser(), "rtsp", 554);
}
}
}

View File

@@ -0,0 +1,77 @@
using System;
using System.Collections.Generic;
using System.Diagnostics.Contracts;
using System.Linq;
namespace Inspectron.HawkEye.RTSP.Sdp
{
public class Attribut
{
private static readonly Dictionary<string, Type> attributMap = new Dictionary<string, Type>()
{
{AttributRtpMap.NAME,typeof(AttributRtpMap)},
{AttributFmtp.NAME,typeof(AttributFmtp)},
};
public virtual string Key { get; private set; }
public virtual string Value { get; protected set; }
public static void RegisterNewAttributeType(string key, Type attributType)
{
if(!attributType.IsSubclassOf(typeof(Attribut)))
throw new ArgumentException("Type must be subclass of Rtsp.Sdp.Attribut","attributType");
attributMap[key] = attributType;
}
public Attribut()
{
}
public Attribut(string key)
{
Key = key;
}
public static Attribut ParseInvariant(string value)
{
if(value == null)
throw new ArgumentNullException("value");
Contract.EndContractBlock();
var listValues = value.Split(new char[] {':'}, 2);
Attribut returnValue;
// Call parser of child type
Type childType;
attributMap.TryGetValue(listValues[0], out childType);
if (childType != null)
{
var defaultContructor = childType.GetConstructor(Type.EmptyTypes);
returnValue = defaultContructor.Invoke(Type.EmptyTypes) as Attribut;
}
else
{
returnValue = new Attribut(listValues[0]);
}
// Parse the value. Note most attributes have a value but recvonly does not have a value
if (listValues.Count() > 1) returnValue.ParseValue(listValues[1]);
return returnValue;
}
protected virtual void ParseValue(string value)
{
Value = value;
}
}
}

View File

@@ -0,0 +1,75 @@
using System;
using System.Collections.Generic;
using System.Linq;
namespace Inspectron.HawkEye.RTSP.Sdp
{
public class AttributFmtp : Attribut
{
public const string NAME = "fmtp";
private Dictionary<String, String> parameters = new Dictionary<string, string>();
public AttributFmtp()
{
}
public override string Key
{
get
{
return NAME;
}
}
public override string Value
{
get
{
return string.Format("{0} {1}", PayloadNumber, FormatParameter);
}
protected set
{
ParseValue(value);
}
}
public int PayloadNumber { get; set; }
// temporary aatibute to store remaning data not parsed
public string FormatParameter { get; set; }
// Extract the Payload Number and the Format Parameters
protected override void ParseValue(string value)
{
var parts = value.Split(new char[] { ' ' }, 2);
int payloadNumber;
if(int.TryParse(parts[0], out payloadNumber))
{
this.PayloadNumber = payloadNumber;
}
if(parts.Length > 1)
{
FormatParameter = parts[1];
// Split on ';' to get a list of items.
// Then Trim each item and then Split on the first '='
// Add them to the dictionary
parameters.Clear();
foreach (var pair in parts[1].Split(';').Select(x => x.Trim().Split(new char[] { '=' }, 2))) {
if (!string.IsNullOrWhiteSpace(pair[0]))
parameters[pair[0]] = pair.Length > 1 ? pair[1] : null;
}
}
}
public String GetParameter(String index)
{
if (parameters.ContainsKey(index)) return parameters[index];
else return "";
}
}
}

View File

@@ -0,0 +1,76 @@
using System;
namespace Inspectron.HawkEye.RTSP.Sdp
{
public class AttributRtpMap : Attribut
{
// Format
// rtpmap:<payload type> <encoding name>/<clock rate> [/<encoding parameters>]
// Examples
// rtpmap:96 H264/90000
// rtpmap:8 PCMA/8000
public const string NAME = "rtpmap";
public AttributRtpMap()
{
}
public override string Key
{
get
{
return NAME;
}
}
public override string Value
{
get
{
if(string.IsNullOrEmpty(EncodingParameters))
{
return string.Format("{0} {1}/{2}", PayloadNumber, EncodingName, ClockRate);
} else {
return string.Format("{0} {1}/{2}/{3}", PayloadNumber, EncodingName, ClockRate, EncodingParameters);
}
}
protected set
{
ParseValue(value);
}
}
public int PayloadNumber { get; set; }
public String EncodingName { get; set; }
public String ClockRate { get; set; }
public String EncodingParameters { get; set; }
protected override void ParseValue(string value)
{
var parts = value.Split(new char[] { ' ', '/' });
if (parts.Length >= 1) {
int tmp_payloadNumber;
if (int.TryParse(parts[0], out tmp_payloadNumber))
{
PayloadNumber = tmp_payloadNumber;
}
}
if (parts.Length >= 2)
{
EncodingName = parts[1];
}
if (parts.Length >= 3)
{
ClockRate = parts[2];
}
if (parts.Length >= 4)
{
EncodingParameters = parts[3];
}
}
}
}

View File

@@ -0,0 +1,15 @@
namespace Inspectron.HawkEye.RTSP.Sdp
{
public class Bandwidth
{
public Bandwidth()
{
}
internal static Bandwidth Parse(string value)
{
//TODO really parse.
return new Bandwidth();
}
}
}

View File

@@ -0,0 +1,48 @@
using System;
using System.Globalization;
namespace Inspectron.HawkEye.RTSP.Sdp
{
public abstract class Connection
{
public Connection()
{
//Default value from spec
NumberOfAddress = 1;
}
public string Host { get; set; }
/// <summary>
/// Gets or sets the number of address specifed in connection.
/// </summary>
/// <value>The number of address.</value>
//TODO handle it a different way (list of adress ?)
public int NumberOfAddress { get; set; }
public static Connection Parse(string value)
{
if(value ==null)
throw new ArgumentNullException("value");
string[] parts = value.Split(' ');
if (parts.Length != 3)
throw new FormatException("Value do not contain 3 parts as needed.");
if (parts[0] != "IN")
throw new NotSupportedException(string.Format(CultureInfo.InvariantCulture, "Net type {0} not suported", parts[0]));
switch (parts[1])
{
case "IP4":
return ConnectionIP4.Parse(parts[2]);
case "IP6":
return ConnectionIP6.Parse(parts[2]);
default:
throw new NotSupportedException(string.Format(CultureInfo.InvariantCulture, "Address type {0} not suported", parts[1]));
}
}
}
}

View File

@@ -0,0 +1,40 @@
using System;
using System.Globalization;
namespace Inspectron.HawkEye.RTSP.Sdp
{
public class ConnectionIP4 : Connection
{
public int Ttl { get; set; }
internal new static ConnectionIP4 Parse(string ipAddress)
{
string[] parts = ipAddress.Split('/');
if (parts.Length > 3)
throw new FormatException("Too much address subpart in " + ipAddress);
ConnectionIP4 result = new ConnectionIP4();
result.Host = parts[0];
int ttl;
if (parts.Length > 1)
{
if (!int.TryParse(parts[1], NumberStyles.Integer, CultureInfo.InvariantCulture, out ttl))
throw new FormatException("Invalid TTL format : " + parts[1]);
result.Ttl = ttl;
}
int numberOfAddress;
if (parts.Length > 2)
{
if (!int.TryParse(parts[2], NumberStyles.Integer, CultureInfo.InvariantCulture, out numberOfAddress))
throw new FormatException("Invalid number of address : " + parts[2]);
result.NumberOfAddress = numberOfAddress;
}
return result;
}
}
}

View File

@@ -0,0 +1,31 @@
using System;
using System.Globalization;
namespace Inspectron.HawkEye.RTSP.Sdp
{
public class ConnectionIP6 : Connection
{
internal new static ConnectionIP6 Parse(string ipAddress)
{
string[] parts = ipAddress.Split('/');
if (parts.Length > 2)
throw new FormatException("Too much address subpart in " + ipAddress);
ConnectionIP6 result = new ConnectionIP6();
result.Host = parts[0];
int numberOfAddress;
if (parts.Length > 1)
{
if (!int.TryParse(parts[1], NumberStyles.Integer, CultureInfo.InvariantCulture, out numberOfAddress))
throw new FormatException("Invalid number of address : " + parts[1]);
result.NumberOfAddress = numberOfAddress;
}
return result;
}
}
}

View File

@@ -0,0 +1,22 @@
using System;
using System.Diagnostics.Contracts;
namespace Inspectron.HawkEye.RTSP.Sdp
{
public class EncriptionKey
{
public EncriptionKey(string p)
{
}
public static EncriptionKey ParseInvariant(string value)
{
if (value == null)
throw new ArgumentNullException("value");
Contract.EndContractBlock();
throw new NotImplementedException();
}
}
}

View File

@@ -0,0 +1,136 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
namespace Inspectron.HawkEye.RTSP.Sdp
{
public class H264Parameters : IDictionary<String, String>
{
private readonly Dictionary<String, String> parameters = new Dictionary<string, string>();
public List<byte[]> SpropParameterSets
{
get
{
List<byte[]> result = new List<byte[]>();
if (ContainsKey("sprop-parameter-sets")&& this["sprop-parameter-sets"] != null)
{
result.AddRange(this["sprop-parameter-sets"].Split(',').Select(x => Convert.FromBase64String(x)));
}
return result;
}
}
public static H264Parameters Parse(String parameterString)
{
var result = new H264Parameters();
foreach (var pair in parameterString.Split(';').Select(x => x.Trim().Split(new char[] { '=' }, 2)))
{
if(!string.IsNullOrWhiteSpace(pair[0]))
result[pair[0]] = pair.Length > 1 ? pair[1] : null;
}
return result;
}
public override string ToString()
{
return parameters.Select(p => p.Key + (p.Value != null ? "=" + p.Value : string.Empty)).Aggregate((x, y) => x + ";" + y);
}
public String this[String index]
{
get { return parameters[index]; }
set { parameters[index] = value; }
}
public int Count
{
get
{
return parameters.Count;
}
}
public bool IsReadOnly
{
get
{
return ((IDictionary<string, string>)parameters).IsReadOnly;
}
}
public ICollection<string> Keys
{
get
{
return ((IDictionary<string, string>)parameters).Keys;
}
}
public ICollection<string> Values
{
get
{
return ((IDictionary<string, string>)parameters).Values;
}
}
public void Add(KeyValuePair<string, string> item)
{
((IDictionary<string, string>)parameters).Add(item);
}
public void Add(string key, string value)
{
parameters.Add(key, value);
}
public void Clear()
{
parameters.Clear();
}
public bool Contains(KeyValuePair<string, string> item)
{
return ((IDictionary<string, string>)parameters).Contains(item);
}
public bool ContainsKey(string key)
{
return parameters.ContainsKey(key);
}
public void CopyTo(KeyValuePair<string, string>[] array, int arrayIndex)
{
((IDictionary<string, string>)parameters).CopyTo(array, arrayIndex);
}
public IEnumerator<KeyValuePair<string, string>> GetEnumerator()
{
return ((IDictionary<string, string>)parameters).GetEnumerator();
}
public bool Remove(KeyValuePair<string, string> item)
{
return ((IDictionary<string, string>)parameters).Remove(item);
}
public bool Remove(string key)
{
return parameters.Remove(key);
}
public bool TryGetValue(string key, out string value)
{
return parameters.TryGetValue(key, out value);
}
IEnumerator IEnumerable.GetEnumerator()
{
return ((IDictionary<string, string>)parameters).GetEnumerator();
}
}
}

View File

@@ -0,0 +1,149 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
// Parse 'fmtp' attribute in SDP
// Extract H265 fields
// By Roger Hardiman, RJH Technical Consultancy Ltd
namespace Inspectron.HawkEye.RTSP.Sdp
{
public class H265Parameters : IDictionary<String, String>
{
private readonly Dictionary<String, String> parameters = new Dictionary<string, string>();
public List<byte[]> SpropParameterSets
{
get
{
List<byte[]> result = new List<byte[]>();
if (ContainsKey("sprop-vps")&& this["sprop-vps"] != null)
{
result.AddRange(this["sprop-vps"].Split(',').Select(x => Convert.FromBase64String(x)));
}
if (ContainsKey("sprop-sps") && this["sprop-sps"] != null)
{
result.AddRange(this["sprop-sps"].Split(',').Select(x => Convert.FromBase64String(x)));
}
if (ContainsKey("sprop-pps") && this["sprop-pps"] != null)
{
result.AddRange(this["sprop-pps"].Split(',').Select(x => Convert.FromBase64String(x)));
}
return result;
}
}
public static H265Parameters Parse(String parameterString)
{
var result = new H265Parameters();
foreach (var pair in parameterString.Split(';').Select(x => x.Trim().Split(new char[] { '=' }, 2)))
{
if(!string.IsNullOrWhiteSpace(pair[0]))
result[pair[0]] = pair.Length > 1 ? pair[1] : null;
}
return result;
}
public override string ToString()
{
return parameters.Select(p => p.Key + (p.Value != null ? "=" + p.Value : string.Empty)).Aggregate((x, y) => x + ";" + y);
}
public String this[String index]
{
get { return parameters[index]; }
set { parameters[index] = value; }
}
public int Count
{
get
{
return parameters.Count;
}
}
public bool IsReadOnly
{
get
{
return ((IDictionary<string, string>)parameters).IsReadOnly;
}
}
public ICollection<string> Keys
{
get
{
return ((IDictionary<string, string>)parameters).Keys;
}
}
public ICollection<string> Values
{
get
{
return ((IDictionary<string, string>)parameters).Values;
}
}
public void Add(KeyValuePair<string, string> item)
{
((IDictionary<string, string>)parameters).Add(item);
}
public void Add(string key, string value)
{
parameters.Add(key, value);
}
public void Clear()
{
parameters.Clear();
}
public bool Contains(KeyValuePair<string, string> item)
{
return ((IDictionary<string, string>)parameters).Contains(item);
}
public bool ContainsKey(string key)
{
return parameters.ContainsKey(key);
}
public void CopyTo(KeyValuePair<string, string>[] array, int arrayIndex)
{
((IDictionary<string, string>)parameters).CopyTo(array, arrayIndex);
}
public IEnumerator<KeyValuePair<string, string>> GetEnumerator()
{
return ((IDictionary<string, string>)parameters).GetEnumerator();
}
public bool Remove(KeyValuePair<string, string> item)
{
return ((IDictionary<string, string>)parameters).Remove(item);
}
public bool Remove(string key)
{
return parameters.Remove(key);
}
public bool TryGetValue(string key, out string value)
{
return parameters.TryGetValue(key, out value);
}
IEnumerator IEnumerable.GetEnumerator()
{
return ((IDictionary<string, string>)parameters).GetEnumerator();
}
}
}

View File

@@ -0,0 +1,60 @@
using System.Collections.Generic;
using System.Linq;
namespace Inspectron.HawkEye.RTSP.Sdp
{
public class Media
{
private string mediaString;
public Media(string mediaString)
{
// Example is 'video 0 RTP/AVP 26;
this.mediaString = mediaString;
var parts = mediaString.Split(new char[] { ' ' } , 4);
if (parts.Count() >= 1) {
if (parts[0].Equals("video")) MediaType = MediaTypes.video;
else if (parts[0].Equals("audio")) MediaType = MediaTypes.audio;
else if (parts[0].Equals("text")) MediaType = MediaTypes.text;
else if (parts[0].Equals("application")) MediaType = MediaTypes.application;
else if (parts[0].Equals("message")) MediaType = MediaTypes.message;
else MediaType = MediaTypes.unknown; // standard does allow for future types to be defined
}
int pt;
if (parts.Count() >= 4) {
if(int.TryParse(parts[3], out pt))
{
PayloadType = pt;
} else {
PayloadType = 0;
}
}
}
// RFC4566 Media Types
public enum MediaTypes { video, audio, text, application, message, unknown };
public Connection Connection { get; set; }
public Bandwidth Bandwidth { get; set; }
public EncriptionKey EncriptionKey { get; set; }
public MediaTypes MediaType { get; set; }
public int PayloadType { get; set; }
private readonly List<Attribut> attributs = new List<Attribut>();
public IList<Attribut> Attributs
{
get
{
return attributs;
}
}
}
}

View File

@@ -0,0 +1,101 @@
using System;
using System.Globalization;
namespace Inspectron.HawkEye.RTSP.Sdp
{
/// <summary>
/// Object ot represent orgin in an Session Description Protocol
/// </summary>
public class Origin
{
public Origin()
{
}
/// <summary>
/// Parses the specified origin string.
/// </summary>
/// <param name="originString">The string to convert to origin object.</param>
/// <returns></returns>
public static Origin Parse(string originString)
{
if (originString == null)
throw new ArgumentNullException("originString");
string[] parts = originString.Split(' ');
if (parts.Length != 6)
throw new FormatException("Number of element invalid in origin string.");
Origin result = new Origin();
result.Username = parts[0];
result.SessionId = parts[1];
result.SessionVersion = parts[2];
result.NetType = parts[3];
result.AddressType = parts[4];
result.UnicastAddress = parts[5];
return result;
}
/// <summary>
/// Gets or sets the username.
/// </summary>
/// <remarks>It is the user's login on the originating host, or it is "-"
/// if the originating host does not support the concept of user IDs.
/// This MUST NOT contain spaces</remarks>
/// <value>The username.</value>
public string Username { get; set; }
/// <summary>
/// Gets or sets the session id.
/// </summary>
/// <remarks>It is a numeric string such that the tuple of <see cref="Username"/>,
/// <see cref="SessionId"/>, <see cref="NetType"/>, <see cref="AddressType"/>, and <see cref="UnicastAddress"/> forms a
/// globally unique identifier for the session. The method of
/// <see cref="SessionId"/> allocation is up to the creating tool, but it has been
/// suggested that a Network Time Protocol (NTP) format timestamp be
/// used to ensure uniqueness</remarks>
/// <value>The session id.</value>
public string SessionId { get; set; }
/// <summary>
/// Gets or sets the session version.
/// </summary>
/// <value>The session version.</value>
public string SessionVersion { get; set; }
/// <summary>
/// Gets or sets the type of the net.
/// </summary>
/// <value>The type of the net.</value>
public string NetType { get; set; }
/// <see cref="SessionId"/><summary>
/// Gets or sets the type of the address.
/// </summary>
/// <value>The type of the address.</value>
public string AddressType { get; set; }
/// <summary>
/// Gets or sets the unicast address (IP or FDQN).
/// </summary>
/// <value>The unicast address.</value>
public string UnicastAddress { get; set; }
public override string ToString()
{
return String.Join(" ",
new string[]
{
Username,
SessionId,
SessionVersion.ToString(CultureInfo.InvariantCulture),
NetType,
AddressType,
UnicastAddress,
}
);
}
}
}

View File

@@ -0,0 +1,267 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
namespace Inspectron.HawkEye.RTSP.Sdp
{
public class SdpFile
{
private static KeyValuePair<string, string> GetKeyValue(TextReader sdpStream)
{
string line = sdpStream.ReadLine();
// end of file ?
if(string.IsNullOrEmpty(line))
return new KeyValuePair<string, string>(null, null);
string[] parts = line.Split(new char[] { '=' }, 2);
if (parts.Length != 2)
throw new InvalidDataException();
if (parts[0].Length != 1)
throw new InvalidDataException();
KeyValuePair<string, string> value = new KeyValuePair<string, string>(parts[0], parts[1]);
return value;
}
/// <summary>
/// Reads the specified SDP stream.
/// As define in RFC 4566
/// </summary>
/// <param name="sdpStream">The SDP stream.</param>
/// <returns></returns>
public static SdpFile Read(TextReader sdpStream)
{
SdpFile returnValue = new SdpFile();
KeyValuePair<string, string> value = GetKeyValue(sdpStream);
// Version mandatory
if (value.Key == "v")
{
returnValue.Version = int.Parse(value.Value, CultureInfo.InvariantCulture);
value = GetKeyValue(sdpStream);
}
else {
throw new InvalidDataException();
}
// Origin mandatory
if (value.Key == "o")
{
returnValue.Origin = Origin.Parse(value.Value);
value = GetKeyValue(sdpStream);
}
else {
throw new InvalidDataException();
}
// Session mandatory.
// However the MuxLab HDMI Encoder (TX-500762) Firmware 1.0.6
// does not include the 'Session' so supress InvalidDatarException
if (value.Key == "s")
{
returnValue.Session = value.Value;
value = GetKeyValue(sdpStream);
}
else {
// throw new InvalidDataException(); // we should throw, but instead we just ignore the error
}
// Session Information optional
if (value.Key == "i")
{
returnValue.SessionInformation = value.Value;
value = GetKeyValue(sdpStream);
}
// Uri optional
if (value.Key == "u")
{
returnValue.Url = new Uri(value.Value);
value = GetKeyValue(sdpStream);
}
// Email optional
if (value.Key == "e")
{
returnValue.Email = value.Value;
value = GetKeyValue(sdpStream);
}
// Phone optional
if (value.Key == "p")
{
returnValue.Phone = value.Value;
value = GetKeyValue(sdpStream);
}
// Connection optional
if (value.Key == "c")
{
returnValue.Connection = Connection.Parse(value.Value);
value = GetKeyValue(sdpStream);
}
// bandwidth optional
if (value.Key == "b")
{
returnValue.Bandwidth = Bandwidth.Parse(value.Value);
value = GetKeyValue(sdpStream);
}
// Timing mandatory
while (value.Key == "t")
{
string timing = value.Value;
string repeat = string.Empty;
value = GetKeyValue(sdpStream);
if (value.Key == "r")
{
repeat = value.Value;
value = GetKeyValue(sdpStream);
}
returnValue.Timings.Add(new Timing(timing, repeat));
}
// timezone optional
if (value.Key == "z")
{
returnValue.TimeZone = SdpTimeZone.ParseInvariant(value.Value);
value = GetKeyValue(sdpStream);
}
// encryption key optional
if (value.Key == "k")
{
returnValue.EncriptionKey = EncriptionKey.ParseInvariant(value.Value);
value = GetKeyValue(sdpStream);
}
//Attribute optional multiple
while (value.Key == "a")
{
returnValue.Attributs.Add(Attribut.ParseInvariant(value.Value));
value = GetKeyValue(sdpStream);
}
// Hack for MuxLab HDMI Encoder (TX-500762) Firmware 1.0.6
// Skip over all other Key/Value pairs until the 'm=' key
while (value.Key != "m") {
value = GetKeyValue(sdpStream);
}
// Media
while (value.Key == "m")
{
Media newMedia = ReadMedia(sdpStream, ref value);
returnValue.Medias.Add(newMedia);
}
return returnValue;
}
private static Media ReadMedia(TextReader sdpStream, ref KeyValuePair<string, string> value)
{
Media returnValue = new Media(value.Value);
value = GetKeyValue(sdpStream);
// Media title
if (value.Key == "i")
{
value = GetKeyValue(sdpStream);
}
// Connexion optional
if (value.Key == "c")
{
returnValue.Connection = Connection.Parse(value.Value);
value = GetKeyValue(sdpStream);
}
// bandwidth optional
if (value.Key == "b")
{
returnValue.Bandwidth = Bandwidth.Parse(value.Value);
value = GetKeyValue(sdpStream);
}
// enkription key optional
if (value.Key == "k")
{
returnValue.EncriptionKey = EncriptionKey.ParseInvariant(value.Value);
value = GetKeyValue(sdpStream);
}
//Attribut optional multiple
while (value.Key == "a")
{
returnValue.Attributs.Add(Attribut.ParseInvariant(value.Value));
value = GetKeyValue(sdpStream);
}
return returnValue;
}
public int Version { get; set; }
public Origin Origin { get; set; }
public string Session { get; set; }
public string SessionInformation { get; set; }
public Uri Url { get; set; }
public string Email { get; set; }
public string Phone { get; set; }
public Connection Connection { get; set; }
public Bandwidth Bandwidth { get; set; }
private readonly List<Timing> timingList = new List<Timing>();
public IList<Timing> Timings
{
get
{
return timingList;
}
}
public SdpTimeZone TimeZone { get; set; }
public EncriptionKey EncriptionKey { get; set; }
private readonly List<Attribut> attributs = new List<Attribut>();
public IList<Attribut> Attributs
{
get
{
return attributs;
}
}
private readonly List<Media> medias = new List<Media>();
public IList<Media> Medias
{
get
{
return medias;
}
}
}
}

View File

@@ -0,0 +1,26 @@
using System;
using System.Diagnostics.Contracts;
namespace Inspectron.HawkEye.RTSP.Sdp
{
public class SdpTimeZone
{
public SdpTimeZone()
{
}
public static SdpTimeZone ParseInvariant(string value)
{
if (value == null)
throw new ArgumentNullException("value");
Contract.EndContractBlock();
SdpTimeZone returnValue = new SdpTimeZone();
throw new NotImplementedException();
return returnValue;
}
}
}

View File

@@ -0,0 +1,15 @@
namespace Inspectron.HawkEye.RTSP.Sdp
{
public class Timing
{
private string timing;
private string repeat;
public Timing(string timing, string repeat)
{
// TODO: Complete member initialization
this.timing = timing;
this.repeat = repeat;
}
}
}

View File

@@ -0,0 +1,401 @@
using System;
using System.Collections.Generic;
/*
* CJOCh264bitstream.cpp
*
* Created on: Aug 23, 2014
* Author: Jordi Cenzano (www.jordicenzano.name)
*/
/*
* CJOCh264bitstream.h
*
* Created on: Aug 23, 2014
* Author: Jordi Cenzano (www.jordicenzano.name)
*/
//! h264 bitstream class
/*!
It is used to create the h264 bit oriented stream, it contains different functions that helps you to create the h264 compliant stream (bit oriented, exp golomb coder)
*/
namespace Inspectron.HawkEye.RTSP.Server
{
public class CJOCh264bitstream : System.IDisposable
{
private const int BUFFER_SIZE_BITS = 24; //! Buffer size in bits used for emulation prevention
//C++ TO C# CONVERTER NOTE: The following #define macro was replaced in-line:
//ORIGINAL LINE: #define BUFFER_SIZE_BYTES (24/8)
private const int H264_EMULATION_PREVENTION_BYTE = 0x03; //! Emulation prevention byte
/*! Buffer */
private byte[] m_buffer = new byte[BUFFER_SIZE_BITS];
/*! Bit buffer index */
private int m_nLastbitinbuffer;
/*! Starting byte indicator */
private int m_nStartingbyte;
/*! Pointer to output file */
//private FILE m_pOutFile;
//Byte Array used for output
private List<byte> m_pOutFile;
//! Clears the buffer
private void clearbuffer()
{
//C++ TO C# CONVERTER TODO TASK: The memory management function 'memset' has no equivalent in C#:
//memset(m_buffer, 0, sizeof(byte) * BUFFER_SIZE_BITS);
System.Array.Clear(m_buffer, 0, BUFFER_SIZE_BITS);
m_nLastbitinbuffer = 0;
m_nStartingbyte = 0;
}
//! Returns the nNumbit value (1 or 0) of lval
/*!
\param lval number to extract the nNumbit value
\param nNumbit Bit position that we want to know if its 1 or 0 (from 0 to 63)
\return bit value (1 or 0)
*/
private static int getbitnum(uint lval, int nNumbit)
{
int lrc = 0;
uint lmask = (uint) Math.Pow((uint)2,(uint)nNumbit);
if ((lval & lmask) > 0)
{
lrc = 1;
}
return lrc;
}
//! Adds 1 bit to the end of h264 bitstream
/*!
\param nVal bit to add at the end of h264 bitstream
*/
private void addbittostream(int nVal)
{
if (m_nLastbitinbuffer >= BUFFER_SIZE_BITS)
{
//Must be aligned, no need to do dobytealign();
savebufferbyte();
}
//Use circular buffer of BUFFER_SIZE_BYTES
int nBytePos = (m_nStartingbyte + (m_nLastbitinbuffer / 8)) % (24 / 8);
//The first bit to add is on the left
int nBitPosInByte = 7 - m_nLastbitinbuffer % 8;
//Get the byte value from buffer
int nValTmp = m_buffer[nBytePos];
//Change the bit
if (nVal > 0)
{
nValTmp = (nValTmp | (int) Math.Pow(2,nBitPosInByte));
}
else
{
nValTmp = (nValTmp & ~((int) Math.Pow(2,nBitPosInByte)));
}
//Save the new byte value to the buffer
m_buffer[nBytePos] = (byte) nValTmp;
m_nLastbitinbuffer++;
}
//! Adds 8 bit to the end of h264 bitstream (it is optimized for byte aligned situations)
/*!
\param nVal byte to add at the end of h264 bitstream (from 0 to 255)
*/
private void addbytetostream(int nVal)
{
if (m_nLastbitinbuffer >= BUFFER_SIZE_BITS)
{
//Must be aligned, no need to do dobytealign();
savebufferbyte();
}
//Used circular buffer of BUFFER_SIZE_BYTES
int nBytePos = (m_nStartingbyte + (m_nLastbitinbuffer / 8)) % (24 / 8);
//The first bit to add is on the left
int nBitPosInByte = 7 - m_nLastbitinbuffer % 8;
//Check if it is byte aligned
if (nBitPosInByte != 7)
{
throw new System.Exception("Error: inserting not aligment byte");
}
//Add all byte to buffer
m_buffer[nBytePos] = (byte) nVal;
m_nLastbitinbuffer = m_nLastbitinbuffer + 8;
}
//! Save all buffer to file
/*!
\param bemulationprevention Indicates if it will insert the emulation prevention byte or not (when it is needed)
*/
private void savebufferbyte(bool bemulationprevention = true)
{
bool bemulationpreventionexecuted = false;
if (m_pOutFile == null)
{
throw new System.Exception("Error: out file is NULL");
}
//Check if the last bit in buffer is multiple of 8
if ((m_nLastbitinbuffer % 8) != 0)
{
throw new System.Exception("Error: Save to file must be byte aligned");
}
if ((m_nLastbitinbuffer / 8) <= 0)
{
throw new System.Exception("Error: NO bytes to save");
}
if (bemulationprevention == true)
{
//Emulation prevention will be used:
/*As per h.264 spec,
rbsp_data shouldn't contain
- 0x 00 00 00
- 0x 00 00 01
- 0x 00 00 02
- 0x 00 00 03
rbsp_data shall be in the following way
- 0x 00 00 03 00
- 0x 00 00 03 01
- 0x 00 00 03 02
- 0x 00 00 03 03
*/
//Check if emulation prevention is needed (emulation prevention is byte align defined)
if ( (m_buffer[((m_nStartingbyte + 0) % (24 / 8))] == 0x00)
&& (m_buffer[((m_nStartingbyte + 1) % (24 / 8))] == 0x00)
&& ((m_buffer[((m_nStartingbyte + 2) % (24 / 8))] == 0x00)
|| (m_buffer[((m_nStartingbyte + 2) % (24 / 8))] == 0x01)
|| (m_buffer[((m_nStartingbyte + 2) % (24 / 8))] == 0x02)
|| (m_buffer[((m_nStartingbyte + 2) % (24 / 8))] == 0x03)))
{
int nbuffersaved = 0;
byte cEmulationPreventionByte = H264_EMULATION_PREVENTION_BYTE;
//Save 1st byte
fwrite(m_buffer[((m_nStartingbyte + nbuffersaved) % (24 / 8))], 1, 1, m_pOutFile);
nbuffersaved++;
//Save 2st byte
fwrite(m_buffer[((m_nStartingbyte + nbuffersaved) % (24 / 8))], 1, 1, m_pOutFile);
nbuffersaved++;
//Save emulation prevention byte
fwrite(cEmulationPreventionByte, 1, 1, m_pOutFile);
//Save the rest of bytes (usually 1)
while (nbuffersaved < (24 / 8))
{
fwrite(m_buffer[((m_nStartingbyte + nbuffersaved) % (24 / 8))], 1, 1, m_pOutFile);
nbuffersaved++;
}
//All bytes in buffer are saved, so clear the buffer
clearbuffer();
bemulationpreventionexecuted = true;
}
}
if (bemulationpreventionexecuted == false)
{
//No emulation prevention was used
//Save the oldest byte in buffer
fwrite(m_buffer[m_nStartingbyte], 1, 1, m_pOutFile);
//Move the index
m_buffer[m_nStartingbyte] = 0;
m_nStartingbyte++;
m_nStartingbyte = m_nStartingbyte % (24 / 8);
m_nLastbitinbuffer = m_nLastbitinbuffer - 8;
}
}
//! Constructor
/*!
\param pOutBinaryFile The output file pointer
*/
public CJOCh264bitstream(List<byte> pOutBinaryFile)
{
clearbuffer();
//C++ TO C# CONVERTER TODO TASK: C# does not have an equivalent to pointers to variables (in C#, the variable no longer points to the original when the original variable is re-assigned):
//ORIGINAL LINE: m_pOutFile = pOutBinaryFile;
m_pOutFile = pOutBinaryFile;
}
//! Destructor
public virtual void Dispose()
{
close();
}
//! Add 4 bytes to h264 bistream without taking into acount the emulation prevention. Used to add the NAL header to the h264 bistream
/*!
\param nVal The 32b value to add
\param bDoAlign Indicates if the function will insert 0 in order to create a byte aligned stream before adding nVal 4 bytes to stream. If you try to call this function and the stream is not byte aligned an exception will be thrown
*/
public void add4bytesnoemulationprevention(uint nVal, bool bDoAlign = false)
{
//Used to add NAL header stream
//Remember: NAL header is byte oriented
if (bDoAlign == true)
{
dobytealign();
}
if ((m_nLastbitinbuffer % 8) != 0)
{
throw new System.Exception("Error: Save to file must be byte aligned");
}
while (m_nLastbitinbuffer != 0)
{
savebufferbyte();
}
byte cbyte = (byte)((nVal & 0xFF000000) >> 24);
fwrite(cbyte, 1, 1, m_pOutFile);
cbyte = (byte)((nVal & 0x00FF0000) >> 16);
fwrite(cbyte, 1, 1, m_pOutFile);
cbyte = (byte)((nVal & 0x0000FF00) >> 8);
fwrite(cbyte, 1, 1, m_pOutFile);
cbyte = (byte)(nVal & 0x000000FF);
fwrite(cbyte, 1, 1, m_pOutFile);
}
//! Adds nNumbits of lval to the end of h264 bitstream
/*!
\param nVal value to add at the end of the h264 stream (only the LAST nNumbits will be added)
\param nNumbits number of bits of lval that will be added to h264 stream (counting from left)
*/
//Public functions
public void addbits(uint lval, int nNumbits)
{
if ((nNumbits <= 0) || (nNumbits > 64))
{
throw new System.Exception("Error: numbits must be between 1 ... 64");
}
int nBit = 0;
int n = nNumbits - 1;
while (n >= 0)
{
nBit = getbitnum(lval, n);
n--;
addbittostream(nBit);
}
}
//! Adds lval to the end of h264 bitstream using exp golomb coding for unsigned values
/*!
\param nVal value to add at the end of the h264 stream
*/
public void addexpgolombunsigned(uint lval)
{
//it implements unsigned exp golomb coding
uint lvalint = lval + 1;
int nnumbits = (int)(Math.Log(lvalint,2) + 1);
for (int n = 0; n < (nnumbits - 1); n++)
{
addbits(0, 1);
}
addbits(lvalint, nnumbits);
}
//! Adds lval to the end of h264 bitstream using exp golomb coding for signed values
/*!
\param nVal value to add at the end of the h264 stream
*/
public void addexpgolombsigned(int lval)
{
//it implements a signed exp golomb coding
uint lvalint = (uint)(Math.Abs(lval) * 2 - 1);
if (lval <= 0)
{
lvalint = (uint)(2 * Math.Abs(lval));
}
addexpgolombunsigned(lvalint);
}
//! Adds 0 to the end of h264 bistream in order to leave a byte aligned stream (It will insert seven 0 maximum)
public void dobytealign()
{
//Check if the last bit in buffer is multiple of 8
int nr = m_nLastbitinbuffer % 8;
if ((nr % 8) != 0)
{
m_nLastbitinbuffer = m_nLastbitinbuffer + (8 - nr);
}
}
//! Adds cByte (8 bits) to the end of h264 bitstream. This function it is optimized in byte aligned streams.
/*!
\param cByte value to add at the end of the h264 stream (from 0 to 255)
*/
public void addbyte(byte cByte)
{
//Byte alignment optimization
if ((m_nLastbitinbuffer % 8) == 0)
{
addbytetostream(cByte);
}
else
{
addbits(cByte, 8);
}
}
//! Close the h264 stream saving to disk the last remaing bits in buffer
public void close()
{
//Flush the data in stream buffer
dobytealign();
while (m_nLastbitinbuffer != 0)
{
savebufferbyte();
}
}
// 'writing' to memory
private void fwrite(byte b, int x, int y, List<byte>data)
{
data.Add(b);
}
}
}

View File

@@ -0,0 +1,498 @@
/* * CJOCh264encoder.cpp
*
* Created on: Aug 17, 2014
* Author: Jordi Cenzano (www.jordicenzano.name)
*/
/*
* CJOCh264encoder.h
*
* Created on: Aug 17, 2014
* Author: Jordi Cenzano (www.jordicenzano.name)
*/
//C++ TO C# CONVERTER NOTE: The following #define macro was replaced in-line:
//ORIGINAL LINE: #define BUFFER_SIZE_BYTES (24/8)
//! h264 encoder class
/*!
It is used to create the h264 compliant stream
*/
using System.Collections.Generic;
namespace Inspectron.HawkEye.RTSP.Server
{
public class CJOCh264encoder : CJOCh264bitstream
{
/**
* Allowed sample formats
*/
public enum enSampleFormat
{
SAMPLE_FORMAT_YUV420p //!< SAMPLE_FORMAT_YUV420p
}
public List<byte> m_pOutFile = null;
public byte[] sps = null;
public byte[] pps = null;
public byte[] nal = null;
/*!Set the used Y macroblock size for I PCM in YUV420p */
private const int MACROBLOCK_Y_WIDTH = 16;
private const int MACROBLOCK_Y_HEIGHT = 16;
/*!Set time base in Hz */
private const int TIME_SCALE_IN_HZ = 27000000;
/*!Pointer to pixels */
private class YUV420p_frame_t
{
public byte[] pYCbCr;
}
/*! Frame */
private class frame_t
{
public enSampleFormat sampleformat; //!< Sample format
public uint nYwidth; //!< Y (luminance) block width in pixels
public uint nYheight; //!< Y (luminance) block height in pixels
public uint nCwidth; //!< C (Crominance) block width in pixels
public uint nCheight; //!< C (Crominance) block height in pixels
public uint nYmbwidth; //!< Y (luminance) macroblock width in pixels
public uint nYmbheight; //!< Y (luminance) macroblock height in pixels
public uint nCmbwidth; //!< Y (Crominance) macroblock width in pixels
public uint nCmbheight; //!< Y (Crominance) macroblock height in pixels
public YUV420p_frame_t yuv420pframe = new YUV420p_frame_t(); //!< Pointer to current frame data
public uint nyuv420pframesize; //!< Size in bytes of yuv420pframe
}
/*! The frame var*/
private frame_t m_frame = new frame_t();
/*! The frames per second var*/
private uint m_nFps;
/*! Number of frames sent to the output */
private uint m_lNumFramesAdded;
//! Frees the frame yuv420pframe allocated memory
//Free the allocated video frame mem
private void free_video_src_frame()
{
if (m_frame.yuv420pframe.pYCbCr != null)
{
//C++ TO C# CONVERTER TODO TASK: The memory management function 'free' has no equivalent in C#:
// free(m_frame.yuv420pframe.pYCbCr);
}
//C++ TO C# CONVERTER TODO TASK: The memory management function 'memset' has no equivalent in C#:
// memset(m_frame, 0, sizeof(frame_t));
}
//! Allocs the frame yuv420pframe memory according to the frame properties
//Alloc mem to store a video frame
private void alloc_video_src_frame()
{
if (m_frame.yuv420pframe.pYCbCr != null)
{
throw new System.Exception("Error: null values in frame");
}
uint nYsize = m_frame.nYwidth * m_frame.nYheight;
uint nCsize = m_frame.nCwidth * m_frame.nCheight;
m_frame.nyuv420pframesize = nYsize + nCsize + nCsize;
m_frame.yuv420pframe.pYCbCr = new byte[m_frame.nyuv420pframesize];
if (m_frame.yuv420pframe.pYCbCr == null)
{
throw new System.Exception("Error: memory alloc");
}
}
//! Creates SPS NAL and add it to the output
/*!
\param nImW Frame width in pixels
\param nImH Frame height in pixels
\param nMbW macroblock width in pixels
\param nMbH macroblock height in pixels
\param nFps frames x second (tipical values are: 25, 30, 50, etc)
\param nSARw Indicates the horizontal size of the sample aspect ratio (tipical values are:1, 4, 16, etc)
\param nSARh Indicates the vertical size of the sample aspect ratio (tipical values are:1, 3, 9, etc)
*/
//Creates and saves the NAL SPS (including VUI) (one per file)
private void create_sps(uint nImW, uint nImH, uint nMbW, uint nMbH, uint nFps, uint nSARw, uint nSARh)
{
add4bytesnoemulationprevention(0x000001); // NAL header
addbits(0x0, 1); // forbidden_bit
addbits(0x3, 2); // nal_ref_idc
addbits(0x7, 5); // nal_unit_type : 7 ( SPS )
addbits(0x42, 8); // profile_idc = baseline ( 0x42 )
addbits(0x0, 1); // constraint_set0_flag
addbits(0x0, 1); // constraint_set1_flag
addbits(0x0, 1); // constraint_set2_flag
addbits(0x0, 1); // constraint_set3_flag
addbits(0x0, 1); // constraint_set4_flag
addbits(0x0, 1); // constraint_set5_flag
addbits(0x0, 2); // reserved_zero_2bits /* equal to 0 */
addbits(0x0a, 8); // level_idc: 3.1 (0x0a)
addexpgolombunsigned(0); // seq_parameter_set_id
addexpgolombunsigned(0); // log2_max_frame_num_minus4
addexpgolombunsigned(0); // pic_order_cnt_type
addexpgolombunsigned(0); // log2_max_pic_order_cnt_lsb_minus4
addexpgolombunsigned(0); // max_num_refs_frames
addbits(0x0, 1); // gaps_in_frame_num_value_allowed_flag
uint nWinMbs = nImW / nMbW;
addexpgolombunsigned(nWinMbs - 1); // pic_width_in_mbs_minus_1
uint nHinMbs = nImH / nMbH;
addexpgolombunsigned(nHinMbs - 1); // pic_height_in_map_units_minus_1
addbits(0x1, 1); // frame_mbs_only_flag
addbits(0x0, 1); // direct_8x8_interfernce
addbits(0x0, 1); // frame_cropping_flag
// addbits(0x1, 1); // vui_parameter_present
addbits(0x0, 1); // vui_parameter_present
//VUI parameters (AR, timming)
// addbits(0x1, 1); //aspect_ratio_info_present_flag
// addbits(0xFF, 8); //aspect_ratio_idc = Extended_SAR
//AR
// addbits(nSARw, 16); //sar_width
// addbits(nSARh, 16); //sar_height
// addbits(0x0, 1); //overscan_info_present_flag
// addbits(0x0, 1); //video_signal_type_present_flag
// addbits(0x0, 1); //chroma_loc_info_present_flag
// addbits(0x1, 1); //timing_info_present_flag
// uint nnum_units_in_tick = TIME_SCALE_IN_HZ / (2 * nFps);
// addbits(nnum_units_in_tick, 32); //num_units_in_tick
// addbits(TIME_SCALE_IN_HZ, 32); //time_scale
// addbits(0x1, 1); //fixed_frame_rate_flag
// addbits(0x0, 1); //nal_hrd_parameters_present_flag
// addbits(0x0, 1); //vcl_hrd_parameters_present_flag
// addbits(0x0, 1); //pic_struct_present_flag
// addbits(0x0, 1); //bitstream_restriction_flag
//END VUI
//BUG? addbits(0x0, 1); // frame_mbs_only_flag
addbits(0x1, 1); // rbsp stop bit
dobytealign();
}
//! Creates PPS NAL and add it to the output
//Creates and saves the NAL PPS (one per file)
private void create_pps()
{
add4bytesnoemulationprevention(0x000001); // NAL header
addbits(0x0, 1); // forbidden_bit
addbits(0x3, 2); // nal_ref_idc
addbits(0x8, 5); // nal_unit_type : 8 ( PPS )
addexpgolombunsigned(0); // pic_parameter_set_id
addexpgolombunsigned(0); // seq_parameter_set_id
addbits(0x0, 1); // entropy_coding_mode_flag
addbits(0x0, 1); // bottom_field_pic_order_in frame_present_flag
addexpgolombunsigned(0); // nun_slices_groups_minus1
addexpgolombunsigned(0); // num_ref_idx10_default_active_minus
addexpgolombunsigned(0); // num_ref_idx11_default_active_minus
addbits(0x0, 1); // weighted_pred_flag
addbits(0x0, 2); // weighted_bipred_idc
addexpgolombsigned(0); // pic_init_qp_minus26
addexpgolombsigned(0); // pic_init_qs_minus26
addexpgolombsigned(0); // chroma_qp_index_offset
addbits(0x0, 1); //deblocking_filter_present_flag
addbits(0x0, 1); // constrained_intra_pred_flag
addbits(0x0, 1); //redundant_pic_ent_present_flag
addbits(0x1, 1); // rbsp stop bit
dobytealign();
}
//! Creates Slice NAL and add it to the output
/*!
\param lFrameNum number of frame
*/
//Creates and saves the NAL SLICE (one per frame)
//H264 Spec Section 7.3.3 Slice Header Syntax
private void create_slice_header(uint lFrameNum)
{
add4bytesnoemulationprevention(0x000001); // NAL header
addbits(0x0, 1); // forbidden_bit
addbits(0x3, 2); // nal_ref_idc
addbits(0x5, 5); // nal_unit_type : 5 ( Coded slice of an IDR picture )
addexpgolombunsigned(0); // first_mb_in_slice
addexpgolombunsigned(7); // slice_type
addexpgolombunsigned(0); // pic_param_set_id
byte cFrameNum = 0; // (byte)(lFrameNum % 16); // H264 Spec says "If the current picture is an IDR picture, frame_num shall be equal to 0. "
// Also any maths here must relate to the value of log2_max_frame_num_minus4 in the SPS
addbits(cFrameNum, 4); // frame_num ( numbits = v = log2_max_frame_num_minus4 + 4)
// idr_pic_id range is 0..65535. All slices in the same IDR must have the same pic_id. Spec says if there are two
// IDRs back to back they must have different idr_pic_id values
uint lidr_pic_id = lFrameNum % 65536;
addexpgolombunsigned(lidr_pic_id); // idr_pic_id
addbits(0x0, 4); // pic_order_cnt_lsb (numbits = v = log2_max_fpic_order_cnt_lsb_minus4 + 4)
// nal_ref_idc != 0. Insert dec_ref_pic_marking
addbits(0x0, 1); // no_output_of_prior_pics_flag
addbits(0x0, 1); // long_term_reference_flag
addexpgolombsigned(0); //slice_qp_delta
//Probably NOT byte aligned!!!
}
//! Creates macroblock header and add it to the output
//Creates and saves the macroblock header(one per macroblock)
private void create_macroblock_header()
{
addexpgolombunsigned(25); // mb_type (I_PCM)
}
//! Creates the slice footer and add it to the output
//Creates and saves the SLICE footer (one per SLICE)
private void create_slice_footer()
{
addbits(0x1, 1); // rbsp stop bit
}
//! Creates SPS NAL and add it to the output
/*!
\param nYpos First vertical macroblock pixel inside the frame
\param nYpos nXpos horizontal macroblock pixel inside the frame
*/
//Creates & saves a macroblock (coded INTRA 16x16)
private void create_macroblock(uint nYpos, uint nXpos)
{
uint x;
uint y;
create_macroblock_header();
dobytealign();
//Y
uint nYsize = m_frame.nYwidth * m_frame.nYheight;
for (y = nYpos * m_frame.nYmbheight; y < (nYpos + 1) * m_frame.nYmbheight; y++)
{
for (x = nXpos * m_frame.nYmbwidth; x < (nXpos + 1) * m_frame.nYmbwidth; x++)
{
addbyte(m_frame.yuv420pframe.pYCbCr[(y * m_frame.nYwidth + x)]);
}
}
//Cb
uint nCsize = m_frame.nCwidth * m_frame.nCheight;
for (y = nYpos * m_frame.nCmbheight; y < (nYpos + 1) * m_frame.nCmbheight; y++)
{
for (x = nXpos * m_frame.nCmbwidth; x < (nXpos + 1) * m_frame.nCmbwidth; x++)
{
addbyte(m_frame.yuv420pframe.pYCbCr[nYsize + (y * m_frame.nCwidth + x)]);
}
}
//Cr
for (y = nYpos * m_frame.nCmbheight; y < (nYpos + 1) * m_frame.nCmbheight; y++)
{
for (x = nXpos * m_frame.nCmbwidth; x < (nXpos + 1) * m_frame.nCmbwidth; x++)
{
addbyte(m_frame.yuv420pframe.pYCbCr[nYsize + nCsize + (y * m_frame.nCwidth + x)]);
}
}
}
//! Constructor
/*!
\param pOutFile The output file pointer
*/
//Private functions
//Contructor
public CJOCh264encoder(List<byte> pOutFile) : base(pOutFile)
{
m_lNumFramesAdded = 0;
//C++ TO C# CONVERTER TODO TASK: The memory management function 'memset' has no equivalent in C#:
//memset(m_frame, 0, sizeof(frame_t));
m_nFps = 25;
m_pOutFile = pOutFile;
}
//! Destructor
//Destructor
public override void Dispose()
{
free_video_src_frame();
base.Dispose();
}
//! Initializes the coder
/*!
\param nImW Frame width in pixels
\param nImH Frame height in pixels
\param nFps Desired frames per second of the output file (typical values are: 25, 30, 50, etc)
\param SampleFormat Sample format if the input file. In this implementation only SAMPLE_FORMAT_YUV420p is allowed
\param nSARw Indicates the horizontal size of the sample aspect ratio (typical values are:1, 4, 16, etc)
\param nSARh Indicates the vertical size of the sample aspect ratio (typical values are:1, 3, 9, etc)
*/
//public functions
//Initilizes the h264 coder (mini-coder)
public void IniCoder(uint nImW, uint nImH, uint nImFps, CJOCh264encoder.enSampleFormat SampleFormat, uint nSARw = 1, uint nSARh = 1)
{
m_lNumFramesAdded = 0;
if (SampleFormat != enSampleFormat.SAMPLE_FORMAT_YUV420p)
{
throw new System.Exception("Error: SAMPLE FORMAT not allowed. Only yuv420p is allowed in this version");
}
free_video_src_frame();
//Ini vars
m_frame.sampleformat = SampleFormat;
m_frame.nYwidth = nImW;
m_frame.nYheight = nImH;
if (SampleFormat == enSampleFormat.SAMPLE_FORMAT_YUV420p)
{
//Set macroblock Y size
m_frame.nYmbwidth = MACROBLOCK_Y_WIDTH;
m_frame.nYmbheight = MACROBLOCK_Y_HEIGHT;
//Set macroblock C size (in YUV420 is 1/2 of Y)
m_frame.nCmbwidth = MACROBLOCK_Y_WIDTH / 2;
m_frame.nCmbheight = MACROBLOCK_Y_HEIGHT / 2;
//Set C size
m_frame.nCwidth = m_frame.nYwidth / 2;
m_frame.nCheight = m_frame.nYheight / 2;
//In this implementation only picture sizes multiples of macroblock size (16x16) are allowed
if (((nImW % MACROBLOCK_Y_WIDTH) != 0) || ((nImH % MACROBLOCK_Y_HEIGHT) != 0))
{
throw new System.Exception("Error: size not allowed. Only multiples of macroblock are allowed (macroblock size is: 16x16)");
}
}
m_nFps = nImFps;
//Alloc mem for 1 frame
alloc_video_src_frame();
//Create h264 SPS & PPS
create_sps(m_frame.nYwidth, m_frame.nYheight, m_frame.nYmbwidth, m_frame.nYmbheight, nImFps, nSARw, nSARh);
close(); // Flush data to the List<byte>
sps = m_pOutFile.ToArray();
m_pOutFile.Clear();
create_pps();
close(); // Flush data to the List<byte>
pps = m_pOutFile.ToArray();
m_pOutFile.Clear();
}
//! Returns the frame pointer
/*!
\return Frame pointer ready to fill with frame pixels data (the format to fill the data is indicated by SampleFormat parameter when the coder is initialized
*/
//Returns the frame pointer to load the video frame
public byte[] GetFramePtr()
{
if (m_frame.yuv420pframe.pYCbCr == null)
{
throw new System.Exception("Error: video frame is null (not initialized)");
}
return m_frame.yuv420pframe.pYCbCr;
}
//! Returns the allocated frame memory in bytes
/*!
\return The allocated memory to store the frame data
*/
//Returns the the allocated size for video frame
public uint GetFrameSize()
{
return m_frame.nyuv420pframesize;
}
//! It codes the frame that is in frame memory a it saves the coded data to disc
//Codifies & save the video frame (it only uses 16x16 intra PCM -> NO COMPRESSION!)
public void CodeAndSaveFrame()
{
m_pOutFile.Clear();
//The slice header is not byte aligned, so the first macroblock header is not byte aligned
create_slice_header(m_lNumFramesAdded);
//Loop over macroblock size
uint y;
uint x;
for (y = 0; y < m_frame.nYheight / m_frame.nYmbheight; y++)
{
for (x = 0; x < m_frame.nYwidth / m_frame.nYmbwidth; x++)
{
create_macroblock(y, x);
}
}
create_slice_footer();
dobytealign();
m_lNumFramesAdded++;
// flush
close();
nal = m_pOutFile.ToArray();
}
//! Returns number of coded frames
/*!
\return The number of coded frames
*/
//Returns the number of codified frames
public uint GetSavedFrames()
{
return m_lNumFramesAdded;
}
//! Flush all data and save the trailing bits
//Closes the h264 coder saving the last bits in the buffer
public void CloseCoder()
{
close();
}
}
}

View File

@@ -0,0 +1,34 @@
namespace Inspectron.HawkEye.RTSP.Server
{
public static class RTPPacketUtil
{
public static void WriteHeader(byte[] rtp_packet, int rtp_version, int rtp_padding, int rtp_extension, int rtp_csrc_count, int rtp_marker, int rtp_payload_type)
{
rtp_packet[0] = (byte)((rtp_version << 6) | (rtp_padding << 5) | (rtp_extension << 4) | rtp_csrc_count);
rtp_packet[1] = (byte)((rtp_marker << 7) | (rtp_payload_type & 0x7F));
}
public static void WriteSequenceNumber(byte[] rtp_packet, uint empty_sequence_id)
{
rtp_packet[2] = ((byte)((empty_sequence_id >> 8) & 0xFF));
rtp_packet[3] = ((byte)((empty_sequence_id >> 0) & 0xFF));
}
public static void WriteTS(byte[] rtp_packet, uint ts)
{
rtp_packet[4] = ((byte)((ts >> 24) & 0xFF));
rtp_packet[5] = ((byte)((ts >> 16) & 0xFF));
rtp_packet[6] = ((byte)((ts >> 8) & 0xFF));
rtp_packet[7] = ((byte)((ts >> 0) & 0xFF));
}
public static void WriteSSRC(byte[] rtp_packet, uint ssrc)
{
rtp_packet[8] = ((byte)((ssrc >> 24) & 0xFF));
rtp_packet[9] = ((byte)((ssrc >> 16) & 0xFF));
rtp_packet[10] = ((byte)((ssrc >> 8) & 0xFF));
rtp_packet[11] = ((byte)((ssrc >> 0) & 0xFF));
}
}
}

View File

@@ -0,0 +1,800 @@
using System;
using System.Collections.Generic;
using System.Diagnostics.Contracts;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using Inspectron.HawkEye.RTSP.Messages;
// RTSP Server Example (c) Roger Hardiman, 2016, 2018
// Released uder the MIT Open Source Licence
//
// Re-uses some code from the Multiplexer example of SharpRTSP
//
// This example simulates a live RTSP video stream, for example a CCTV Camera
// It creates a Video Source (a test card) that creates a YUV Image
// The image is then encoded as H264 data using a very basic H264 Encoder
// The H264 data (the NALs) are sent to the RTSP clients
// Video is sent in UDP Mode or TCP Mode (ie RTP over RTSP mode)
// The Tiny H264 Encoder is a 100% .NET encoder which is lossless and creates large bitstreams as
// there is no compression. It is limited to 128x96 resolution. However it makes it easy to write a quick
// demo without needing native APIs or cross compiled C libraries for H264
namespace Inspectron.HawkEye.RTSP.Server
{
public class RtspServer : IDisposable
{
const int h264_width = 192; // Tiny needs 128x96
const int h264_height = 128;
const int h264_fps = 25;
const uint global_ssrc = 0x4321FADE; // 8 hex digits
private TcpListener _RTSPServerListener;
private ManualResetEvent _Stopping;
private Thread _ListenTread;
private TestCard video_source = null;
private SimpleH264Encoder h264_encoder = null;
//private TinyH264Encoder h264_encoder = null;
byte[] raw_sps = null;
byte[] raw_pps = null;
List<RTSPConnection> rtsp_list = new List<RTSPConnection>(); // list of RTSP Listeners
Random rnd = new Random();
int session_handle = 1;
Authentication auth = null;
/// <summary>
/// Initializes a new instance of the <see cref="RTSPServer"/> class.
/// </summary>
/// <param name="aPortNumber">A numero port.</param>
/// <param name="username">username.</param>
/// <param name="password">password.</param>
public RtspServer(int portNumber, String username, String password)
{
if (portNumber < System.Net.IPEndPoint.MinPort || portNumber > System.Net.IPEndPoint.MaxPort)
throw new ArgumentOutOfRangeException("aPortNumber", portNumber, "Port number must be between System.Net.IPEndPoint.MinPort and System.Net.IPEndPoint.MaxPort");
Contract.EndContractBlock();
if (String.IsNullOrEmpty(username) == false
&& String.IsNullOrEmpty(password) == false) {
String realm = "SharpRTSPServer";
auth = new Authentication(username,password,realm,Authentication.Type.Digest);
} else {
auth = null;
}
RtspUtils.RegisterUri();
_RTSPServerListener = new TcpListener(IPAddress.Any, portNumber);
}
/// <summary>
/// Starts the listen.
/// </summary>
public void StartListen()
{
_RTSPServerListener.Start();
_Stopping = new ManualResetEvent(false);
_ListenTread = new Thread(new ThreadStart(AcceptConnection));
_ListenTread.Start();
// Initialise the H264 encoder
h264_encoder = new SimpleH264Encoder(h264_width, h264_height, h264_fps);
//h264_encoder = new TinyH264Encoder(); // hard coded to 192x128
// Start the VideoSource
video_source = new TestCard(h264_width, h264_height, h264_fps);
video_source.ReceivedYUVFrame += video_source_ReceivedYUVFrame;
}
/// <summary>
/// Accepts the connection.
/// </summary>
private void AcceptConnection()
{
try
{
while (!_Stopping.WaitOne(0))
{
// Wait for an incoming TCP Connection
TcpClient oneClient = _RTSPServerListener.AcceptTcpClient();
Console.WriteLine("Connection from " + oneClient.Client.RemoteEndPoint.ToString());
// Hand the incoming TCP connection over to the RTSP classes
var rtsp_socket = new RtspTcpTransport(oneClient);
RtspListener newListener = new RtspListener(rtsp_socket);
newListener.MessageReceived += RTSP_Message_Received;
//RTSPDispatcher.Instance.AddListener(newListener);
// Add the RtspListener to the RTSPConnections List
lock (rtsp_list) {
RTSPConnection new_connection = new RTSPConnection();
new_connection.listener = newListener;
new_connection.client_hostname = newListener.RemoteAdress.Split(':')[0];
new_connection.ssrc = global_ssrc;
new_connection.time_since_last_rtsp_keepalive = DateTime.UtcNow;
new_connection.video_time_since_last_rtcp_keepalive = DateTime.UtcNow;
rtsp_list.Add(new_connection);
}
newListener.Start();
}
}
catch (SocketException error)
{
// _logger.Warn("Got an error listening, I have to handle the stopping which also throw an error", error);
}
catch (Exception error)
{
// _logger.Error("Got an error listening...", error);
throw;
}
}
public void StopListen()
{
_RTSPServerListener.Stop();
_Stopping.Set();
_ListenTread.Join();
}
#region IDisposable Membres
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing)
{
if (disposing)
{
StopListen();
_Stopping.Dispose();
}
}
#endregion
// Process each RTSP message that is received
private void RTSP_Message_Received(object sender, RtspChunkEventArgs e)
{
// Cast the 'sender' and 'e' into the RTSP Listener (the Socket) and the RTSP Message
RtspListener listener = sender as RtspListener;
RtspMessage message = e.Message as RtspMessage;
Console.WriteLine("RTSP message received " + message);
// Check if the RTSP Message has valid authentication (validating against username,password,realm and nonce)
if (auth != null) {
bool authorized = false;
if (message.Headers.ContainsKey("Authorization") == true ) {
// The Header contained Authorization
// Check the message has the correct Authorization
// If it does not have the correct Authorization then close the RTSP connection
authorized = auth.IsValid(message);
if (authorized == false) {
// Send a 401 Authentication Failed reply, then close the RTSP Socket
RtspResponse authorization_response = (e.Message as RtspRequest).CreateResponse();
authorization_response.AddHeader("WWW-Authenticate: " + auth.GetHeader());
authorization_response.ReturnCode = 401;
listener.SendMessage(authorization_response);
lock (rtsp_list) {
foreach (RTSPConnection connection in rtsp_list.ToArray()){
if (connection.listener == listener) {
rtsp_list.Remove(connection);
}
}
}
listener.Dispose();
return;
}
}
if ((message.Headers.ContainsKey("Authorization") == false)){
// Send a 401 Authentication Failed with extra info in WWW-Authenticate
// to tell the Client if we are using Basic or Digest Authentication
RtspResponse authorization_response = (e.Message as RtspRequest).CreateResponse();
authorization_response.AddHeader("WWW-Authenticate: " + auth.GetHeader()); // 'Basic' or 'Digest'
authorization_response.ReturnCode = 401;
listener.SendMessage(authorization_response);
return;
}
}
// Update the RTSP Keepalive Timeout
// We could check that the message is GET_PARAMETER or OPTIONS for a keepalive but instead we will update the timer on any message
lock (rtsp_list)
{
foreach (RTSPConnection connection in rtsp_list)
{
if (connection.listener.RemoteAdress.Equals(listener.RemoteAdress))
{
// found the connection
connection.time_since_last_rtsp_keepalive = DateTime.UtcNow;
break;
}
}
}
// Handle OPTIONS message
if (message is RtspRequestOptions)
{
// Create the reponse to OPTIONS
RtspResponse options_response = (e.Message as RtspRequestOptions).CreateResponse();
listener.SendMessage(options_response);
}
// Handle DESCRIBE message
if (message is RtspRequestDescribe)
{
String requested_url = (message as RtspRequestDescribe).RtspUri.ToString();
Console.WriteLine("Request for " + requested_url);
// TODO. Check the requsted_url is valid. In this example we accept any RTSP URL
// Make the Base64 SPS and PPS
raw_sps = h264_encoder.GetRawSPS(); // no 0x00 0x00 0x00 0x01 or 32 bit size header
raw_pps = h264_encoder.GetRawPPS(); // no 0x00 0x00 0x00 0x01 or 32 bit size header
String sps_str = Convert.ToBase64String(raw_sps);
String pps_str = Convert.ToBase64String(raw_pps);
StringBuilder sdp = new StringBuilder();
// Generate the SDP
// The sprop-parameter-sets provide the SPS and PPS for H264 video
// The packetization-mode defines the H264 over RTP payloads used but is Optional
sdp.Append("v=0\n");
sdp.Append("o=user 123 0 IN IP4 0.0.0.0\n");
sdp.Append("s=SharpRTSP Test Camera\n");
sdp.Append("m=video 0 RTP/AVP 96\n");
sdp.Append("c=IN IP4 0.0.0.0\n");
sdp.Append("a=control:trackID=0\n");
sdp.Append("a=rtpmap:96 H264/90000\n");
sdp.Append("a=fmtp:96 profile-level-id=42A01E; sprop-parameter-sets=" + sps_str + "," + pps_str + ";\n");
byte[] sdp_bytes = Encoding.ASCII.GetBytes(sdp.ToString());
// Create the reponse to DESCRIBE
// This must include the Session Description Protocol (SDP)
RtspResponse describe_response = (e.Message as RtspRequestDescribe).CreateResponse();
describe_response.AddHeader("Content-Base: " + requested_url);
describe_response.AddHeader("Content-Type: application/sdp");
describe_response.Data = sdp_bytes;
describe_response.AdjustContentLength();
listener.SendMessage(describe_response);
}
// Handle SETUP message
if (message is RtspRequestSetup)
{
//
var setupMessage = message as RtspRequestSetup;
// Check the RTSP transport
// If it is UDP or Multicast, create the sockets
// If it is RTP over RTSP we send data via the RTSP Listener
// FIXME client may send more than one possible transport.
// very rare
RtspTransport transport = setupMessage.GetTransports()[0];
// Construct the Transport: reply from the Server to the client
RtspTransport transport_reply = new RtspTransport();
transport_reply.SSrc = global_ssrc.ToString("X8"); // Convert to Hex, padded to 8 characters
if (transport.LowerTransport == RtspTransport.LowerTransportType.TCP)
{
// RTP over RTSP mode}
transport_reply.LowerTransport = RtspTransport.LowerTransportType.TCP;
transport_reply.Interleaved = new PortCouple(transport.Interleaved.First, transport.Interleaved.Second);
}
UDPSocket udp_pair = null;
if (transport.LowerTransport == RtspTransport.LowerTransportType.UDP
&& transport.IsMulticast == false)
{
Boolean udp_supported = true;
if (udp_supported) {
// RTP over UDP mode
// Create a pair of UDP sockets - One is for the Video, one is for the RTCP
udp_pair = new UDPSocket(50000, 51000); // give a range of 500 pairs (1000 addresses) to try incase some address are in use
udp_pair.DataReceived += (object local_sender, RtspChunkEventArgs local_e) => {
// RTCP data received
Console.WriteLine("RTCP data received " + local_sender.ToString() + " " + local_e.ToString());
};
udp_pair.Start(); // start listening for data on the UDP ports
// Pass the Port of the two sockets back in the reply
transport_reply.LowerTransport = RtspTransport.LowerTransportType.UDP;
transport_reply.IsMulticast = false;
transport_reply.ClientPort = new PortCouple(udp_pair.data_port,udp_pair.control_port);
} else {
transport_reply = null;
}
}
if (transport.LowerTransport == RtspTransport.LowerTransportType.UDP
&& transport.IsMulticast == true)
{
// RTP over Multicast UDP mode}
// Create a pair of UDP sockets in Multicast Mode
// Pass the Ports of the two sockets back in the reply
transport_reply.LowerTransport = RtspTransport.LowerTransportType.UDP;
transport_reply.IsMulticast = true;
transport_reply.Port = new PortCouple(7000, 7001); // FIX
// for now until implemented
transport_reply = null;
}
if (transport_reply != null)
{
// Update the session with transport information
String copy_of_session_id = "";
lock (rtsp_list)
{
foreach (RTSPConnection connection in rtsp_list)
{
if (connection.listener.RemoteAdress.Equals(listener.RemoteAdress)) {
// ToDo - Check the Track ID to determine if this is a SETUP for the Video Stream
// or a SETUP for an Audio Stream.
// In the SDP the H264 video track is TrackID 0
// found the connection
// Add the transports to the connection
connection.video_client_transport = transport;
connection.video_transport_reply = transport_reply;
// If we are sending in UDP mode, add the UDP Socket pair and the Client Hostname
connection.video_udp_pair = udp_pair;
connection.video_session_id = session_handle.ToString();
session_handle++;
// Copy the Session ID
copy_of_session_id = connection.video_session_id;
break;
}
}
}
RtspResponse setup_response = setupMessage.CreateResponse();
setup_response.Headers[RtspHeaderNames.Transport] = transport_reply.ToString();
setup_response.Session = copy_of_session_id;
listener.SendMessage(setup_response);
}
else
{
RtspResponse setup_response = setupMessage.CreateResponse();
// unsuported transport
setup_response.ReturnCode = 461;
listener.SendMessage(setup_response);
}
}
// Handle PLAY message (Sent with a Session ID)
if (message is RtspRequestPlay)
{
lock (rtsp_list)
{
// Search for the Session in the Sessions List. Change the state to "PLAY"
bool session_found = false;
foreach (RTSPConnection connection in rtsp_list)
{
if (message.Session == connection.video_session_id) /* OR AUDIO_SESSION_ID */
{
// found the session
session_found = true;
connection.play = true; // ACTUALLY YOU COULD PAUSE JUST THE VIDEO (or JUST THE AUDIO)
string range = "npt=0-"; // Playing the 'video' from 0 seconds until the end
string rtp_info = "url="+((RtspRequestPlay)message).RtspUri+";seq=" + connection.video_sequence_number; // TODO Add rtptime +";rtptime="+session.rtp_initial_timestamp;
// Send the reply
RtspResponse play_response = (e.Message as RtspRequestPlay).CreateResponse();
play_response.AddHeader("Range: " + range);
play_response.AddHeader("RTP-Info: " + rtp_info);
listener.SendMessage(play_response);
break;
}
}
if (session_found == false) {
// Session ID was not found in the list of Sessions. Send a 454 error
RtspResponse play_failed_response = (e.Message as RtspRequestPlay).CreateResponse();
play_failed_response.ReturnCode = 454; // Session Not Found
listener.SendMessage(play_failed_response);
}
}
}
// Handle PAUSE message (Sent with a Session ID)
if (message is RtspRequestPause)
{
lock (rtsp_list)
{
// Search for the Session in the Sessions List. Change the state of "PLAY"
foreach (RTSPConnection connection in rtsp_list)
{
if (message.Session == connection.video_session_id /* OR AUDIO SESSION ID */)
{
// found the session
connection.play = false; // COULD HAVE PLAY/PAUSE FOR VIDEO AND AUDIO
break;
}
}
}
// ToDo - only send back the OK response if the Session in the RTSP message was found
RtspResponse pause_response = (e.Message as RtspRequestPause).CreateResponse();
listener.SendMessage(pause_response);
}
// Handle GET_PARAMETER message, often used as a Keep Alive
if (message is RtspRequestGetParameter)
{
// Create the reponse to GET_PARAMETER
RtspResponse getparameter_response = (e.Message as RtspRequestGetParameter).CreateResponse();
listener.SendMessage(getparameter_response);
}
// Handle TEARDOWN (sent with a Session ID)
if (message is RtspRequestTeardown)
{
lock (rtsp_list)
{
// Search for the Session in the Sessions List.
foreach (RTSPConnection connection in rtsp_list.ToArray()) // Convert to ToArray so we can delete from the rtp_list
{
if (message.Session == connection.video_session_id) // SHOULD HAVE AN AUDIO TEARDOWN AS WELL
{
// If this is UDP, close the transport
// For TCP there is no transport to close (as RTP packets were interleaved into the RTSP connection)
if (connection.video_udp_pair != null) {
connection.video_udp_pair.Stop();
connection.video_udp_pair = null;
}
rtsp_list.Remove(connection);
// Close the RTSP socket
listener.Dispose();
}
}
}
}
}
// The 'Camera' (YUV TestCard) has generated a YUV image.
// If there are RTSP clients connected then Compress the Video Frame (with H264) and send it to the client
void video_source_ReceivedYUVFrame(uint timestamp_ms, int width, int height, byte[] yuv_data)
{
DateTime now = DateTime.UtcNow;
int current_rtp_play_count = 0;
int current_rtp_count = 0;
int timeout_in_seconds = 70; // must have a RTSP message every 70 seconds or we will close the connection
lock (rtsp_list) {
current_rtp_count = rtsp_list.Count;
foreach (RTSPConnection connection in rtsp_list.ToArray()) { // Convert to Array to allow us to delete from rtsp_list
// RTSP Timeout (clients receiving RTP video over the RTSP session
// do not need to send a keepalive (so we check for Socket write errors)
Boolean sending_rtp_via_tcp = false;
if ((connection.video_client_transport != null) &&
(connection.video_client_transport.LowerTransport == RtspTransport.LowerTransportType.TCP))
{
sending_rtp_via_tcp = true;
}
if (sending_rtp_via_tcp == false && ((now - connection.time_since_last_rtsp_keepalive).TotalSeconds > timeout_in_seconds)) {
Console.WriteLine("Removing session " + connection.video_session_id + " due to TIMEOUT");
connection.play = false; // stop sending data
if (connection.video_udp_pair != null)
{
connection.video_udp_pair.Stop();
connection.video_udp_pair = null;
}
connection.listener.Dispose();
rtsp_list.Remove(connection);
continue;
}
else if (connection.play) current_rtp_play_count++;
}
}
// Take the YUV image and encode it into a H264 NAL
// This returns a NAL with no headers (no 00 00 00 01 header and no 32 bit sizes)
Console.WriteLine(current_rtp_count + " RTSP clients connected. " + current_rtp_play_count + " RTSP clients in PLAY mode");
if (current_rtp_play_count == 0) return;
// Compress the video (YUV to H264)
byte[] raw_video_nal = h264_encoder.CompressFrame(yuv_data);
Boolean isKeyframe = true; // SimpleH264encoder and TinyH24encoder only emit keyframes
List<byte[]> nal_array = new List<byte[]>();
// We may want to add the SPS and PPS to the H264 stream as in-band data.
// This may be of use if the client did not parse the SPS/PPS in the SDP
// or if the H264 encoder changes properties (eg a new resolution or framerate which
// gives a new SPS or PPS).
// Also looking towards H265, the VPS/SPS/PPS do not need to be in the SDP so would be added here.
Boolean add_sps_pps_to_keyframe = true;
if (add_sps_pps_to_keyframe && isKeyframe) {
nal_array.Add(raw_sps);
nal_array.Add(raw_pps);
}
// add the rest of the NALs
nal_array.Add(raw_video_nal);
UInt32 rtp_timestamp = timestamp_ms * 90; // 90kHz clock
// Build a list of 1 or more RTP packets
// The last packet will have the M bit set to '1'
List<byte[]> rtp_packets = new List<byte[]>();
for(int x = 0; x < nal_array.Count; x++) {
byte[] raw_nal = nal_array[x];
Boolean last_nal = false;
if (x == nal_array.Count - 1) {
last_nal = true; // last NAL in our nal_array
}
// The H264 Payload could be sent as one large RTP packet (assuming the receiver can handle it)
// or as a Fragmented Data, split over several RTP packets with the same Timestamp.
bool fragmenting = false;
int packetMTU = 65500;
if (raw_nal.Length > packetMTU) fragmenting = true;
if (fragmenting == false)
{
// Put the whole NAL into one RTP packet.
// Note some receivers will have maximum buffers and be unable to handle large RTP packets.
// Also with RTP over RTSP there is a limit of 65535 bytes for the RTP packet.
byte[] rtp_packet = new byte[12 + raw_nal.Length]; // 12 is header size when there are no CSRCs or extensions
// Create an single RTP fragment
// RTP Packet Header
// 0 - Version, P, X, CC, M, PT and Sequence Number
//32 - Timestamp. H264 uses a 90kHz clock
//64 - SSRC
//96 - CSRCs (optional)
//nn - Extension ID and Length
//nn - Extension header
int rtp_version = 2;
int rtp_padding = 0;
int rtp_extension = 0;
int rtp_csrc_count = 0;
int rtp_marker = (last_nal == true ? 1 : 0); // set to 1 if the last NAL in the array
int rtp_payload_type = 96;
RTPPacketUtil.WriteHeader(rtp_packet, rtp_version, rtp_padding, rtp_extension, rtp_csrc_count, rtp_marker, rtp_payload_type);
UInt32 empty_sequence_id = 0;
RTPPacketUtil.WriteSequenceNumber(rtp_packet, empty_sequence_id);
RTPPacketUtil.WriteTS(rtp_packet, rtp_timestamp);
UInt32 empty_ssrc = 0;
RTPPacketUtil.WriteSSRC(rtp_packet, empty_ssrc);
// Now append the raw NAL
System.Array.Copy(raw_nal, 0, rtp_packet, 12, raw_nal.Length);
rtp_packets.Add(rtp_packet);
}
else
{
int data_remaining = raw_nal.Length;
int nal_pointer = 0;
int start_bit = 1;
int end_bit = 0;
// consume first byte of the raw_nal. It is used in the FU header
byte first_byte = raw_nal[0];
nal_pointer++;
data_remaining--;
while (data_remaining > 0)
{
int payload_size = Math.Min(packetMTU, data_remaining);
if (data_remaining - payload_size == 0) end_bit = 1;
byte[] rtp_packet = new byte[12 + 2 + payload_size]; // 12 is header size. 2 bytes for FU-A header. Then payload
// RTP Packet Header
// 0 - Version, P, X, CC, M, PT and Sequence Number
//32 - Timestamp. H264 uses a 90kHz clock
//64 - SSRC
//96 - CSRCs (optional)
//nn - Extension ID and Length
//nn - Extension header
int rtp_version = 2;
int rtp_padding = 0;
int rtp_extension = 0;
int rtp_csrc_count = 0;
int rtp_marker = (last_nal == true ? 1 : 0); // Marker set to 1 on last packet
int rtp_payload_type = 96;
RTPPacketUtil.WriteHeader(rtp_packet, rtp_version, rtp_padding, rtp_extension, rtp_csrc_count, rtp_marker, rtp_payload_type);
UInt32 empty_sequence_id = 0;
RTPPacketUtil.WriteSequenceNumber(rtp_packet, empty_sequence_id);
RTPPacketUtil.WriteTS(rtp_packet, rtp_timestamp);
UInt32 empty_ssrc = 0;
RTPPacketUtil.WriteSSRC(rtp_packet, empty_ssrc);
// Now append the Fragmentation Header (with Start and End marker) and part of the raw_nal
byte f_bit = 0;
byte nri = (byte)((first_byte >> 5) & 0x03); // Part of the 1st byte of the Raw NAL (NAL Reference ID)
byte type = 28; // FU-A Fragmentation
rtp_packet[12] = (byte)((f_bit << 7) + (nri << 5) + type);
rtp_packet[13] = (byte)((start_bit << 7) + (end_bit << 6) + (0 << 5) + (first_byte & 0x1F));
System.Array.Copy(raw_nal, nal_pointer, rtp_packet, 14, payload_size);
nal_pointer = nal_pointer + payload_size;
data_remaining = data_remaining - payload_size;
rtp_packets.Add(rtp_packet);
start_bit = 0;
}
}
}
lock (rtsp_list)
{
// Go through each RTSP connection and output the NAL on the Video Session
foreach (RTSPConnection connection in rtsp_list.ToArray()) // ToArray makes a temp copy of the list.
// This lets us delete items in the foreach
// eg when there is Write Error
{
// Only process Sessions in Play Mode
if (connection.play == false) continue;
String connection_type = "";
if (connection.video_client_transport.LowerTransport == RtspTransport.LowerTransportType.TCP) connection_type = "TCP";
if (connection.video_client_transport.LowerTransport == RtspTransport.LowerTransportType.UDP
&& connection.video_client_transport.IsMulticast == false) connection_type = "UDP";
if (connection.video_client_transport.LowerTransport == RtspTransport.LowerTransportType.UDP
&& connection.video_client_transport.IsMulticast == true) connection_type = "Multicast";
Console.WriteLine("Sending video session " + connection.video_session_id + " " + connection_type + " Timestamp(ms)=" + timestamp_ms + ". RTP timestamp=" + rtp_timestamp + ". Sequence="+ connection.video_sequence_number);
// There could be more than 1 RTP packet (if the data is fragmented)
Boolean write_error = false;
foreach (byte[] rtp_packet in rtp_packets)
{
// Add the specific data for each transmission
RTPPacketUtil.WriteSequenceNumber(rtp_packet, connection.video_sequence_number);
connection.video_sequence_number++;
// Add the specific SSRC for each transmission
RTPPacketUtil.WriteSSRC(rtp_packet, connection.ssrc);
// Send as RTP over RTSP (Interleaved)
if (connection.video_transport_reply.LowerTransport == RtspTransport.LowerTransportType.TCP)
{
int video_channel = connection.video_transport_reply.Interleaved.First; // second is for RTCP status messages)
object state = new object();
try
{
// send the whole NAL. With RTP over RTSP we do not need to Fragment the NAL (as we do with UDP packets or Multicast)
//session.listener.BeginSendData(video_channel, rtp_packet, new AsyncCallback(session.listener.EndSendData), state);
connection.listener.SendData(video_channel, rtp_packet);
}
catch
{
Console.WriteLine("Error writing to listener " + connection.listener.RemoteAdress);
write_error = true;
break; // exit out of foreach loop
}
}
// Send as RTP over UDP
if (connection.video_transport_reply.LowerTransport == RtspTransport.LowerTransportType.UDP && connection.video_transport_reply.IsMulticast == false)
{
try
{
// send the whole NAL. ** We could fragment the RTP packet into smaller chuncks that fit within the MTU
// Send to the IP address of the Client
// Send to the UDP Port the Client gave us in the SETUP command
connection.video_udp_pair.Write_To_Data_Port(rtp_packet,connection.client_hostname,connection.video_client_transport.ClientPort.First);
}
catch (Exception e)
{
Console.WriteLine("UDP Write Exception " + e.ToString());
Console.WriteLine("Error writing to listener " + connection.listener.RemoteAdress);
write_error = true;
break; // exit out of foreach loop
}
}
// TODO. Add Multicast
}
if (write_error)
{
Console.WriteLine("Removing session " + connection.video_session_id + " due to write error");
connection.play = false; // stop sending data
if (connection.video_udp_pair != null) {
connection.video_udp_pair.Stop();
connection.video_udp_pair = null;
}
connection.listener.Dispose();
rtsp_list.Remove(connection); // remove the session. It is dead
}
}
}
}
public class RTSPConnection
{
public RtspListener listener = null; // The RTSP client connection
public bool play = false; // set to true when Session is in Play mode
public DateTime time_since_last_rtsp_keepalive = DateTime.UtcNow; // Time since last RTSP message received - used to spot dead UDP clients
public UInt32 ssrc = 0x12345678; // SSRC value used with this client connection
public String client_hostname = ""; // Client Hostname/IP Address
public String video_session_id = ""; // RTSP Session ID used with this client connection
public UInt16 video_sequence_number = 1; // 16 bit RTP packet sequence number used with this client connection
public RtspTransport video_client_transport; // Transport: string from the client to the server
public RtspTransport video_transport_reply; // Transport: reply from the server to the client
public UDPSocket video_udp_pair = null; // Pair of UDP sockets (data and control) used when sending via UDP
public DateTime video_time_since_last_rtcp_keepalive = DateTime.UtcNow; // Time since last RTCP message received - used to spot dead UDP clients
// TODO - Add Audio
}
}
}

View File

@@ -0,0 +1,109 @@
using System;
using System.Collections.Generic;
// Simple H264 Encoder
// Written by Jordi Cenzano (www.jordicenzano.name)
//
// Ported to C# by Roger Hardiman www.rjh.org.uk
// This is a very simple lossless H264 encoder. No compression is used and so the output NAL data is as
// large as the input YUV data.
// It is used for a quick example of H264 encoding in pure .Net without needing OS specific APIs
// or cross compiled C libraries.
//
// SimpleH264Encoder can use any image Width or Height
namespace Inspectron.HawkEye.RTSP.Server
{
public class SimpleH264Encoder
{
CJOCh264encoder h264encoder = null;
uint width = 0;
uint height = 0;
List<byte> nal = new List<byte>();
// Constuctor
public SimpleH264Encoder(uint width, uint height, uint fps)
{
// We have the ability to set the aspect ratio (SAR).
// For now we set to 1:1
uint SARw = 1;
uint SARh = 1;
// Initialise H264 encoder. The original C++ code writes to a file. In this port it writes to a List<byte>
h264encoder = new CJOCh264encoder(nal);
h264encoder.IniCoder(width, height, fps, CJOCh264encoder.enSampleFormat.SAMPLE_FORMAT_YUV420p, SARw, SARh);
this.width = width;
this.height = height;
// NAL array will contain SPS and PPS
}
// Raw SPS with no Size Header and no 00 00 00 01 headers
public byte[] GetRawSPS()
{
byte[] sps_with_header = h264encoder.sps;
byte[] sps = new byte[sps_with_header.Length - 4];
System.Array.Copy(sps_with_header, 4, sps, 0, sps.Length);
return sps;
}
public byte[] GetRawPPS()
{
byte[] pps_with_header = h264encoder.pps;
byte[] pps = new byte[pps_with_header.Length - 4];
System.Array.Copy(pps_with_header, 4, pps, 0, pps.Length);
return pps;
}
public byte[] CompressFrame(byte[] yuv_data)
{
byte[] image = h264encoder.GetFramePtr();
// copy over the YUV image
System.Array.Copy(yuv_data, image, image.Length);
// // HACK. Set the YUV pixels all to 127
// for (int hack = 0; hack < image.Length; hack++) image[hack] = 127;
h264encoder.CodeAndSaveFrame();
// Get the NAL (which has the 00 00 00 01 header)
byte[] nal_with_header = h264encoder.nal;
byte[] nal = new byte[nal_with_header.Length - 4];
System.Array.Copy(nal_with_header, 4, nal, 0, nal.Length);
return nal;
}
public void ChangeAnnexBto32BitSize(byte[] data)
{
if (data.Length < 4) return;
// change data from 0x00 0x00 0x00 0x01 format to 32 bit size
int len = data.Length - 4;// subtract Annex B header size
if (BitConverter.IsLittleEndian)
{
data[0] = (byte)((len >> 24) & 0xFF);
data[1] = (byte)((len >> 16) & 0xFF);
data[2] = (byte)((len >> 8) & 0xFF);
data[3] = (byte)((len << 0) & 0xFF);
}
else
{
data[0] = (byte)((len >> 0) & 0xFF);
data[1] = (byte)((len >> 8) & 0xFF);
data[2] = (byte)((len >> 16) & 0xFF);
data[3] = (byte)((len >> 24) & 0xFF);
}
}
}
}

View File

@@ -0,0 +1,209 @@
using System;
using System.Diagnostics;
using System.Globalization;
// (c) Roger Hardiman 2016
// This class uses a System Timer to generate a YUV image at regular intervals
// The ReceivedYUVFrame event is fired for each new YUV image
namespace Inspectron.HawkEye.RTSP.Server
{
public class TestCard
{
// Events that applications can receive
public event ReceivedYUVFrameHandler ReceivedYUVFrame;
// Delegated functions (essentially the function prototype)
public delegate void ReceivedYUVFrameHandler(uint timestamp, int width, int height, byte[] data);
// Local variables
private System.Timers.Timer frame_timer;
private int fps = 0;
private Stopwatch stopwatch;
private byte[] yuv_frame = null;
private int x_position = 0;
private int y_position = 0;
private int width = 0;
private int height = 0;
private Object generate_lock = new Object();
private long count = 0;
// ASCII Font
// Created by Roger Hardiman using an online generation tool
// http://www.riyas.org/2013/12/online-led-matrix-font-generator-with.html
byte[] ascii_0 = { 0x00, 0x3c, 0x42, 0x42, 0x42, 0x42, 0x42, 0x3c };
byte[] ascii_1 = { 0x00, 0x08, 0x18, 0x28, 0x08, 0x08, 0x08, 0x3e };
byte[] ascii_2 = { 0x00, 0x3e, 0x42, 0x02, 0x0c, 0x30, 0x40, 0x7e };
byte[] ascii_3 = { 0x00, 0x7c, 0x02, 0x02, 0x3c, 0x02, 0x02, 0x7c };
byte[] ascii_4 = { 0x00, 0x0c, 0x14, 0x24, 0x44, 0x7e, 0x04, 0x04 };
byte[] ascii_5 = { 0x00, 0x7e, 0x40, 0x40, 0x7c, 0x02, 0x02, 0x7c };
byte[] ascii_6 = { 0x00, 0x3e, 0x40, 0x40, 0x7c, 0x42, 0x42, 0x3c };
byte[] ascii_7 = { 0x00, 0x7e, 0x02, 0x02, 0x04, 0x08, 0x10, 0x20 };
byte[] ascii_8 = { 0x00, 0x3c, 0x42, 0x42, 0x3c, 0x42, 0x42, 0x3c };
byte[] ascii_9 = { 0x00, 0x3c, 0x42, 0x42, 0x3c, 0x02, 0x02, 0x3e };
byte[] ascii_colon = { 0x00, 0x00, 0x18, 0x18, 0x00, 0x18, 0x18, 0x00 };
byte[] ascii_space = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
byte[] ascii_dot = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x18, 0x00 };
// Constructor
public TestCard(int width, int height, int fps)
{
this.width = width;
this.height = height;
this.fps = fps;
// YUV size
int y_size = width * height;
int u_size = (width >> 1) * (height >> 1);
int v_size = (width >> 1) * (height >> 1);
yuv_frame = new byte[y_size + u_size + v_size];
// Set all values to 127
for (int x = 0; x < yuv_frame.Length; x++)
{
yuv_frame[x] = 127;
}
stopwatch = new Stopwatch();
stopwatch.Start();
// Start timer. The Timer will generate each YUV frame
frame_timer = new System.Timers.Timer();
frame_timer.Interval = 1; // on first pass timer will fire straight away (cannot have zero interval)
frame_timer.AutoReset = false; // do not restart timer after the time has elapsed
frame_timer.Elapsed += (object sender, System.Timers.ElapsedEventArgs e) =>
{
// send a frame
Send_YUV_Frame();
count++;
// Some CPU cycles will have been used in Sending the YUV Frame.
// Compute the delay required (the Timer Interval) before sending the next YUV frame
long time_for_next_tick_ms = (count * 1000) / fps;
long time_to_wait = time_for_next_tick_ms - stopwatch.ElapsedMilliseconds;
if (time_to_wait <= 0) time_to_wait = 1; // cannot have negative or zero intervals
frame_timer.Interval = time_to_wait;
frame_timer.Start();
};
frame_timer.Start();
}
// Dispose
public void Disconnect()
{
// Stop the frame timer
frame_timer.Stop();
frame_timer.Dispose();
}
private void Send_YUV_Frame()
{
lock (generate_lock)
{
// Get the current time
DateTime now_utc = DateTime.UtcNow;
DateTime now_local = now_utc.ToLocalTime();
long timestamp_ms = ((long)(now_utc.Ticks / TimeSpan.TicksPerMillisecond));
// Generate the String to write
char[] overlay = null;
if (width >= 96)
{
// Need 12 characters of 8x8 pixels. 12*8 = 96
// HH:MM:SS.mmm
String overlay_str = now_local.ToString("HH:mm:ss.fff", CultureInfo.InvariantCulture); // do not replace : or . by local formats
overlay = overlay_str.ToCharArray();
}
else
{
// Min for most video formats is 16x16, enough for 2 characters
String overlay_str = now_local.ToString("ss", CultureInfo.InvariantCulture); // do not replace : or . by local formats
overlay = overlay_str.ToCharArray();
}
// process each character
int start_row = ((height / 2) - 4); // start 4 pixels above the centre row (4 is half the font height)
for (int c = 0; c < overlay.Length; c++)
{
byte[] font = ascii_space;
if (overlay[c] == '0') font = ascii_0;
if (overlay[c] == '1') font = ascii_1;
if (overlay[c] == '2') font = ascii_2;
if (overlay[c] == '3') font = ascii_3;
if (overlay[c] == '4') font = ascii_4;
if (overlay[c] == '5') font = ascii_5;
if (overlay[c] == '6') font = ascii_6;
if (overlay[c] == '7') font = ascii_7;
if (overlay[c] == '8') font = ascii_8;
if (overlay[c] == '9') font = ascii_9;
if (overlay[c] == ' ') font = ascii_space;
if (overlay[c] == ':') font = ascii_colon;
if (overlay[c] == '.') font = ascii_dot;
// process the font character
for (int rows = 0; rows < 8; rows++)
{
int y_plane_pos = (start_row * width) + (rows * width) + (c * 8);
byte row_byte = font[rows];
// bit shift the row byte into individual pixels where the font On/Off maps to Y intensity 50 or 200
for (int bits = 0; bits < 8; bits++)
{
if ((row_byte & 0x80) == 0x80)
{
// Pixel On
yuv_frame[y_plane_pos] = 200;
}
else
{
yuv_frame[y_plane_pos] = 50;
}
y_plane_pos++;
row_byte = (byte)(row_byte << 1); // shift up so the next 'bit' to process is the most significant bit
}
}
}
// Toggle the pixel value
byte pixel_value = yuv_frame[(y_position * width) + x_position];
// change brightness of pixel
if (pixel_value > 128) pixel_value = 30;
else pixel_value = 230;
yuv_frame[(y_position * width) + x_position] = pixel_value;
// move the x and y position
x_position = x_position + 5;
if (x_position >= width)
{
x_position = 0;
y_position = y_position + 1;
}
if (y_position >= height)
{
y_position = 0;
}
// fire the Event
if (ReceivedYUVFrame != null)
{
ReceivedYUVFrame((uint)stopwatch.ElapsedMilliseconds, width, height, yuv_frame);
}
}
}
}
}

View File

@@ -0,0 +1,166 @@
using System;
using System.Collections.Generic;
using System.Linq;
// Tiny H264 Encoder
// World's Smallest h.264 Encoder, by Ben Mesander.
// https://cardinalpeak.com/blog/worlds-smallest-h-264-encoder/
//
// Ported to C# by Roger Hardiman www.rjh.org.uk
// Input: YUV image that must be 128x96
// Output: H264 NAL
//
// This is a very simple lossless H264 encoder. No compression is used and so the output NAL data is as
// large as the input YUV data.
// It is used for a quick example of H264 encoding in pure .Net without needing OS specific APIs
// or cross compiled C libraries.
//
// The H264 SPS/PPS data includes the image size. As the SPS/PPS is hard coded in this example the YUV
// image size must be 128 x 96
namespace Inspectron.HawkEye.RTSP.Server
{
public class TinyH264Encoder
{
int width = 0;
int height = 0;
int uv_width = 0;
int uv_height = 0;
int y_size = 0;
int u_size = 0;
int v_size = 0;
byte[] sps = { 0x67, 0x42, 0x00, 0x0a, 0xf8, 0x41, 0xa2 };
//byte[] sps_b = { 0x00, 0x00, 0x00, 0x01, 0x67, 0x42, 0x00, 0x0a, 0xf8, 0x41, 0xa2 }; // Annex B
//byte[] sps32 = { 0x00, 0x00, 0x00, 0x07, 0x67, 0x42, 0x00, 0x0a, 0xf8, 0x41, 0xa2 }; // 32 bit size
byte[] pps = { 0x68, 0xce, 0x38, 0x80 };
//byte[] pps_b = { 0x00, 0x00, 0x00, 0x01, 0x68, 0xce, 0x38, 0x80 }; // Annex B
//byte[] pps32 = { 0x00, 0x00, 0x00, 0x04, 0x68, 0xce, 0x38, 0x80 }; // 32 bit size
byte[] slice_header = { 0x05, 0x88, 0x84, 0x21, 0xa0 };
//byte[] slice_header_b = { 0x00, 0x00, 0x00, 0x01, 0x05, 0x88, 0x84, 0x21, 0xa0 };
//byte[] slice_header_32 = { 0x00, 0x00, 0x00, 0x00, 0x05, 0x88, 0x84, 0x21, 0xa0 }; // must replace size bytes
byte[] slice_end = { 0x80 };
byte[] macroblock_header = { 0x0d, 0x00 };
List<byte> nal = new List<byte>();
// Constuctor
public TinyH264Encoder()
{
this.width = 128; // Hard coded size that is embedded in the SPS/PPS data
this.height = 96; // Hard coded size that is embedded in the SPS/PPS data
this.uv_width = width >> 1;
this.uv_height = height >> 1;
this.y_size = width * height;
this.u_size = (width >> 1) * (height >> 1);
this.v_size = (width >> 1) * (height >> 1);
}
public byte[] GetRawSPS()
{
return sps.ToArray();
}
public byte[] GetRawPPS()
{
return pps.ToArray();
}
public byte[] CompressFrame(byte[] yuv_data)
{
// we can only do 128 x 96
if (width != 128) return null;
if (height != 96) return null;
// check size
if (yuv_data.Length < (y_size + u_size + v_size))
{
// the yuv image is too small.
return null;
}
nal.Clear();
// Slice Header
foreach (byte b in slice_header) nal.Add(b);
// Add each macro block
for (int i = 0; i < (height / 16); i++) {
for (int j = 0; j < (width / 16); j++) {
macroblock(i, j, yuv_data);
}
}
// Add slice end
foreach (byte b in slice_end) nal.Add(b);
byte[] nal_array = nal.ToArray();
return nal_array;
}
/* Write a macroblock's worth of YUV data in I_PCM mode */
private void macroblock(int i, int j, byte[] frame)
{
int x, y;
if (!((i == 0) && (j == 0)))
{
foreach (byte b in macroblock_header) nal.Add(b);
}
for (x = i * 16; x < ((i + 1) * 16); x++)
{
for (y = j * 16; y < ((j + 1) * 16); y++)
{
nal.Add(frame[(x * width) + y]);
}
}
for (x = i * 8; x < (i + 1) * 8; x++)
{
for (y = j * 8; y < (j + 1) * 8; y++)
{
nal.Add(frame[y_size + (x * uv_width) + y]);
}
}
for (x = i * 8; x < (i + 1) * 8; x++)
{
for (y = j * 8; y < (j + 1) * 8; y++)
{
nal.Add(frame[y_size + u_size + (x * uv_width) + y]);
}
}
}
public void ChangeAnnexBto32BitSize(byte[] data)
{
if (data.Length < 4) return;
// change data from 0x00 0x00 0x00 0x01 format to 32 bit size
int len = data.Length - 4;// subtract Annex B header size
if (BitConverter.IsLittleEndian)
{
data[0] = (byte)((len >> 24) & 0xFF);
data[1] = (byte)((len >> 16) & 0xFF);
data[2] = (byte)((len >> 8) & 0xFF);
data[3] = (byte)((len << 0) & 0xFF);
}
else
{
data[0] = (byte)((len >> 0) & 0xFF);
data[1] = (byte)((len >> 8) & 0xFF);
data[2] = (byte)((len >> 16) & 0xFF);
data[3] = (byte)((len >> 24) & 0xFF);
}
}
}
}

View File

@@ -0,0 +1,231 @@
using System;
using System.Net;
using System.Net.Sockets;
using System.Threading;
using Inspectron.HawkEye.RTSP.Messages;
namespace Inspectron.HawkEye.RTSP
{
public class UDPSocket
{
private UdpClient data_socket = null;
private UdpClient control_socket = null;
private Thread data_read_thread = null;
private Thread control_read_thread = null;
public int data_port = 50000;
public int control_port = 50001;
bool is_multicast = false;
IPAddress data_mcast_addr;
IPAddress control_mcast_addr;
/// <summary>
/// Initializes a new instance of the <see cref="UDPSocket"/> class.
/// Creates two new UDP sockets using the start and end Port range
/// </summary>
public UDPSocket(int start_port, int end_port)
{
is_multicast = false;
// open a pair of UDP sockets - one for data (video or audio) and one for the status channel (RTCP messages)
data_port = start_port;
control_port = start_port + 1;
bool ok = false;
while (ok == false && (control_port < end_port))
{
// Video/Audio port must be odd and command even (next one)
try
{
data_socket = new UdpClient(data_port);
control_socket = new UdpClient(control_port);
ok = true;
}
catch (SocketException)
{
// Fail to allocate port, try again
if (data_socket != null)
data_socket.Close();
if (control_socket != null)
control_socket.Close();
// try next data or control port
data_port += 2;
control_port += 2;
}
if (ok)
{
data_socket.Client.ReceiveBufferSize = 100 * 1024;
data_socket.Client.SendBufferSize = 65535; // default is 8192. Make it as large as possible for large RTP packets which are not fragmented
control_socket.Client.DontFragment = false;
}
}
}
/// <summary>
/// Initializes a new instance of the <see cref="UDPSocket"/> class.
/// Used with Multicast mode with the Multicast Address and Port
/// </summary>
public UDPSocket(String data_multicast_address, int data_multicast_port, String control_multicast_address, int control_multicast_port)
{
is_multicast = true;
// open a pair of UDP sockets - one for data (video or audio) and one for the status channel (RTCP messages)
this.data_port = data_multicast_port;
this.control_port = control_multicast_port;
try
{
IPEndPoint data_ep = new IPEndPoint(IPAddress.Any, data_port);
IPEndPoint control_ep = new IPEndPoint(IPAddress.Any, control_port);
data_mcast_addr = IPAddress.Parse(data_multicast_address);
control_mcast_addr = IPAddress.Parse(control_multicast_address);
data_socket = new UdpClient();
data_socket.Client.Bind(data_ep);
data_socket.JoinMulticastGroup(data_mcast_addr);
control_socket = new UdpClient();
control_socket.Client.Bind(control_ep);
control_socket.JoinMulticastGroup(control_mcast_addr);
data_socket.Client.ReceiveBufferSize = 100 * 1024;
data_socket.Client.SendBufferSize = 65535; // default is 8192. Make it as large as possible for large RTP packets which are not fragmented
control_socket.Client.DontFragment = false;
}
catch (SocketException)
{
// Fail to allocate port, try again
if (data_socket != null)
data_socket.Close();
if (control_socket != null)
control_socket.Close();
return;
}
}
/// <summary>
/// Starts this instance.
/// </summary>
public void Start()
{
if (data_socket == null || control_socket == null)
{
throw new InvalidOperationException("UDP Forwader host was not initialized, can't continue");
}
if (data_read_thread != null)
{
throw new InvalidOperationException("Forwarder was stopped, can't restart it");
}
data_read_thread = new Thread(() => DoWorkerJob(data_socket, data_port));
data_read_thread.Name = "DataPort " + data_port;
data_read_thread.Start();
control_read_thread = new Thread(() => DoWorkerJob(control_socket, control_port));
control_read_thread.Name = "ControlPort " + control_port;
control_read_thread.Start();
}
/// <summary>
/// Stops this instance.
/// </summary>
public void Stop()
{
if (is_multicast)
{
// leave the multicast groups
data_socket.DropMulticastGroup(data_mcast_addr);
control_socket.DropMulticastGroup(control_mcast_addr);
}
data_socket.Close();
control_socket.Close();
}
/// <summary>
/// Occurs when message is received.
/// </summary>
public event EventHandler<RtspChunkEventArgs> DataReceived;
/// <summary>
/// Raises the <see cref="E:DataReceived"/> event.
/// </summary>
/// <param name="rtspChunkEventArgs">The <see cref="Rtsp.RtspChunkEventArgs"/> instance containing the event data.</param>
protected void OnDataReceived(RtspChunkEventArgs rtspChunkEventArgs)
{
EventHandler<RtspChunkEventArgs> handler = DataReceived;
if (handler != null)
handler(this, rtspChunkEventArgs);
}
/// <summary>
/// Does the video job.
/// </summary>
private void DoWorkerJob(System.Net.Sockets.UdpClient socket, int data_port)
{
IPEndPoint ipEndPoint = new IPEndPoint(IPAddress.Any, data_port);
try
{
// loop until we get an exception eg the socket closed
while (true)
{
byte[] frame = socket.Receive(ref ipEndPoint);
// We have an RTP frame.
// Fire the DataReceived event with 'frame'
Console.WriteLine("Received RTP data on port " + data_port);
RtspChunk currentMessage = new RtspData();
// aMessage.SourcePort = ??
currentMessage.Data = frame;
((RtspData)currentMessage).Channel = data_port;
OnDataReceived(new RtspChunkEventArgs(currentMessage));
}
}
catch (ObjectDisposedException)
{
}
catch (SocketException)
{
}
}
/// <summary>
/// Write to the RTP Data Port
/// </summary>
public void Write_To_Data_Port(byte[] data, String hostname, int port) {
data_socket.Send(data,data.Length, hostname, port);
}
/// <summary>
/// Write to the RTP Control Port
/// </summary>
public void Write_To_Control_Port(byte[] data, String hostname, int port)
{
data_socket.Send(data, data.Length, hostname, port);
}
}
}

View File

@@ -0,0 +1,106 @@
using System;
using System.Collections;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading.Tasks;
namespace Inspectron.HawkEye
{
public class TCPSocket:IDisposable
{
private Socket _socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
private const int bufSize = 8 * 1024;
private State state = new State();
private EndPoint epFrom = new IPEndPoint(IPAddress.Any, 0);
private AsyncCallback recv = null;
public class State
{
public byte[] buffer = new byte[bufSize];
}
public void Server(string address, int port)
{
_socket.SetSocketOption(SocketOptionLevel.IP, SocketOptionName.ReuseAddress, true);
_socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReceiveBuffer, 10*1024*1024);
_socket.Bind(new IPEndPoint(IPAddress.Parse(address), port));
ReceiveAsync();
}
public void Client(string address, int port)
{
_socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.SendBuffer, 10 * 1024 * 1024);
_socket.Connect(IPAddress.Parse(address), port);
ReceiveAsync();
}
public void Send(byte[] data)
{
//_socket.BeginSend(data, 0, data.Length, SocketFlags.None, (ar) =>
//{
// State so = (State)ar.AsyncState;
// int bytes = _socket.EndSend(ar);
//}, state);
for (int i = 0; i < Math.Ceiling(data.Length/1050.0); i++)
{
var size = Math.Min(1050, data.Length - i * 1050);
_socket.Send(data,i* 1050, size,SocketFlags.None);
}
}
public event Action<byte[]> Received = delegate { };
ConcurrentQueue<byte[]> _receivePool = new ConcurrentQueue<byte[]>();
private void ReceiveAsync()
{
for (int i = 0; i < 1000; i++)
{
_receivePool.Enqueue(new byte[1050]);
}
_receivePool.TryDequeue(out var buffer);
_socket.BeginReceiveFrom(buffer, 0, buffer.Length, SocketFlags.None, ref epFrom, recv = (ar) =>
{
byte[] so = (byte[])ar.AsyncState;
Task.Run(() =>
{
Received(so);
_receivePool.Enqueue(so);
}
);
int bytes = _socket.EndReceiveFrom(ar, ref epFrom);
_receivePool.TryDequeue(out var bufferLoc);
_socket.BeginReceiveFrom(bufferLoc, 0, so.Length, SocketFlags.None, ref epFrom, recv, bufferLoc);
}, buffer);
}
byte[] _receiveBuffer = new byte[1024*1024];
public byte[] Receive()
{
int received = 0;
while (received<1024*1024)
{
received+=_socket.Receive(_receiveBuffer);
}
return _receiveBuffer;
}
public void Dispose()
{
_socket?.Dispose();
}
}
}

View File

@@ -0,0 +1,9 @@
namespace Inspectron.HawkEye.UDPB
{
public class Block
{
public byte[] Data;
public uint Length;
public uint MessageNumber;
}
}

View File

@@ -0,0 +1,91 @@
using System;
using System.IO;
namespace Inspectron.HawkEye.UDPB
{
public class UDPBPacket
{
public enum EPacketType:uint
{
Data=0,
CloseSequence=1,
BeginSequence=2,
Nak=3,
Ok=4
}
public UDPBPacket()
{
}
private const int sequenceIndex = 0;
private const int messageIndex = 1;
private const int typeIndex = 2;
private const int datalengthIndex = 3;
public const int packetHeaderSize = 16;
private uint[] _header=new uint[4];
public uint SequenceId
{
get => _header[sequenceIndex];
set => _header[sequenceIndex] = value;
}
public uint MessageId
{
get => _header[messageIndex];
set => _header[messageIndex] = value;
}
public EPacketType Type
{
get => (EPacketType)_header[typeIndex];
set => _header[typeIndex] = (uint)value;
}
private byte[] _payload;
public byte[] Payload
{
get => _payload??new byte[0];
set
{
_payload = value;
}
}
public uint Length
{
get => _header[datalengthIndex];
set => _header[datalengthIndex] = value;
}
public byte[] Serialize()
{
byte[] bytes = new byte[UDPBQueue.PACKET_SIZE+packetHeaderSize];
Buffer.BlockCopy(_header, 0, bytes, 0, packetHeaderSize);
if(_payload!=null)
Array.Copy(_payload, 0, bytes, packetHeaderSize, _payload.Length);
return bytes;
}
public void Deserialize(byte[] data,int offset,int len)
{
Buffer.BlockCopy(data, offset, _header, 0, packetHeaderSize);
var dataLen = _header[datalengthIndex];
if (dataLen > 0)
{
_payload = new byte[dataLen];
Array.Copy(data, packetHeaderSize+ offset, _payload, 0, (int) dataLen);
}
}
}
}

View File

@@ -0,0 +1,77 @@
using System;
using System.Collections.Generic;
namespace Inspectron.HawkEye.UDPB
{
public class UDPBQueue
{
public const int PACKET_SIZE = 1400;
private const int QUEUE_SIZE = 20000;
Block[] _dataQueue = new Block[QUEUE_SIZE];
private int _queueWritePtr = 0;
private uint _messageId = 1;
private int _queueReadPtr = 0;
public UDPBQueue()
{
for (int i = 0; i < QUEUE_SIZE; i++)
{
_dataQueue[i]=new Block();
_dataQueue[i].Data=new byte[PACKET_SIZE];
}
}
public void Reset()
{
_messageId = 1;
}
public void AddBuffer(byte[] data, int offset, int len)
{
int size = len / PACKET_SIZE;
if ((len % PACKET_SIZE) != 0)
size++;
for (int i = 0; i < size; i++)
{
uint pktlen = (uint)(len - i * PACKET_SIZE);
if (pktlen > PACKET_SIZE)
pktlen = PACKET_SIZE;
Array.Copy(data, i * PACKET_SIZE + offset, _dataQueue[_queueWritePtr].Data,0, pktlen);
_dataQueue[_queueWritePtr].Length = pktlen;
_dataQueue[_queueWritePtr].MessageNumber = _messageId;
IncrementWrite();
_messageId++;
if (_messageId == UInt32.MaxValue) _messageId = 1;
}
}
public uint ReadData(ref byte[] data, ref uint msgno)
{
if (_queueReadPtr == _queueWritePtr)
return 0;
data = _dataQueue[_queueReadPtr].Data;
msgno = _dataQueue[_queueReadPtr].MessageNumber;
uint readlen = _dataQueue[_queueReadPtr].Length;
IncrementRead();
return readlen;
}
private void IncrementWrite()
{
_queueWritePtr++;
if (_queueWritePtr == QUEUE_SIZE) _queueWritePtr = 0;
}
private void IncrementRead()
{
_queueReadPtr++;
if (_queueReadPtr == QUEUE_SIZE) _queueReadPtr = 0;
}
public int PacketsToSend()
{
return _queueReadPtr - _queueWritePtr;
}
}
}

View File

@@ -0,0 +1,127 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace Inspectron.HawkEye.UDPB
{
public class UDPBReceiveBuffer
{
private readonly UDPBSocket _socket;
private readonly ConcurrentQueue<byte[]> _receiveQueue;
public UDPBReceiveBuffer(UDPBSocket socket, ConcurrentQueue<byte[]> receiveQueue)
{
_socket = socket;
_receiveQueue = receiveQueue;
}
List<UDPBSequence> _sequences = new List<UDPBSequence>();
public byte[] LastReconstructedBuffer { get; private set; }
public bool IsSequenceFinished()
{
var res = _sequences.OrderByDescending(x=>x.SequenceId).FirstOrDefault(x => x.IsFinished);
if (res != null)
{
LastReconstructedBuffer = res.LastReconstructedBuffer;
_sequences.Remove(res);
return true;
}
return false;
}
public UDPBSequence StartSequence(uint sequence)
{
while (_sequences.Count > 5)
{
_sequences.Remove(_sequences.OrderBy(x => x.SequenceId).First());
}
var exists = _sequences.FirstOrDefault(x => x.SequenceId == sequence);
if (exists != null)
{
Console.WriteLine($"Sequence exists {sequence}");
return exists;
}
var res = new UDPBSequence(sequence, _socket, _receiveQueue);
_sequences.Add(res);
return res;
}
public void AddPacketBytes(byte[] data, int offset, int len)
{
var packet = new UDPBPacket();
packet.Deserialize(data, offset, len);
if (packet.Type == UDPBPacket.EPacketType.Data)
{
var seqId = packet.SequenceId;
var sequence = _sequences.FirstOrDefault(x => x.SequenceId == seqId);
if (sequence != null)
{
sequence.AddData(packet);
}
else
{
var s=StartSequence(seqId);
s.AddData(packet);
}
}else if (packet.Type == UDPBPacket.EPacketType.Ok)
{
var nb = new byte[len];
Array.Copy(data,offset,nb,0,len);
_receiveQueue.Enqueue(nb);
}
else
{
AddCommand(packet);
}
}
private void AddCommand(UDPBPacket commandPacket)
{
switch (commandPacket.Type)
{
case UDPBPacket.EPacketType.CloseSequence:
bool finishSuccess;
int dataSize;
var sequence = _sequences.FirstOrDefault(x => x.SequenceId == commandPacket.SequenceId);
if (sequence == null) return;
finishSuccess = sequence.FinishSequence(commandPacket.MessageId, out var missingPacketIds,
out dataSize);
//inform error
break;
case UDPBPacket.EPacketType.BeginSequence:
StartSequence(commandPacket.SequenceId);
break;
}
}
}
}

View File

@@ -0,0 +1,142 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.IO;
using System.Linq;
namespace Inspectron.HawkEye.UDPB
{
public class UDPBSequence
{
private readonly uint _sequenceId;
private readonly UDPBSocket _socket;
private readonly ConcurrentQueue<byte[]> _receiveQueue;
private readonly byte[] _reconstructionBuffer = new byte[10 * 1024 * 1024]; //10 mb
public bool IsFinished { get; private set; }
private readonly UDPBPacket[] _buffer = new UDPBPacket[10000];
private List<uint> _nakPackets;
public UDPBSequence(uint sequenceId, UDPBSocket socket, ConcurrentQueue<byte[]> receiveQueue)
{
_sequenceId = sequenceId;
_socket = socket;
_receiveQueue = receiveQueue;
}
public uint SequenceId => _sequenceId;
public void AddData(UDPBPacket dataPacket)
{
if (dataPacket.SequenceId != _sequenceId)
{
//error?
Console.WriteLine($"Error! Unexpected packet sequence {dataPacket.SequenceId} expecting {_sequenceId}");
return;
}
_buffer[dataPacket.MessageId] = dataPacket;
if (IsReconstructing)
{
_nakPackets.Remove(dataPacket.MessageId);
Console.WriteLine($"packet {dataPacket.MessageId} restored");
}
}
public byte[] LastReconstructedBuffer { get; private set; }
public bool FinishSequence(uint packets, out List<uint> missingPacketIds,
out int dataSize)
{
missingPacketIds = new List<uint>();
dataSize = 0;
var defaultDataSize = UDPBQueue.PACKET_SIZE - UDPBPacket.packetHeaderSize;
//validate
for (uint i = 1; i < packets; i++)
if (_buffer[i]==null)
{
missingPacketIds.Add(i);
dataSize += defaultDataSize;
//error
}
else
{
var packetLen = _buffer[i].Payload.Length;
Buffer.BlockCopy(_buffer[i].Payload, 0, _reconstructionBuffer, dataSize, packetLen);
dataSize += packetLen;
}
if (missingPacketIds.Count > 0)
{
Console.WriteLine($"Error! Missing packets:{missingPacketIds.Count} seq:{_sequenceId}");
//send Nak
_nakPackets = missingPacketIds;
var nakLine = _nakPackets.Select(x => x.ToString()).Aggregate((s1, s2) => s1 + "," + s2);
Console.WriteLine($"Nak: {nakLine}");
RequestMissingPackets(missingPacketIds);
RequestAgainIn(TimeSpan.FromMilliseconds(10), missingPacketIds);
IsReconstructing = true;
return false;
}
else
{
var packet = new UDPBPacket();
packet.SequenceId = SequenceId;
packet.Type = UDPBPacket.EPacketType.Ok;
IsFinished = true;
Console.WriteLine($"Finished seq:{_sequenceId}");
_socket.SendPacket(packet);
LastReconstructedBuffer = new byte[dataSize];
Array.Copy(_reconstructionBuffer, 0, LastReconstructedBuffer, 0, dataSize);
_receiveQueue.Enqueue(LastReconstructedBuffer);
return true;
}
}
private bool _shouldRequest=false;
private void RequestAgainIn(TimeSpan timeout, List<uint> missingPacketIds)
{
_shouldRequest = true;
}
private void RequestMissingPackets(List<uint> missingPacketIds)
{
MemoryStream ms = new MemoryStream();
BinaryWriter sw = new BinaryWriter(ms);
var waitFor = missingPacketIds.Take(10).ToList();
sw.Write(waitFor.Count);
foreach (uint id in waitFor)
{
sw.Write(id);
}
var packet = new UDPBPacket();
packet.SequenceId = SequenceId;
packet.Payload = ms.ToArray();
packet.Length = (uint) packet.Payload.Length;
packet.Type = UDPBPacket.EPacketType.Nak;
_socket.SendPacket(packet);
}
public bool IsReconstructing { get; private set; }
}
}

View File

@@ -0,0 +1,220 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Threading;
namespace Inspectron.HawkEye.UDPB
{
public class UDPBSocket : IDisposable
{
public int LossSimulation { get; set; }
public EndPoint LastConnection => _lastConnection;
private readonly UDPBQueue _queue;
private readonly UDPBReceiveBuffer _receiveBuffer;
private readonly byte[] _receivePacketBuffer = new byte[10 * 1024 * 1024];
private readonly Random _rnd = new Random();
private Socket _localSocket;
private uint _currentSquenceId = 1;
private EndPoint _lastConnection = new IPEndPoint(IPAddress.Any, 27001);
private bool _isServer;
private readonly ConcurrentQueue<byte[]> _receiveQueue = new ConcurrentQueue<byte[]>();
private bool _isReceiveing = true;
private readonly Thread _receiveThread;
private readonly List<UDPBPacket> _packetCache = new List<UDPBPacket>();
public UDPBSocket()
{
_queue = new UDPBQueue();
_receiveBuffer = new UDPBReceiveBuffer(this, _receiveQueue);
_receiveThread = new Thread(ReceiveLoop);
}
public void Dispose()
{
if (!_isServer) _localSocket.Disconnect(false);
_isReceiveing = false;
}
public static int FindFreePort(IPAddress adapter)
{
var port = 0;
var socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
try
{
var localEP = new IPEndPoint(adapter, 0);
socket.Bind(localEP);
localEP = (IPEndPoint) socket.LocalEndPoint;
port = localEP.Port;
}
finally
{
socket.Close();
}
return port;
}
public void Listen(IPAddress adapter, int port)
{
Console.WriteLine($"Listen on {adapter}");
_isServer = true;
_localSocket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
_localSocket.SetSocketOption(SocketOptionLevel.IP, SocketOptionName.ReuseAddress, true);
//_localSocket.SetSocketOption(SocketOptionLevel.Udp, SocketOptionName.DontFragment, true);
_localSocket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReceiveBuffer, 10 * 1024 * 1024);
_localSocket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.SendBuffer, 10 * 1024 * 1024);
_localSocket.Bind(new IPEndPoint(adapter, port));
_receiveThread.Start();
}
public void Connect(IPEndPoint endpoint, IPAddress adapter)
{
Console.WriteLine($"Connect to {adapter}");
_localSocket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
//_localSocket.SetSocketOption(SocketOptionLevel.Udp, SocketOptionName.DontFragment, true);
_localSocket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReceiveBuffer, 10 * 1024 * 1024);
_localSocket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.SendBuffer, 10 * 1024 * 1024);
Console.WriteLine($"Binding on {adapter?.MapToIPv4()}");
if (adapter != null)
_localSocket.Bind(new IPEndPoint(adapter.MapToIPv4(), 0));
_localSocket.Connect(endpoint);
_receiveThread.Start();
}
public void SendData(byte[] data)
{
_packetCache.Clear();
IncrementSequence();
var begin = new UDPBPacket {Type = UDPBPacket.EPacketType.BeginSequence, SequenceId = _currentSquenceId};
SendPacket(begin, true);
_queue.Reset();
_queue.AddBuffer(data, 0, data.Length);
var lastId = Flush();
var finish = new UDPBPacket
{Type = UDPBPacket.EPacketType.CloseSequence, MessageId = lastId + 1, SequenceId = _currentSquenceId};
SendPacket(finish, true);
do
{
byte[] dataOK;
dataOK = Receive();
_localSocket.ReceiveTimeout = 0;
var packet = new UDPBPacket();
packet.Deserialize(dataOK, 0, dataOK.Length);
if (packet.Type == UDPBPacket.EPacketType.Ok)
{
Console.WriteLine("OK");
break;
}
if (packet.Type == UDPBPacket.EPacketType.Nak)
{
var ms = new MemoryStream(packet.Payload);
var br = new BinaryReader(ms);
var packets = br.ReadInt32();
Console.WriteLine("NAK " + packets);
for (var i = 0; i < packets; i++)
{
var id = br.ReadUInt32();
var p = _packetCache.First(x => x.MessageId == id);
SendPacket(p, true);
}
SendPacket(finish, true);
}
} while (true);
}
public byte[] Receive()
{
byte[] res;
while (!_receiveQueue.TryDequeue(out res)) Thread.Sleep(1);
return res;
}
internal void SendPacket(UDPBPacket packet, bool noCache = false)
{
if (!noCache) _packetCache.Add(packet);
if (LossSimulation > 0)
if (_rnd.Next(LossSimulation) == LossSimulation - 1)
return;
if (_isServer)
_localSocket.SendTo(packet.Serialize(), _lastConnection);
else
_localSocket.Send(packet.Serialize());
}
private void ReceiveLoop()
{
while (_isReceiveing)
{
var received = ReceiveFrom(_receivePacketBuffer, 0);
_receiveBuffer.AddPacketBytes(_receivePacketBuffer, 0, received);
}
}
private int ReceiveFrom(byte[] buffer, int bufferOffset)
{
return _localSocket.ReceiveFrom(buffer, bufferOffset,
UDPBQueue.PACKET_SIZE + UDPBPacket.packetHeaderSize,
SocketFlags.None, ref _lastConnection);
}
private void IncrementSequence()
{
_currentSquenceId++;
if (_currentSquenceId == uint.MaxValue) _currentSquenceId = 1;
}
private uint Flush()
{
uint lastPacketId = 0;
while (_queue.PacketsToSend() != 0)
{
var packet = new UDPBPacket();
byte[] data = null;
uint msgNo = 0;
var payload = _queue.ReadData(ref data, ref msgNo);
packet.Type = UDPBPacket.EPacketType.Data;
packet.Length = payload;
packet.Payload = data;
packet.MessageId = msgNo;
packet.SequenceId = _currentSquenceId;
SendPacket(packet);
lastPacketId = msgNo;
}
return lastPacketId;
}
}
}

View File

@@ -0,0 +1,17 @@
namespace Inspectron.HawkEye
{
public class UDPServer
{
public UDPServer(string address, int port)
{
UDPSocket server = new UDPSocket();
server.Server(address, port);
server.Received += Server_Received;
}
private void Server_Received(byte[] obj)
{
}
}
}

View File

@@ -0,0 +1,106 @@
using System;
using System.Collections;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading.Tasks;
namespace Inspectron.HawkEye
{
public class UDPSocket:IDisposable
{
private Socket _socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
private const int bufSize = 8 * 1024;
private State state = new State();
private EndPoint epFrom = new IPEndPoint(IPAddress.Any, 0);
private AsyncCallback recv = null;
public class State
{
public byte[] buffer = new byte[bufSize];
}
public void Server(string address, int port)
{
_socket.SetSocketOption(SocketOptionLevel.IP, SocketOptionName.ReuseAddress, true);
_socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReceiveBuffer, 10*1024*1024);
_socket.Bind(new IPEndPoint(IPAddress.Parse(address), port));
ReceiveAsync();
}
public void Client(string address, int port)
{
_socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.SendBuffer, 10 * 1024 * 1024);
_socket.Connect(IPAddress.Parse(address), port);
ReceiveAsync();
}
public void Send(byte[] data)
{
//_socket.BeginSend(data, 0, data.Length, SocketFlags.None, (ar) =>
//{
// State so = (State)ar.AsyncState;
// int bytes = _socket.EndSend(ar);
//}, state);
for (int i = 0; i < Math.Ceiling(data.Length/1050.0); i++)
{
var size = Math.Min(1050, data.Length - i * 1050);
_socket.Send(data,i* 1050, size,SocketFlags.None);
}
}
public event Action<byte[]> Received = delegate { };
ConcurrentQueue<byte[]> _receivePool = new ConcurrentQueue<byte[]>();
private void ReceiveAsync()
{
for (int i = 0; i < 1000; i++)
{
_receivePool.Enqueue(new byte[1050]);
}
_receivePool.TryDequeue(out var buffer);
_socket.BeginReceiveFrom(buffer, 0, buffer.Length, SocketFlags.None, ref epFrom, recv = (ar) =>
{
byte[] so = (byte[])ar.AsyncState;
Task.Run(() =>
{
Received(so);
_receivePool.Enqueue(so);
}
);
int bytes = _socket.EndReceiveFrom(ar, ref epFrom);
_receivePool.TryDequeue(out var bufferLoc);
_socket.BeginReceiveFrom(bufferLoc, 0, so.Length, SocketFlags.None, ref epFrom, recv, bufferLoc);
}, buffer);
}
byte[] _receiveBuffer = new byte[1024*1024];
public byte[] Receive()
{
int received = 0;
while (received<1024*1024)
{
received+=_socket.Receive(_receiveBuffer);
}
return _receiveBuffer;
}
public void Dispose()
{
_socket?.Dispose();
}
}
}

View File

@@ -0,0 +1,14 @@
// UDT ACK Sub-sequence Number: 0 - (2^31 - 1)
namespace UdtSharp
{
static class AckNumber
{
public static int incack(int ackno)
{
return (ackno == m_iMaxAckSeqNo) ? 0 : ackno + 1;
}
public static int m_iMaxAckSeqNo = 0x7FFFFFFF; // maximum ACK sub-sequence number used in UDT
}
}

View File

@@ -0,0 +1,476 @@
using System;
using System.Collections.Generic;
namespace UdtSharp
{
public class SndBuffer
{
object m_BufLock = new object(); // used to synchronize buffer operation
class Block
{
internal byte[] m_pcData; // pointer to the data block
internal int m_iLength; // length of the block
internal uint m_iMsgNo; // message number
internal ulong m_OriginTime; // original request time
internal int m_iTTL; // time to live (milliseconds)
}
List<Block> mBlockList = new List<Block>();
int m_iLastBlock = 0;
int m_iCurrentBlock = 0;
int m_iFirstBlock = 0;
uint m_iNextMsgNo; // next message number
int m_iSize; // buffer size (number of packets)
int m_iMSS; // maximum seqment/packet size
int m_iCount; // number of used blocks
public SndBuffer(int size, int mss)
{
m_iSize = size;
m_iMSS = mss;
// circular linked list for out bound packets
for (int i = 0; i < m_iSize; ++i)
{
Block block = new Block();
block.m_iMsgNo = 0;
block.m_pcData = new byte[m_iMSS];
mBlockList.Add(block);
}
}
// Functionality:
// Insert a user buffer into the sending list.
// Parameters:
// 0) [in] data: pointer to the user data block.
// 1) [in] len: size of the block.
// 2) [in] ttl: time to live in milliseconds
// 3) [in] order: if the block should be delivered in order, for DGRAM only
// Returned value:
// None.
public void addBuffer(byte[] data, int offset, int len, int ttl = -1, bool order = false)
{
int size = len / m_iMSS;
if ((len % m_iMSS) != 0)
size++;
// dynamically increase sender buffer
while (size + m_iCount >= m_iSize)
increase();
ulong time = Timer.getTime();
uint inorder = Convert.ToUInt32(order);
inorder <<= 29;
for (int i = 0; i < size; ++i)
{
Block s = mBlockList[m_iLastBlock];
IncrementBlockIndex(ref m_iLastBlock);
int pktlen = len - i * m_iMSS;
if (pktlen > m_iMSS)
pktlen = m_iMSS;
Array.Copy(data, i * m_iMSS + offset, s.m_pcData, 0, pktlen);
s.m_iLength = pktlen;
s.m_iMsgNo = m_iNextMsgNo | inorder;
if (i == 0)
s.m_iMsgNo |= 0x80000000;
if (i == size - 1)
s.m_iMsgNo |= 0x40000000;
s.m_OriginTime = time;
s.m_iTTL = ttl;
}
lock (m_BufLock)
{
m_iCount += size;
}
m_iNextMsgNo++;
if (m_iNextMsgNo == MessageNumber.m_iMaxMsgNo)
m_iNextMsgNo = 1;
}
public int readData(ref byte[] data, ref uint msgno)
{
// No data to read
if (m_iCurrentBlock == m_iLastBlock)
return 0;
data = mBlockList[m_iCurrentBlock].m_pcData;
int readlen = mBlockList[m_iCurrentBlock].m_iLength;
msgno = mBlockList[m_iCurrentBlock].m_iMsgNo;
IncrementBlockIndex(ref m_iCurrentBlock);
return readlen;
}
public int readData(ref byte[] data, int offset, ref uint msgno, out int msglen)
{
msglen = 0;
lock (m_BufLock)
{
int blockIndex = m_iFirstBlock;
IncrementBlockIndex(ref blockIndex, offset);
Block p = mBlockList[blockIndex];
if ((p.m_iTTL >= 0) && ((Timer.getTime() - p.m_OriginTime) / 1000 > (ulong)p.m_iTTL))
{
msgno = p.m_iMsgNo & 0x1FFFFFFF;
msglen = 1;
IncrementBlockIndex(ref blockIndex);
p = mBlockList[blockIndex];
bool move = false;
while (msgno == (p.m_iMsgNo & 0x1FFFFFFF))
{
if (blockIndex == m_iCurrentBlock)
move = true;
IncrementBlockIndex(ref blockIndex);
p = mBlockList[blockIndex];
if (move)
m_iCurrentBlock = blockIndex;
msglen++;
}
return -1;
}
data = p.m_pcData;
int readlen = p.m_iLength;
msgno = p.m_iMsgNo;
return readlen;
}
}
void IncrementBlockIndex(ref int blockIndex, int offset = 1)
{
blockIndex = (blockIndex + offset) % mBlockList.Count;
}
public void ackData(int offset)
{
lock (m_BufLock)
{
IncrementBlockIndex(ref m_iFirstBlock, offset);
m_iCount -= offset;
Timer.triggerEvent();
}
}
public int getCurrBufSize()
{
return m_iCount;
}
void increase()
{
int unitsize = m_iSize;
for (int i = 0; i < unitsize; ++i)
{
Block block = new Block();
block.m_iMsgNo = 0;
block.m_pcData = new byte[m_iMSS];
mBlockList.Add(block);
}
m_iSize += unitsize;
}
}
public class RcvBuffer
{
Unit[] m_pUnit; // pointer to the protocol buffer
int m_iSize; // size of the protocol buffer
int m_iStartPos; // the head position for I/O (inclusive)
int m_iLastAckPos; // the last ACKed position (exclusive)
// EMPTY: m_iStartPos = m_iLastAckPos FULL: m_iStartPos = m_iLastAckPos + 1
int m_iMaxPos; // the furthest data position
int m_iNotch; // the starting read point of the first unit
public RcvBuffer(int bufsize)
{
m_iSize = bufsize;
m_iStartPos = 0;
m_iLastAckPos = 0;
m_iMaxPos = 0;
m_iNotch = 0;
m_pUnit = new Unit[m_iSize];
for (int i = 0; i < m_iSize; ++i)
m_pUnit[i] = null;
}
~RcvBuffer()
{
for (int i = 0; i < m_iSize; ++i)
{
if (null != m_pUnit[i])
{
m_pUnit[i].m_iFlag = 0;
}
}
}
public int addData(Unit unit, int offset)
{
int pos = (m_iLastAckPos + offset) % m_iSize;
if (offset > m_iMaxPos)
m_iMaxPos = offset;
if (null != m_pUnit[pos])
return -1;
m_pUnit[pos] = unit;
unit.m_iFlag = 1;
return 0;
}
public int readBuffer(byte[] data, int offset, int len)
{
int p = m_iStartPos;
int lastack = m_iLastAckPos;
int rs = len;
while ((p != lastack) && (rs > 0))
{
int unitsize = m_pUnit[p].m_Packet.getLength() - m_iNotch;
if (unitsize > rs)
unitsize = rs;
unitsize = m_pUnit[p].m_Packet.GetDataBytes(m_iNotch, data, offset, unitsize);
offset += unitsize;
if ((rs > unitsize) || (rs == m_pUnit[p].m_Packet.getLength() - m_iNotch))
{
Unit tmp = m_pUnit[p];
m_pUnit[p] = null;
tmp.m_iFlag = 0;
if (++p == m_iSize)
p = 0;
m_iNotch = 0;
}
else
m_iNotch += rs;
rs -= unitsize;
}
m_iStartPos = p;
return len - rs;
}
public void ackData(int len)
{
m_iLastAckPos = (m_iLastAckPos + len) % m_iSize;
m_iMaxPos -= len;
if (m_iMaxPos < 0)
m_iMaxPos = 0;
Timer.triggerEvent();
}
public int getAvailBufSize()
{
// One slot must be empty in order to tell the difference between "empty buffer" and "full buffer"
return m_iSize - getRcvDataSize() - 1;
}
public int getRcvDataSize()
{
if (m_iLastAckPos >= m_iStartPos)
return m_iLastAckPos - m_iStartPos;
return m_iSize + m_iLastAckPos - m_iStartPos;
}
public void dropMsg(int msgno)
{
for (int i = m_iStartPos, n = (m_iLastAckPos + m_iMaxPos) % m_iSize; i != n; i = (i + 1) % m_iSize)
if ((null != m_pUnit[i]) && (msgno == m_pUnit[i].m_Packet.GetMessageNumber()))
m_pUnit[i].m_iFlag = 3;
}
public int readMsg(byte[] data, int len)
{
int p = 0;
int q = 0;
bool passack = false;
if (!scanMsg(ref p, ref q, ref passack))
return 0;
int rs = len;
int dataOffset = 0;
while (p != (q + 1) % m_iSize)
{
byte[] allData = m_pUnit[p].m_Packet.GetDataBytes();
int unitsize = allData.Length;
if ((rs >= 0) && (unitsize > rs))
unitsize = rs;
if (unitsize > 0)
{
Array.Copy(allData, 0, data, dataOffset, unitsize);
dataOffset += unitsize;
rs -= unitsize;
}
if (!passack)
{
Unit tmp = m_pUnit[p];
m_pUnit[p] = null;
tmp.m_iFlag = 0;
}
else
m_pUnit[p].m_iFlag = 2;
if (++p == m_iSize)
p = 0;
}
if (!passack)
m_iStartPos = (q + 1) % m_iSize;
return len - rs;
}
int getRcvMsgNum()
{
int p = 0;
int q = 0;
bool passack = false;
return scanMsg(ref p, ref q, ref passack) ? 1 : 0;
}
bool scanMsg(ref int p, ref int q, ref bool passack)
{
// empty buffer
if ((m_iStartPos == m_iLastAckPos) && (m_iMaxPos <= 0))
return false;
//skip all bad msgs at the beginning
while (m_iStartPos != m_iLastAckPos)
{
if (null == m_pUnit[m_iStartPos])
{
if (++m_iStartPos == m_iSize)
m_iStartPos = 0;
continue;
}
if ((1 == m_pUnit[m_iStartPos].m_iFlag) && (m_pUnit[m_iStartPos].m_Packet.getMsgBoundary() > 1))
{
bool good = true;
// look ahead for the whole message
for (int i = m_iStartPos; i != m_iLastAckPos;)
{
if ((null == m_pUnit[i]) || (1 != m_pUnit[i].m_iFlag))
{
good = false;
break;
}
if ((m_pUnit[i].m_Packet.getMsgBoundary() == 1) || (m_pUnit[i].m_Packet.getMsgBoundary() == 3))
break;
if (++i == m_iSize)
i = 0;
}
if (good)
break;
}
Unit tmp = m_pUnit[m_iStartPos];
m_pUnit[m_iStartPos] = null;
tmp.m_iFlag = 0;
if (++m_iStartPos == m_iSize)
m_iStartPos = 0;
}
p = -1; // message head
q = m_iStartPos; // message tail
passack = m_iStartPos == m_iLastAckPos;
bool found = false;
// looking for the first message
for (int i = 0, n = m_iMaxPos + getRcvDataSize(); i <= n; ++i)
{
if ((null != m_pUnit[q]) && (1 == m_pUnit[q].m_iFlag))
{
switch (m_pUnit[q].m_Packet.getMsgBoundary())
{
case 3: // 11
p = q;
found = true;
break;
case 2: // 10
p = q;
break;
case 1: // 01
if (p != -1)
found = true;
break;
}
}
else
{
// a hole in this message, not valid, restart search
p = -1;
}
if (found)
{
// the msg has to be ack'ed or it is allowed to read out of order, and was not read before
if (!passack || !m_pUnit[q].m_Packet.getMsgOrderFlag())
break;
found = false;
}
if (++q == m_iSize)
q = 0;
if (q == m_iLastAckPos)
passack = true;
}
// no msg found
if (!found)
{
// if the message is larger than the receiver buffer, return part of the message
if ((p != -1) && ((q + 1) % m_iSize == p))
found = true;
}
return found;
}
}
}

View File

@@ -0,0 +1,217 @@
using System;
using System.Net;
using System.Net.Sockets;
using System.Runtime.InteropServices;
using System.Text;
namespace UdtSharp
{
public class Channel
{
AddressFamily m_iIPversion; // IP version
Socket m_socket; // socket descriptor
int m_iSndBufSize; // UDP sending buffer size
int m_iRcvBufSize;
public Channel()
{
m_iIPversion = AddressFamily.InterNetwork;
m_iSndBufSize = 65536;
m_iRcvBufSize = 65536;
}
public Channel(AddressFamily addressFamily)
{
m_iIPversion = addressFamily;
m_iSndBufSize = 65536;
m_iRcvBufSize = 65536;
}
public void open(IPEndPoint addr)
{
// construct a socket
try
{
m_socket = new Socket(m_iIPversion, SocketType.Dgram, ProtocolType.Udp);
}
catch (SocketException e)
{
throw new UdtException(1, 0, e.ErrorCode);
}
if (null != addr)
{
try
{
m_socket.Bind(addr);
}
catch (SocketException e)
{
throw new UdtException(1, 3, e.ErrorCode);
}
}
else
{
try
{
m_socket.Bind(new IPEndPoint(IPAddress.Any, 0));
}
catch (SocketException e)
{
throw new UdtException(1, 3, e.ErrorCode);
}
}
setUDPSockOpt();
}
public void open(Socket udpsock)
{
m_socket = udpsock;
setUDPSockOpt();
}
void setUDPSockOpt()
{
m_socket.ReceiveBufferSize = m_iRcvBufSize;
m_socket.SendBufferSize = m_iSndBufSize;
}
public void close()
{
m_socket.Close();
}
int getSndBufSize()
{
m_iSndBufSize = (int)m_socket.GetSocketOption(SocketOptionLevel.Socket, SocketOptionName.SendBuffer);
return m_iSndBufSize;
}
int getRcvBufSize()
{
m_iRcvBufSize = (int)m_socket.GetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReceiveBuffer);
return m_iRcvBufSize;
}
public void setSndBufSize(int size)
{
m_iSndBufSize = size;
}
public void setRcvBufSize(int size)
{
m_iRcvBufSize = size;
}
public void getSockAddr(ref IPEndPoint addr)
{
addr = (IPEndPoint)m_socket.LocalEndPoint;
}
void getPeerAddr(ref IPEndPoint addr)
{
addr = (IPEndPoint)m_socket.RemoteEndPoint;
}
public int sendto(IPEndPoint addr, Packet packet)
{
TraceSend(addr, packet);
// convert control information into network order
packet.ConvertControlInfoToNetworkOrder();
// convert packet header into network order
packet.ConvertHeaderToNetworkOrder();
byte[] data = packet.GetBytes();
int res = m_socket.SendTo(data, addr);
// convert back into local host order
packet.ConvertHeaderToHostOrder();
packet.ConvertControlInfoToHostOrder();
return res;
}
void TraceSend(IPEndPoint destination, Packet packet)
{
return;
StringBuilder sb = new StringBuilder();
sb.Append(DateTime.Now.ToString("hh:mm:ss.fff"));
sb.AppendFormat(" SND {0} => {1}", m_socket.LocalEndPoint, destination);
sb.AppendLine();
sb.AppendLine(packet.ToString());
sb.AppendLine();
Console.WriteLine(sb.ToString());
}
void TraceRecv(IPEndPoint source, Packet packet)
{
return;
StringBuilder sb = new StringBuilder();
sb.Append(DateTime.Now.ToString("hh:mm:ss.fff"));
sb.AppendFormat(" RCV {0} <= {1}", m_socket.LocalEndPoint, source);
sb.AppendLine();
sb.AppendLine(packet.ToString());
sb.AppendLine();
Console.WriteLine(sb.ToString());
}
public int recvfrom(ref IPEndPoint addr, Packet packet)
{
try
{
if (!m_socket.Poll(10000, SelectMode.SelectRead))
return -1;
}
catch (SocketException sex)
{
return -1;
}
catch (ObjectDisposedException odex)
{
return -1;
}
byte[] bytes = new byte[Packet.m_iPktHdrSize + packet.getLength()];
EndPoint source = addr;
int res;
try
{
res = m_socket.ReceiveFrom(bytes, ref source);
}
catch (SocketException sex)
{
return -1;
}
catch (ObjectDisposedException odex)
{
return -1;
}
addr = source as IPEndPoint;
if (res <= 0)
{
return -1;
}
bool success = packet.SetHeaderAndDataFromBytes(bytes, res);
if (!success)
return -1;
// convert back into local host order
packet.ConvertHeaderToHostOrder();
packet.ConvertControlInfoToHostOrder();
TraceRecv(addr, packet);
return packet.getLength();
}
}
}

View File

@@ -0,0 +1,362 @@
using System;
using UDTSOCKET = System.Int32;
namespace UdtSharp
{
public class CC
{
protected const int m_iSYNInterval = UDT.m_iSYNInterval; // UDT constant parameter, SYN
public double m_dPktSndPeriod; // Packet sending period, in microseconds
public double m_dCWndSize; // Congestion window size, in packets
protected int m_iBandwidth; // estimated bandwidth, packets per second
protected double m_dMaxCWndSize; // maximum cwnd size, in packets
protected int m_iMSS; // Maximum Packet Size, including all packet headers
protected int m_iSndCurrSeqNo; // current maximum seq no sent out
protected int m_iRcvRate; // packet arrive rate at receiver side, packets per second
protected int m_iRTT; // current estimated RTT, microsecond
protected string m_pcParam; // user defined parameter
public UDTSOCKET m_UDT; // The UDT entity that this congestion control algorithm is bound to
public int m_iACKPeriod; // Periodical timer to send an ACK, in milliseconds
public int m_iACKInterval; // How many packets to send one ACK, in packets
public bool m_bUserDefinedRTO; // if the RTO value is defined by users
public int m_iRTO; // RTO value, microseconds
PerfMon m_PerfInfo = new PerfMon(); // protocol statistics information
public CC()
{
m_dPktSndPeriod = 1.0;
m_dCWndSize = 16.0;
m_pcParam = null;
m_iACKPeriod = 0;
m_iACKInterval = 0;
m_bUserDefinedRTO = false;
m_iRTO = -1;
}
// Functionality:
// Callback function to be called (only) at the start of a UDT connection.
// note that this is different from CCC(), which is always called.
// Parameters:
// None.
// Returned value:
// None.
public virtual void init() { }
// Functionality:
// Callback function to be called when a UDT connection is closed.
// Parameters:
// None.
// Returned value:
// None.
public virtual void close() { }
// Functionality:
// Callback function to be called when an ACK packet is received.
// Parameters:
// 0) [in] ackno: the data sequence number acknowledged by this ACK.
// Returned value:
// None.
public virtual void onACK(int seqno) { }
// Functionality:
// Callback function to be called when a loss report is received.
// Parameters:
// 0) [in] losslist: list of sequence number of packets, in the format describled in packet.cpp.
// 1) [in] size: length of the loss list.
// Returned value:
// None.
public virtual void onLoss(int[] loss, int length) { }
// Functionality:
// Callback function to be called when a timeout event occurs.
// Parameters:
// None.
// Returned value:
// None.
public virtual void onTimeout() { }
// Functionality:
// Callback function to be called when a data is sent.
// Parameters:
// 0) [in] seqno: the data sequence number.
// 1) [in] size: the payload size.
// Returned value:
// None.
public virtual void onPktSent(Packet packet) { }
// Functionality:
// Callback function to be called when a data is received.
// Parameters:
// 0) [in] seqno: the data sequence number.
// 1) [in] size: the payload size.
// Returned value:
// None.
public virtual void onPktReceived(Packet packet) { }
// Functionality:
// Callback function to Process a user defined packet.
// Parameters:
// 0) [in] pkt: the user defined packet.
// Returned value:
// None.
public virtual void processCustomMsg(Packet packet) { }
protected void setACKTimer(int msINT)
{
m_iACKPeriod = msINT > m_iSYNInterval ? m_iSYNInterval : msINT;
}
protected void setACKInterval(int pktINT)
{
m_iACKInterval = pktINT;
}
protected void setRTO(int usRTO)
{
m_bUserDefinedRTO = true;
m_iRTO = usRTO;
}
protected void sendCustomMsg(Packet pkt)
{
UDT u = UDT.s_UDTUnited.lookup(m_UDT);
if (null != u)
{
pkt.SetId(u.m_PeerID);
u.m_pSndQueue.sendto(u.m_pPeerAddr, pkt);
}
}
protected PerfMon getPerfInfo()
{
try
{
UDT u = UDT.s_UDTUnited.lookup(m_UDT);
if (null != u)
u.sample(m_PerfInfo, false);
}
catch (Exception e)
{
return null;
}
return m_PerfInfo;
}
public void setMSS(int mss)
{
m_iMSS = mss;
}
public void setBandwidth(int bw)
{
m_iBandwidth = bw;
}
public void setSndCurrSeqNo(int seqno)
{
m_iSndCurrSeqNo = seqno;
}
public void setRcvRate(int rcvrate)
{
m_iRcvRate = rcvrate;
}
public void setMaxCWndSize(int cwnd)
{
m_dMaxCWndSize = cwnd;
}
public void setRTT(int rtt)
{
m_iRTT = rtt;
}
protected void setUserParam(string param)
{
m_pcParam = param;
}
}
public class UDTCC : CC
{
int m_iRCInterval; // UDT Rate control interval
ulong m_LastRCTime; // last rate increase time
bool m_bSlowStart; // if in slow start phase
int m_iLastAck; // last ACKed seq no
bool m_bLoss; // if loss happened since last rate increase
int m_iLastDecSeq; // max pkt seq no sent out when last decrease happened
double m_dLastDecPeriod; // value of pktsndperiod when last decrease happened
int m_iNAKCount; // NAK counter
int m_iDecRandom; // random threshold on decrease by number of loss events
int m_iAvgNAKNum; // average number of NAKs per congestion
int m_iDecCount; // number of decreases in a congestion epoch
static Random m_random = new Random();
public override void init()
{
m_iRCInterval = m_iSYNInterval;
m_LastRCTime = Timer.getTime();
setACKTimer(m_iRCInterval);
m_bSlowStart = true;
m_iLastAck = m_iSndCurrSeqNo;
m_bLoss = false;
m_iLastDecSeq = SequenceNumber.decseq(m_iLastAck);
m_dLastDecPeriod = 1;
m_iAvgNAKNum = 0;
m_iNAKCount = 0;
m_iDecRandom = 1;
m_dCWndSize = 16;
m_dPktSndPeriod = 1;
}
public override void onACK(int ack)
{
long B = 0;
double inc = 0;
// Note: 1/24/2012
// The minimum increase parameter is increased from "1.0 / m_iMSS" to 0.01
// because the original was too small and caused sending rate to stay at low level
// for long time.
const double min_inc = 0.01;
ulong currtime = Timer.getTime();
if (currtime - m_LastRCTime < (ulong)m_iRCInterval)
return;
m_LastRCTime = currtime;
if (m_bSlowStart)
{
m_dCWndSize += SequenceNumber.seqlen(m_iLastAck, ack);
m_iLastAck = ack;
if (m_dCWndSize > m_dMaxCWndSize)
{
m_bSlowStart = false;
if (m_iRcvRate > 0)
m_dPktSndPeriod = 1000000.0 / m_iRcvRate;
else
m_dPktSndPeriod = (m_iRTT + m_iRCInterval) / m_dCWndSize;
}
}
else
m_dCWndSize = m_iRcvRate / 1000000.0 * (m_iRTT + m_iRCInterval) + 16;
// During Slow Start, no rate increase
if (m_bSlowStart)
return;
if (m_bLoss)
{
m_bLoss = false;
return;
}
B = (long)(m_iBandwidth - 1000000.0 / m_dPktSndPeriod);
if ((m_dPktSndPeriod > m_dLastDecPeriod) && ((m_iBandwidth / 9) < B))
B = m_iBandwidth / 9;
if (B <= 0)
inc = min_inc;
else
{
// inc = max(10 ^ ceil(log10( B * MSS * 8 ) * Beta / MSS, 1/MSS)
// Beta = 1.5 * 10^(-6)
inc = Math.Pow(10.0, Math.Ceiling(Math.Log10(B * m_iMSS * 8.0))) * 0.0000015 / m_iMSS;
if (inc < min_inc)
inc = min_inc;
}
m_dPktSndPeriod = (m_dPktSndPeriod * m_iRCInterval) / (m_dPktSndPeriod * inc + m_iRCInterval);
}
public override void onLoss(int[] losslist, int length)
{
//Slow Start stopped, if it hasn't yet
if (m_bSlowStart)
{
m_bSlowStart = false;
if (m_iRcvRate > 0)
{
// Set the sending rate to the receiving rate.
m_dPktSndPeriod = 1000000.0 / m_iRcvRate;
return;
}
// If no receiving rate is observed, we have to compute the sending
// rate according to the current window size, and decrease it
// using the method below.
m_dPktSndPeriod = m_dCWndSize / (m_iRTT + m_iRCInterval);
}
m_bLoss = true;
if (SequenceNumber.seqcmp(losslist[0] & 0x7FFFFFFF, m_iLastDecSeq) > 0)
{
m_dLastDecPeriod = m_dPktSndPeriod;
m_dPktSndPeriod = Math.Ceiling(m_dPktSndPeriod * 1.125);
m_iAvgNAKNum = (int)Math.Ceiling(m_iAvgNAKNum * 0.875 + m_iNAKCount * 0.125);
m_iNAKCount = 1;
m_iDecCount = 1;
m_iLastDecSeq = m_iSndCurrSeqNo;
// remove global synchronization using randomization
m_iDecRandom = (int)Math.Ceiling(m_iAvgNAKNum * m_random.NextDouble());
if (m_iDecRandom < 1)
m_iDecRandom = 1;
}
else if ((m_iDecCount++ < 5) && (0 == (++m_iNAKCount % m_iDecRandom)))
{
// 0.875^5 = 0.51, rate should not be decreased by more than half within a congestion period
m_dPktSndPeriod = Math.Ceiling(m_dPktSndPeriod * 1.125);
m_iLastDecSeq = m_iSndCurrSeqNo;
}
}
public override void onTimeout()
{
if (m_bSlowStart)
{
m_bSlowStart = false;
if (m_iRcvRate > 0)
m_dPktSndPeriod = 1000000.0 / m_iRcvRate;
else
m_dPktSndPeriod = m_dCWndSize / (m_iRTT + m_iRCInterval);
}
else
{
/*
m_dLastDecPeriod = m_dPktSndPeriod;
m_dPktSndPeriod = ceil(m_dPktSndPeriod * 2);
m_iLastDecSeq = m_iLastAck;
*/
}
}
}
}

View File

@@ -0,0 +1,22 @@
namespace UdtSharp
{
public abstract class CCVirtualFactory
{
public abstract CC create();
public abstract CCVirtualFactory clone();
}
public class CCFactory<T> : CCVirtualFactory where T : new()
{
public override CC create()
{
return new T() as CC;
}
public override CCVirtualFactory clone()
{
return new CCFactory<T>();
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,19 @@
using System.Collections.Generic;
using System.Linq;
namespace UdtSharp
{
public static class CoreExtensions
{
public static bool TryGetValue(this HashSet<InfoBlock> self, InfoBlock equalValue, out InfoBlock actualValue)
{
if (self.Contains(equalValue))
{
actualValue = self.First(x=>x==equalValue);
return true;
}
actualValue = null;
return false;
}
}
}

View File

@@ -0,0 +1,7 @@
namespace UdtSharp
{
public enum ERequestType
{
Trigger
}
}

View File

@@ -0,0 +1,111 @@
using System;
using System.Net;
using System.Net.Sockets;
namespace UdtSharp
{
public class InfoBlock
{
uint[] m_piIP = new uint[4]; // IP address, machine read only, not human readable format
AddressFamily m_iIPversion; // IP version
public ulong m_ullTimeStamp; // last update time
public int m_iRTT; // RTT
public int m_iBandwidth; // estimated bandwidth
public int m_iLossRate; // average loss rate
public int m_iReorderDistance; // packet reordering distance
public double m_dInterval; // inter-packet time, congestion control
public double m_dCWnd; // congestion window size, congestion control
public InfoBlock(IPAddress address)
{
m_iIPversion = address.AddressFamily;
ConvertIPAddress.ToUintArray(address, ref m_piIP);
}
public override bool Equals(object value)
{
// Is null?
if (Object.ReferenceEquals(null, value))
{
return false;
}
// Is the same object?
if (Object.ReferenceEquals(this, value))
{
return true;
}
// Is the same type?
if (value.GetType() != this.GetType())
{
return false;
}
return IsEqual((InfoBlock)value);
}
public bool Equals(InfoBlock infoBlock)
{
if (Object.ReferenceEquals(null, infoBlock))
{
return false;
}
// Is the same object?
if (Object.ReferenceEquals(this, infoBlock))
{
return true;
}
return IsEqual(infoBlock);
}
public static bool operator ==(InfoBlock infoBlockA, InfoBlock infoBlockB)
{
if (Object.ReferenceEquals(infoBlockA, infoBlockB))
{
return true;
}
// Ensure that "numberA" isn't null
if (Object.ReferenceEquals(null, infoBlockA))
{
return false;
}
return (infoBlockA.Equals(infoBlockB));
}
public static bool operator !=(InfoBlock infoBlockA, InfoBlock infoBlockB)
{
return !(infoBlockA == infoBlockB);
}
public override int GetHashCode()
{
if (m_iIPversion == AddressFamily.InterNetwork)
return (int)m_piIP[0];
return (int)(m_piIP[0] + m_piIP[1] + m_piIP[2] + m_piIP[3]);
}
bool IsEqual(InfoBlock infoBlock)
{
if (m_iIPversion != infoBlock.m_iIPversion)
return false;
else if (m_iIPversion == AddressFamily.InterNetwork)
return (m_piIP[0] == infoBlock.m_piIP[0]);
for (int i = 0; i < 4; ++i)
{
if (m_piIP[i] != infoBlock.m_piIP[i])
return false;
}
return true;
}
}
}

View File

@@ -0,0 +1,672 @@
namespace UdtSharp
{
public class SndLossList
{
int[] m_piData1; // sequence number starts
int[] m_piData2; // seqnence number ends
int[] m_piNext; // next node in the list
int m_iHead; // first node
int m_iLength; // loss length
int m_iSize; // size of the static array
int m_iLastInsertPos; // position of last insert node
object m_ListLock = new object(); // used to synchronize list operation
public SndLossList(int size)
{
m_iHead = -1;
m_iLength = 0;
m_iSize = size;
m_iLastInsertPos = -1;
m_piData1 = new int[m_iSize];
m_piData2 = new int[m_iSize];
m_piNext = new int[m_iSize];
// -1 means there is no data in the node
for (int i = 0; i < size; ++i)
{
m_piData1[i] = -1;
m_piData2[i] = -1;
}
}
public int insert(int seqno1, int seqno2)
{
lock (m_ListLock)
{
return insert_unsafe(seqno1, seqno2);
}
}
int insert_unsafe(int seqno1, int seqno2)
{
if (0 == m_iLength)
{
// insert data into an empty list
m_iHead = 0;
m_piData1[m_iHead] = seqno1;
if (seqno2 != seqno1)
m_piData2[m_iHead] = seqno2;
m_piNext[m_iHead] = -1;
m_iLastInsertPos = m_iHead;
m_iLength += SequenceNumber.seqlen(seqno1, seqno2);
return m_iLength;
}
// otherwise find the position where the data can be inserted
int origlen = m_iLength;
int offset = SequenceNumber.seqoff(m_piData1[m_iHead], seqno1);
int loc = (m_iHead + offset + m_iSize) % m_iSize;
if (offset < 0)
{
// Insert data prior to the head pointer
m_piData1[loc] = seqno1;
if (seqno2 != seqno1)
m_piData2[loc] = seqno2;
// new node becomes head
m_piNext[loc] = m_iHead;
m_iHead = loc;
m_iLastInsertPos = loc;
m_iLength += SequenceNumber.seqlen(seqno1, seqno2);
}
else if (offset > 0)
{
if (seqno1 == m_piData1[loc])
{
m_iLastInsertPos = loc;
// first seqno is equivlent, compare the second
if (-1 == m_piData2[loc])
{
if (seqno2 != seqno1)
{
m_iLength += SequenceNumber.seqlen(seqno1, seqno2) - 1;
m_piData2[loc] = seqno2;
}
}
else if (SequenceNumber.seqcmp(seqno2, m_piData2[loc]) > 0)
{
// new seq pair is longer than old pair, e.g., insert [3, 7] to [3, 5], becomes [3, 7]
m_iLength += SequenceNumber.seqlen(m_piData2[loc], seqno2) - 1;
m_piData2[loc] = seqno2;
}
else
// Do nothing if it is already there
return 0;
}
else
{
// searching the prior node
int i;
if ((-1 != m_iLastInsertPos) && (SequenceNumber.seqcmp(m_piData1[m_iLastInsertPos], seqno1) < 0))
i = m_iLastInsertPos;
else
i = m_iHead;
while ((-1 != m_piNext[i]) && (SequenceNumber.seqcmp(m_piData1[m_piNext[i]], seqno1) < 0))
i = m_piNext[i];
if ((-1 == m_piData2[i]) || (SequenceNumber.seqcmp(m_piData2[i], seqno1) < 0))
{
m_iLastInsertPos = loc;
// no overlap, create new node
m_piData1[loc] = seqno1;
if (seqno2 != seqno1)
m_piData2[loc] = seqno2;
m_piNext[loc] = m_piNext[i];
m_piNext[i] = loc;
m_iLength += SequenceNumber.seqlen(seqno1, seqno2);
}
else
{
m_iLastInsertPos = i;
// overlap, coalesce with prior node, insert(3, 7) to [2, 5], ... becomes [2, 7]
if (SequenceNumber.seqcmp(m_piData2[i], seqno2) < 0)
{
m_iLength += SequenceNumber.seqlen(m_piData2[i], seqno2) - 1;
m_piData2[i] = seqno2;
loc = i;
}
else
return 0;
}
}
}
else
{
m_iLastInsertPos = m_iHead;
// insert to head node
if (seqno2 != seqno1)
{
if (-1 == m_piData2[loc])
{
m_iLength += SequenceNumber.seqlen(seqno1, seqno2) - 1;
m_piData2[loc] = seqno2;
}
else if (SequenceNumber.seqcmp(seqno2, m_piData2[loc]) > 0)
{
m_iLength += SequenceNumber.seqlen(m_piData2[loc], seqno2) - 1;
m_piData2[loc] = seqno2;
}
else
return 0;
}
else
return 0;
}
// coalesce with next node. E.g., [3, 7], ..., [6, 9] becomes [3, 9]
while ((-1 != m_piNext[loc]) && (-1 != m_piData2[loc]))
{
int i = m_piNext[loc];
if (SequenceNumber.seqcmp(m_piData1[i], SequenceNumber.incseq(m_piData2[loc])) <= 0)
{
// coalesce if there is overlap
if (-1 != m_piData2[i])
{
if (SequenceNumber.seqcmp(m_piData2[i], m_piData2[loc]) > 0)
{
if (SequenceNumber.seqcmp(m_piData2[loc], m_piData1[i]) >= 0)
m_iLength -= SequenceNumber.seqlen(m_piData1[i], m_piData2[loc]);
m_piData2[loc] = m_piData2[i];
}
else
m_iLength -= SequenceNumber.seqlen(m_piData1[i], m_piData2[i]);
}
else
{
if (m_piData1[i] == SequenceNumber.incseq(m_piData2[loc]))
m_piData2[loc] = m_piData1[i];
else
m_iLength--;
}
m_piData1[i] = -1;
m_piData2[i] = -1;
m_piNext[loc] = m_piNext[i];
}
else
break;
}
return m_iLength - origlen;
}
public void remove(int seqno)
{
lock (m_ListLock)
{
remove_unsafe(seqno);
}
}
void remove_unsafe(int seqno)
{
if (0 == m_iLength)
return;
// Remove all from the head pointer to a node with a larger seq. no. or the list is empty
int offset = SequenceNumber.seqoff(m_piData1[m_iHead], seqno);
int loc = (m_iHead + offset + m_iSize) % m_iSize;
if (0 == offset)
{
// It is the head. Remove the head and point to the next node
loc = (loc + 1) % m_iSize;
if (-1 == m_piData2[m_iHead])
loc = m_piNext[m_iHead];
else
{
m_piData1[loc] = SequenceNumber.incseq(seqno);
if (SequenceNumber.seqcmp(m_piData2[m_iHead], SequenceNumber.incseq(seqno)) > 0)
m_piData2[loc] = m_piData2[m_iHead];
m_piData2[m_iHead] = -1;
m_piNext[loc] = m_piNext[m_iHead];
}
m_piData1[m_iHead] = -1;
if (m_iLastInsertPos == m_iHead)
m_iLastInsertPos = -1;
m_iHead = loc;
m_iLength--;
}
else if (offset > 0)
{
int h = m_iHead;
if (seqno == m_piData1[loc])
{
// target node is not empty, remove part/all of the seqno in the node.
int temp = loc;
loc = (loc + 1) % m_iSize;
if (-1 == m_piData2[temp])
m_iHead = m_piNext[temp];
else
{
// remove part, e.g., [3, 7] becomes [], [4, 7] after remove(3)
m_piData1[loc] = SequenceNumber.incseq(seqno);
if (SequenceNumber.seqcmp(m_piData2[temp], m_piData1[loc]) > 0)
m_piData2[loc] = m_piData2[temp];
m_iHead = loc;
m_piNext[loc] = m_piNext[temp];
m_piNext[temp] = loc;
m_piData2[temp] = -1;
}
}
else
{
// target node is empty, check prior node
int i = m_iHead;
while ((-1 != m_piNext[i]) && (SequenceNumber.seqcmp(m_piData1[m_piNext[i]], seqno) < 0))
i = m_piNext[i];
loc = (loc + 1) % m_iSize;
if (-1 == m_piData2[i])
m_iHead = m_piNext[i];
else if (SequenceNumber.seqcmp(m_piData2[i], seqno) > 0)
{
// remove part/all seqno in the prior node
m_piData1[loc] = SequenceNumber.incseq(seqno);
if (SequenceNumber.seqcmp(m_piData2[i], m_piData1[loc]) > 0)
m_piData2[loc] = m_piData2[i];
m_piData2[i] = seqno;
m_piNext[loc] = m_piNext[i];
m_piNext[i] = loc;
m_iHead = loc;
}
else
m_iHead = m_piNext[i];
}
// Remove all nodes prior to the new head
while (h != m_iHead)
{
if (m_piData2[h] != -1)
{
m_iLength -= SequenceNumber.seqlen(m_piData1[h], m_piData2[h]);
m_piData2[h] = -1;
}
else
m_iLength--;
m_piData1[h] = -1;
if (m_iLastInsertPos == h)
m_iLastInsertPos = -1;
h = m_piNext[h];
}
}
}
public int getLossLength()
{
lock (m_ListLock)
{
return m_iLength;
}
}
public int getLostSeq()
{
if (0 == m_iLength)
return -1;
lock (m_ListLock)
{
if (0 == m_iLength)
return -1;
if (m_iLastInsertPos == m_iHead)
m_iLastInsertPos = -1;
// return the first loss seq. no.
int seqno = m_piData1[m_iHead];
// head moves to the next node
if (-1 == m_piData2[m_iHead])
{
//[3, -1] becomes [], and head moves to next node in the list
m_piData1[m_iHead] = -1;
m_iHead = m_piNext[m_iHead];
}
else
{
// shift to next node, e.g., [3, 7] becomes [], [4, 7]
int loc = (m_iHead + 1) % m_iSize;
m_piData1[loc] = SequenceNumber.incseq(seqno);
if (SequenceNumber.seqcmp(m_piData2[m_iHead], m_piData1[loc]) > 0)
m_piData2[loc] = m_piData2[m_iHead];
m_piData1[m_iHead] = -1;
m_piData2[m_iHead] = -1;
m_piNext[loc] = m_piNext[m_iHead];
m_iHead = loc;
}
m_iLength--;
return seqno;
}
}
}
public class RcvLossList
{
int[] m_piData1; // sequence number starts
int[] m_piData2; // sequence number ends
int[] m_piNext; // next node in the list
int[] m_piPrior; // prior node in the list;
int m_iHead; // first node in the list
int m_iTail; // last node in the list;
int m_iLength; // loss length
int m_iSize; // size of the static array
public RcvLossList(int size)
{
m_iHead = -1;
m_iTail = -1;
m_iLength = 0;
m_iSize = size;
m_piData1 = new int[m_iSize];
m_piData2 = new int[m_iSize];
m_piNext = new int[m_iSize];
m_piPrior = new int[m_iSize];
// -1 means there is no data in the node
for (int i = 0; i < size; ++i)
{
m_piData1[i] = -1;
m_piData2[i] = -1;
}
}
public void insert(int seqno1, int seqno2)
{
// Data to be inserted must be larger than all those in the list
// guaranteed by the UDT receiver
if (0 == m_iLength)
{
// insert data into an empty list
m_iHead = 0;
m_iTail = 0;
m_piData1[m_iHead] = seqno1;
if (seqno2 != seqno1)
m_piData2[m_iHead] = seqno2;
m_piNext[m_iHead] = -1;
m_piPrior[m_iHead] = -1;
m_iLength += SequenceNumber.seqlen(seqno1, seqno2);
return;
}
// otherwise searching for the position where the node should be
int offset = SequenceNumber.seqoff(m_piData1[m_iHead], seqno1);
int loc = (m_iHead + offset) % m_iSize;
if ((-1 != m_piData2[m_iTail]) && (SequenceNumber.incseq(m_piData2[m_iTail]) == seqno1))
{
// coalesce with prior node, e.g., [2, 5], [6, 7] becomes [2, 7]
loc = m_iTail;
m_piData2[loc] = seqno2;
}
else
{
// create new node
m_piData1[loc] = seqno1;
if (seqno2 != seqno1)
m_piData2[loc] = seqno2;
m_piNext[m_iTail] = loc;
m_piPrior[loc] = m_iTail;
m_piNext[loc] = -1;
m_iTail = loc;
}
m_iLength += SequenceNumber.seqlen(seqno1, seqno2);
}
public bool remove(int seqno)
{
if (0 == m_iLength)
return false;
// locate the position of "seqno" in the list
int offset = SequenceNumber.seqoff(m_piData1[m_iHead], seqno);
if (offset < 0)
return false;
int loc = (m_iHead + offset) % m_iSize;
if (seqno == m_piData1[loc])
{
// This is a seq. no. that starts the loss sequence
if (-1 == m_piData2[loc])
{
// there is only 1 loss in the sequence, delete it from the node
if (m_iHead == loc)
{
m_iHead = m_piNext[m_iHead];
if (-1 != m_iHead)
m_piPrior[m_iHead] = -1;
}
else
{
m_piNext[m_piPrior[loc]] = m_piNext[loc];
if (-1 != m_piNext[loc])
m_piPrior[m_piNext[loc]] = m_piPrior[loc];
else
m_iTail = m_piPrior[loc];
}
m_piData1[loc] = -1;
}
else
{
// there are more than 1 loss in the sequence
// move the node to the next and update the starter as the next loss inSeqNo(seqno)
// find next node
int j = (loc + 1) % m_iSize;
// remove the "seqno" and change the starter as next seq. no.
m_piData1[j] = SequenceNumber.incseq(m_piData1[loc]);
// process the sequence end
if (SequenceNumber.seqcmp(m_piData2[loc], SequenceNumber.incseq(m_piData1[loc])) > 0)
m_piData2[j] = m_piData2[loc];
// remove the current node
m_piData1[loc] = -1;
m_piData2[loc] = -1;
// update list pointer
m_piNext[j] = m_piNext[loc];
m_piPrior[j] = m_piPrior[loc];
if (m_iHead == loc)
m_iHead = j;
else
m_piNext[m_piPrior[j]] = j;
if (m_iTail == loc)
m_iTail = j;
else
m_piPrior[m_piNext[j]] = j;
}
m_iLength--;
return true;
}
// There is no loss sequence in the current position
// the "seqno" may be contained in a previous node
// searching previous node
int i = (loc - 1 + m_iSize) % m_iSize;
while (-1 == m_piData1[i])
i = (i - 1 + m_iSize) % m_iSize;
// not contained in this node, return
if ((-1 == m_piData2[i]) || (SequenceNumber.seqcmp(seqno, m_piData2[i]) > 0))
return false;
if (seqno == m_piData2[i])
{
// it is the sequence end
if (seqno == SequenceNumber.incseq(m_piData1[i]))
m_piData2[i] = -1;
else
m_piData2[i] = SequenceNumber.decseq(seqno);
}
else
{
// split the sequence
// construct the second sequence from SequenceNumber.incseq(seqno) to the original sequence end
// located at "loc + 1"
loc = (loc + 1) % m_iSize;
m_piData1[loc] = SequenceNumber.incseq(seqno);
if (SequenceNumber.seqcmp(m_piData2[i], m_piData1[loc]) > 0)
m_piData2[loc] = m_piData2[i];
// the first (original) sequence is between the original sequence start to SequenceNumber.decseq(seqno)
if (seqno == SequenceNumber.incseq(m_piData1[i]))
m_piData2[i] = -1;
else
m_piData2[i] = SequenceNumber.decseq(seqno);
// update the list pointer
m_piNext[loc] = m_piNext[i];
m_piNext[i] = loc;
m_piPrior[loc] = i;
if (m_iTail == i)
m_iTail = loc;
else
m_piPrior[m_piNext[loc]] = loc;
}
m_iLength--;
return true;
}
public bool remove(int seqno1, int seqno2)
{
if (seqno1 <= seqno2)
{
for (int i = seqno1; i <= seqno2; ++i)
remove(i);
}
else
{
for (int j = seqno1; j < SequenceNumber.m_iMaxSeqNo; ++j)
remove(j);
for (int k = 0; k <= seqno2; ++k)
remove(k);
}
return true;
}
bool find(int seqno1, int seqno2)
{
if (0 == m_iLength)
return false;
int p = m_iHead;
while (-1 != p)
{
if ((SequenceNumber.seqcmp(m_piData1[p], seqno1) == 0) ||
((SequenceNumber.seqcmp(m_piData1[p], seqno1) > 0) && (SequenceNumber.seqcmp(m_piData1[p], seqno2) <= 0)) ||
((SequenceNumber.seqcmp(m_piData1[p], seqno1) < 0) && (m_piData2[p] != -1) && SequenceNumber.seqcmp(m_piData2[p], seqno1) >= 0))
return true;
p = m_piNext[p];
}
return false;
}
public int getLossLength()
{
return m_iLength;
}
public int getFirstLostSeq()
{
if (0 == m_iLength)
return -1;
return m_piData1[m_iHead];
}
public void getLossArray(int[] array, out int len, int limit)
{
len = 0;
int i = m_iHead;
while ((len < limit - 1) && (-1 != i))
{
array[len] = m_piData1[i];
if (-1 != m_piData2[i])
{
// there are more than 1 loss in the sequence
array[len] = (int)((uint)array[len] | 0x80000000);
++len;
array[len] = m_piData2[i];
}
++len;
i = m_piNext[i];
}
}
}
}

View File

@@ -0,0 +1,38 @@
// UDT Message Number: 0 - (2^29 - 1)
using System;
namespace UdtSharp
{
static class MessageNumber
{
public static int msgcmp(int msgno1, int msgno2)
{
return (Math.Abs(msgno1 - msgno2) < m_iMsgNoTH) ? (msgno1 - msgno2) : (msgno2 - msgno1);
}
public static int msglen(int msgno1, int msgno2)
{
return (msgno1 <= msgno2) ? (msgno2 - msgno1 + 1) : (msgno2 - msgno1 + m_iMaxMsgNo + 2);
}
public static int msgoff(int msgno1, int msgno2)
{
if (Math.Abs(msgno1 - msgno2) < m_iMsgNoTH)
return msgno2 - msgno1;
if (msgno1 < msgno2)
return msgno2 - msgno1 - m_iMaxMsgNo - 1;
return msgno2 - msgno1 + m_iMaxMsgNo + 1;
}
public static int incmsg(int msgno)
{
return (msgno == m_iMaxMsgNo) ? 0 : msgno + 1;
}
static int m_iMsgNoTH = 0xFFFFFFF; // threshold for comparing msg. no.
public static int m_iMaxMsgNo = 0x1FFFFFFF; // maximum message number used in UDT
}
}

View File

@@ -0,0 +1,764 @@
//////////////////////////////////////////////////////////////////////////////
// 0 1 2 3
// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// | Packet Header |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// | |
// ~ Data / Control Information Field ~
// | |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
//
// 0 1 2 3
// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// |0| Sequence Number |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// |ff |o| Message Number |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// | Time Stamp |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// | Destination Socket ID |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
//
// bit 0:
// 0: Data Packet
// 1: Control Packet
// bit ff:
// 11: solo message packet
// 10: first packet of a message
// 01: last packet of a message
// bit o:
// 0: in order delivery not required
// 1: in order delivery required
//
// 0 1 2 3
// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// |1| Type | Reserved |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// | Additional Info |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// | Time Stamp |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// | Destination Socket ID |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
//
// bit 1-15:
// 0: Protocol Connection Handshake
// Add. Info: Undefined
// Control Info: Handshake information (see CHandShake)
// 1: Keep-alive
// Add. Info: Undefined
// Control Info: None
// 2: Acknowledgement (ACK)
// Add. Info: The ACK sequence number
// Control Info: The sequence number to which (but not include) all the previous packets have beed received
// Optional: RTT
// RTT Variance
// available receiver buffer size (in bytes)
// advertised flow window size (number of packets)
// estimated bandwidth (number of packets per second)
// 3: Negative Acknowledgement (NAK)
// Add. Info: Undefined
// Control Info: Loss list (see loss list coding below)
// 4: Congestion/Delay Warning
// Add. Info: Undefined
// Control Info: None
// 5: Shutdown
// Add. Info: Undefined
// Control Info: None
// 6: Acknowledgement of Acknowledement (ACK-square)
// Add. Info: The ACK sequence number
// Control Info: None
// 7: Message Drop Request
// Add. Info: Message ID
// Control Info: first sequence number of the message
// last seqeunce number of the message
// 8: Error Signal from the Peer Side
// Add. Info: Error code
// Control Info: None
// 0x7FFF: Explained by bits 16 - 31
//
// bit 16 - 31:
// This space is used for future expansion or user defined control packets.
//
// 0 1 2 3
// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// |1| Sequence Number a (first) |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// |0| Sequence Number b (last) |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// |0| Sequence Number (single) |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
//
// Loss List Field Coding:
// For any consectutive lost seqeunce numbers that the differnece between
// the last and first is more than 1, only record the first (a) and the
// the last (b) sequence numbers in the loss list field, and modify the
// the first bit of a to 1.
// For any single loss or consectutive loss less than 2 packets, use
// the original sequence numbers in the field.
using System;
using System.Net;
using System.Net.Sockets;
using System.Text;
namespace UdtSharp
{
public struct iovec
{
public uint[] iov_base;
public int iov_len;
}
public unsafe class Packet
{
public enum ControlType
{
Handshake = 0,
KeepAlive = 1,
Ack = 2,
Nak = 3,
CongestionWarning = 4,
Shutdown = 5,
Ack2 = 6,
DropMessage = 7,
Error = 8,
UserType = 32767,
}
const int m_iSeqNoIndex = 0; // alias: sequence number
const int m_iMsgNoIndex = 1; // alias: message number
const int m_iTimeStampIndex = 2; // alias: timestamp
const int m_iIdIndex = 3; // alias: socket ID
public const int m_iPktHdrSize = 16; // packet header size
iovec[] m_PacketVector = new iovec[2]; // The 2-demension vector of UDT packet [header, data]
public override string ToString()
{
StringBuilder stringBuilder = new StringBuilder();
if (getFlag() == 0)
{
byte[] data = GetDataBytes();
stringBuilder.AppendFormat("Data length {0} bytes", data != null ? data.Length : 0);
stringBuilder.AppendLine();
stringBuilder.AppendLine(" SeqNo " + GetSequenceNumber());
stringBuilder.AppendLine(" MsgNo " + GetMessageNumber());
stringBuilder.AppendLine(" Timestamp " + GetTimestamp());
stringBuilder.AppendLine(" SocketID " + GetId());
stringBuilder.Append(" Data: {");
if (data != null)
{
for (int i = 0; i < Math.Min(data.Length, 10); ++i)
{
stringBuilder.Append(data[i] + ",");
}
stringBuilder.Length = stringBuilder.Length - 1;
}
stringBuilder.AppendLine("}");
}
else if (getFlag() == 1)
{
int type = getType();
stringBuilder.AppendFormat("CTRL {0} ({1})", (Packet.ControlType)type, type);
stringBuilder.AppendLine();
switch (type)
{
case 2: //0010 - Acknowledgement (ACK)
stringBuilder.AppendFormat(" Ack sequence {0}", getAckSeqNo());
stringBuilder.AppendLine();
break;
case 6: //0110 - Acknowledgement of Acknowledgement (ACK-2)
stringBuilder.AppendFormat(" Ack2 sequence {0}", getAckSeqNo());
stringBuilder.AppendLine();
break;
case 3: //0011 - Loss Report (NAK)
break;
case 4: //0100 - Congestion Warning
break;
case 1: //0001 - Keep-alive
break;
case 0: //0000 - Handshake
// control info filed is handshake info
Handshake handshake = new Handshake();
handshake.deserialize(GetDataBytes(), Handshake.m_iContentSize);
stringBuilder.AppendFormat(handshake.ToString());
stringBuilder.AppendLine();
break;
case 5: //0101 - Shutdown
break;
case 7: //0111 - Message Drop Request
break;
case 8: //1000 - Error Signal from the Peer Side
// Error type
stringBuilder.AppendLine("Error: " + m_PacketVector[0].iov_base[m_iMsgNoIndex].ToString());
break;
case 32767: //0x7FFF - Reserved for user defined control packets
break;
default:
break;
}
}
return stringBuilder.ToString();
}
public Packet()
{
m_PacketVector[0].iov_base = new uint[4];
m_PacketVector[0].iov_len = m_iPktHdrSize;
m_PacketVector[1].iov_base = null;
m_PacketVector[1].iov_len = 0;
}
~Packet()
{
}
public void Clone(Packet source)
{
Buffer.BlockCopy(source.m_PacketVector[0].iov_base, 0, m_PacketVector[0].iov_base, 0, m_iPktHdrSize);
if (source.m_PacketVector[1].iov_base == null)
{
m_PacketVector[1].iov_base = null;
m_PacketVector[1].iov_len = source.m_PacketVector[1].iov_len;
return;
}
m_PacketVector[1].iov_base = new uint[source.m_PacketVector[1].iov_base.Length];
Buffer.BlockCopy(source.m_PacketVector[1].iov_base, 0, m_PacketVector[1].iov_base, 0, source.m_PacketVector[1].iov_len);
m_PacketVector[1].iov_len = source.m_PacketVector[1].iov_len;
}
public int GetSequenceNumber()
{
return (int)m_PacketVector[0].iov_base[m_iSeqNoIndex];
}
public void SetSequenceNumber(int sequenceNumber)
{
m_PacketVector[0].iov_base[m_iSeqNoIndex] = (uint)sequenceNumber;
}
public uint GetMessageNumber()
{
return m_PacketVector[0].iov_base[m_iMsgNoIndex];
}
public void SetMessageNumber(uint messageNumber)
{
m_PacketVector[0].iov_base[m_iMsgNoIndex] = messageNumber;
}
public int GetTimestamp()
{
return (int)m_PacketVector[0].iov_base[m_iTimeStampIndex];
}
public void SetTimestamp(int timestamp)
{
m_PacketVector[0].iov_base[m_iTimeStampIndex] = (uint)timestamp;
}
public int GetId()
{
return (int)m_PacketVector[0].iov_base[m_iIdIndex];
}
public void SetId(int id)
{
m_PacketVector[0].iov_base[m_iIdIndex] = (uint)id;
}
public byte[] GetBytes()
{
int dataLength = m_PacketVector[1].iov_len;
byte[] bytes = new byte[m_iPktHdrSize + dataLength];
Buffer.BlockCopy(m_PacketVector[0].iov_base, 0, bytes, 0, m_iPktHdrSize);
if (dataLength == 0 || m_PacketVector[1].iov_base == null)
return bytes;
Buffer.BlockCopy(m_PacketVector[1].iov_base, 0, bytes, m_iPktHdrSize, dataLength);
return bytes;
}
public byte[] GetHeaderBytes()
{
byte[] bytes = new byte[m_iPktHdrSize];
Buffer.BlockCopy(m_PacketVector[0].iov_base, 0, bytes, 0, m_iPktHdrSize);
return bytes;
}
public int GetDataBytes(int packetOffset, byte[] data, int dataOffset, int length)
{
if (m_PacketVector[1].iov_base == null)
return 0;
int bufferAvailable = data.Length - dataOffset;
if (bufferAvailable < length)
length = bufferAvailable;
Buffer.BlockCopy(m_PacketVector[1].iov_base, packetOffset, data, dataOffset, length);
return length;
}
public int GetIntFromData(int offset)
{
return (int)m_PacketVector[1].iov_base[offset];
}
public byte[] GetDataBytes()
{
if (m_PacketVector[1].iov_base == null)
return null;
int dataLength = m_PacketVector[1].iov_len;
if (dataLength <= 0)
return null;
byte[] bytes = new byte[dataLength];
Buffer.BlockCopy(m_PacketVector[1].iov_base, 0, bytes, 0, bytes.Length);
return bytes;
}
public bool SetHeaderAndDataFromBytes(byte[] bytes, int length)
{
if (length < m_iPktHdrSize)
return false;
Buffer.BlockCopy(bytes, 0, m_PacketVector[0].iov_base, 0, m_iPktHdrSize);
int dataLength = length - m_iPktHdrSize;
if (dataLength == 0)
{
m_PacketVector[1].iov_base = null;
m_PacketVector[1].iov_len = 0;
return true;
}
SetDataFromBytes(bytes, m_iPktHdrSize, dataLength);
return true;
}
public void SetDataFromBytes(byte[] bytes)
{
SetDataFromBytes(bytes, 0, bytes.Length);
}
public void SetDataFromBytes(byte[] bytes, int offset, int byteCount)
{
int intCount = byteCount / 4;
if (byteCount % 4 != 0)
++intCount;
m_PacketVector[1].iov_base = new uint[intCount];
m_PacketVector[1].iov_len = byteCount;
Buffer.BlockCopy(bytes, offset, m_PacketVector[1].iov_base, 0, byteCount);
}
public void ConvertControlInfoToNetworkOrder()
{
if (getFlag() == 0)
return;
if (m_PacketVector[1].iov_base == null || m_PacketVector[1].iov_len == 0)
return;
for (int i = 0; i < m_PacketVector[1].iov_base.Length; ++i)
{
m_PacketVector[1].iov_base[i] =
(uint)IPAddress.HostToNetworkOrder((int)m_PacketVector[1].iov_base[i]);
}
}
public void ConvertControlInfoToHostOrder()
{
if (getFlag() == 0)
return;
if (m_PacketVector[1].iov_base == null || m_PacketVector[1].iov_len == 0)
return;
for (int i = 0; i < m_PacketVector[1].iov_base.Length; ++i)
{
m_PacketVector[1].iov_base[i] =
(uint)IPAddress.NetworkToHostOrder((int)m_PacketVector[1].iov_base[i]);
}
}
public void ConvertHeaderToNetworkOrder()
{
for (int i = 0; i < m_PacketVector[0].iov_base.Length; ++i)
{
m_PacketVector[0].iov_base[i] =
(uint)IPAddress.HostToNetworkOrder((int)m_PacketVector[0].iov_base[i]);
}
}
public void ConvertHeaderToHostOrder()
{
for (int i = 0; i < m_PacketVector[0].iov_base.Length; ++i)
{
m_PacketVector[0].iov_base[i] =
(uint)IPAddress.NetworkToHostOrder((int)m_PacketVector[0].iov_base[i]);
}
}
public int getLength()
{
return m_PacketVector[1].iov_len;
}
public void setLength(int len)
{
m_PacketVector[1].iov_len = len;
}
static iovec MakeIovec(void* rparam, int size)
{
iovec result = new iovec();
result.iov_len = size;
if (rparam == null)
return result;
result.iov_base = new uint[size >> 2];
uint* pIn = (uint*)rparam;
for (int i = 0; i < size >> 2; ++i)
{
result.iov_base[i] = *pIn++;
}
return result;
}
public void pack(Handshake hs)
{
// TODO avoid this inefficient buffer creation
// we copy this buffer into another buffer later
byte[] bytes = new byte[Handshake.m_iContentSize];
hs.serialize(bytes);
pack(0, bytes);
}
public void pack(int pkttype, void* lparam)
{
if (pkttype != 6 && pkttype != 8)
throw new Exception("pkttype must be 6 or 8");
pack(pkttype, lparam, (void*)null, 0);
}
public void pack(int pkttype, byte[] rparam)
{
if (pkttype != 0)
throw new Exception("pkttype must be 0");
fixed (byte* prparam = rparam)
{
pack(pkttype, (void*)null, (void*)prparam, rparam.Length);
}
}
public void pack(int pkttype, int lparam, int[] rparam)
{
if (pkttype != 2)
throw new Exception("pkttype must be 2");
fixed (int* prparam = rparam)
{
pack(pkttype, &lparam, (void*)prparam, rparam.Length * 4);
}
}
public void pack(int pkttype, int lparam, int[] rparam, int length)
{
if (pkttype != 2)
throw new Exception("pkttype must be 2");
fixed (int* prparam = rparam)
{
pack(pkttype, &lparam, (void*)prparam, length * 4);
}
}
public void pack(int pkttype, int[] rparam, int length)
{
if (pkttype != 3)
throw new Exception("pkttype must be 3");
fixed (int* prparam = rparam)
{
pack(pkttype, (void*)null, (void*)prparam, length * 4);
}
}
public void pack(int pkttype)
{
if (pkttype != 1 && pkttype != 4 && pkttype != 5)
throw new Exception("pkttype must be 1, 4 or 5");
pack(pkttype, (void*)null, (void*)null, 0);
}
public void pack(int pkttype, void* lparam, void* rparam, int size)
{
// Set (bit-0 = 1) and (bit-1~15 = type)
m_PacketVector[0].iov_base[m_iSeqNoIndex] = (uint)0x80000000 | (uint)(pkttype << 16);
// Set additional information and control information field
switch (pkttype)
{
case 2: //0010 - Acknowledgement (ACK)
// ACK packet seq. no.
if (null != lparam)
m_PacketVector[0].iov_base[m_iMsgNoIndex] = *(uint*)lparam;
// data ACK seq. no.
// optional: RTT (microsends), RTT variance (microseconds) advertised flow window size (packets), and estimated link capacity (packets per second)
m_PacketVector[1] = MakeIovec(rparam, size);
break;
case 6: //0110 - Acknowledgement of Acknowledgement (ACK-2)
// ACK packet seq. no.
m_PacketVector[0].iov_base[m_iMsgNoIndex] = *(uint*)lparam;
// control info field should be none
// but "writev" does not allow this
m_PacketVector[1] = MakeIovec(null, 4);
break;
case 3: //0011 - Loss Report (NAK)
// loss list
m_PacketVector[1] = MakeIovec(rparam, size);
break;
case 4: //0100 - Congestion Warning
// control info field should be none
// but "writev" does not allow this
m_PacketVector[1] = MakeIovec(null, 4);
break;
case 1: //0001 - Keep-alive
// control info field should be none
// but "writev" does not allow this
m_PacketVector[1] = MakeIovec(null, 4);
break;
case 0: //0000 - Handshake
// control info filed is handshake info
m_PacketVector[1] = MakeIovec(rparam, size);
break;
case 5: //0101 - Shutdown
// control info field should be none
// but "writev" does not allow this
m_PacketVector[1] = MakeIovec(null, 4);
break;
case 7: //0111 - Message Drop Request
// msg id
m_PacketVector[0].iov_base[m_iMsgNoIndex] = *(uint*)lparam;
//first seq no, last seq no
m_PacketVector[1] = MakeIovec(rparam, size);
break;
case 8: //1000 - Error Signal from the Peer Side
// Error type
m_PacketVector[0].iov_base[m_iMsgNoIndex] = *(uint*)lparam;
// control info field should be none
// but "writev" does not allow this
m_PacketVector[1] = MakeIovec(null, 4);
break;
case 32767: //0x7FFF - Reserved for user defined control packets
// for extended control packet
// "lparam" contains the extended type information for bit 16 - 31
// "rparam" is the control information
m_PacketVector[0].iov_base[m_iSeqNoIndex] |= *(uint*)lparam;
if (null != rparam)
{
m_PacketVector[1] = MakeIovec(rparam, size);
}
else
{
m_PacketVector[1] = MakeIovec(null, 4);
}
break;
default:
break;
}
}
public iovec[] getPacketVector()
{
return m_PacketVector;
}
public int getFlag()
{
// read bit 0
return (int)(m_PacketVector[0].iov_base[m_iSeqNoIndex] >> 31);
}
public int getType()
{
// read bit 1~15
return (int)((m_PacketVector[0].iov_base[m_iSeqNoIndex] >> 16) & 0x00007FFF);
}
int getExtendedType()
{
// read bit 16~31
return (int)(m_PacketVector[0].iov_base[m_iSeqNoIndex] & 0x0000FFFF);
}
public int getAckSeqNo()
{
// read additional information field
return (int)m_PacketVector[0].iov_base[m_iMsgNoIndex];
}
public int getMsgBoundary()
{
// read [1] bit 0~1
return (int)(m_PacketVector[0].iov_base[m_iMsgNoIndex] >> 30);
}
public bool getMsgOrderFlag()
{
// read [1] bit 2
return (1 == ((m_PacketVector[0].iov_base[m_iMsgNoIndex] >> 29) & 1));
}
public int getMsgSeq()
{
// read [1] bit 3~31
return (int)(m_PacketVector[0].iov_base[m_iMsgNoIndex] & 0x1FFFFFFF);
}
}
public class Handshake
{
public const int m_iContentSize = 48; // Size of hand shake data
public int m_iVersion; // UDT version
public SocketType m_iType; // UDT socket type
public int m_iISN; // random initial sequence number
public int m_iMSS; // maximum segment size
public int m_iFlightFlagSize; // flow control window size
public int m_iReqType; // connection request type: 1: regular connection request, 0: rendezvous connection request, -1/-2: response
public int m_iID; // socket ID
public int m_iCookie; // cookie
public uint[] m_piPeerIP = new uint[4]; // The IP address that the peer's UDP port is bound to
public Handshake()
{
for (int i = 0; i < 4; ++i)
m_piPeerIP[i] = 0;
}
public override string ToString()
{
string type = "connection request";
if (m_iReqType == 0)
type = "rendezvouz";
if (m_iReqType < 0)
type = "reponse";
if (m_iReqType == 1002)
type = "rejected request";
StringBuilder sb = new StringBuilder();
sb.AppendLine(" Version " + m_iVersion);
sb.AppendLine(" Type " + type);
sb.AppendLine(" Cookie " + m_iCookie);
//sb.AppendLine(" Socket type " + m_iType.ToString());
//sb.AppendLine(" Socket id " + m_iID);
sb.AppendLine(" Initial seq# " + m_iISN);
//sb.AppendLine(" MSS " + m_iMSS);
//sb.AppendLine(" Flight size " + m_iFlightFlagSize);
return sb.ToString();
}
public unsafe void serialize(byte[] buf)
{
fixed (byte* pb = buf)
{
int* p = (int*)(pb);
*p++ = m_iVersion;
*p++ = (int)m_iType;
*p++ = m_iISN;
*p++ = m_iMSS;
*p++ = m_iFlightFlagSize;
*p++ = m_iReqType;
*p++ = m_iID;
*p++ = m_iCookie;
for (int i = 0; i < 4; ++i)
*p++ = (int)m_piPeerIP[i];
}
}
public unsafe bool deserialize(byte[] buf, int size)
{
if (size < m_iContentSize)
return false;
fixed (byte* pb = buf)
{
int* p = (int*)(pb);
m_iVersion = *p++;
m_iType = (SocketType)(*p++);
m_iISN = *p++;
m_iMSS = *p++;
m_iFlightFlagSize = *p++;
m_iReqType = *p++;
m_iID = *p++;
m_iCookie = *p++;
for (int i = 0; i < 4; ++i)
m_piPeerIP[i] = (uint)*p++;
}
return true;
}
}
}

View File

@@ -0,0 +1,903 @@
using System;
using System.Collections.Generic;
using System.Net;
using System.Net.Sockets;
using System.Threading;
namespace UdtSharp
{
public class SNode
{
public UDT m_pUDT; // Pointer to the instance of CUDT socket
public ulong m_llTimeStamp; // Time Stamp
public int m_iHeapLoc; // location on the heap, -1 means not on the heap
};
public class RNode
{
public UDT m_pUDT; // Pointer to the instance of CUDT socket
public ulong m_llTimeStamp; // Time Stamp
public bool m_bOnList; // if the node is already on the list
};
class UnitQueue
{
struct QEntry
{
internal Unit[] m_pUnit; // unit queue
internal byte[][] m_pBuffer; // data buffer
internal int m_iSize; // size of each queue
}
List<QEntry> mEntries = new List<QEntry>();
int m_iCurrEntry = 0;
int m_iLastEntry = 0;
int m_iAvailUnit; // recent available unit
int m_iAvailableQueue;
int m_iSize; // total size of the unit queue, in number of packets
public int m_iCount; // total number of valid packets in the queue
int m_iMSS; // unit buffer size
AddressFamily m_iIPversion; // IP version
UnitQueue()
{
m_iSize = 0;
m_iCount = 0;
m_iMSS = 0;
m_iIPversion = 0;
}
~UnitQueue()
{
}
int init(int size, int mss, AddressFamily version)
{
QEntry tempq = new QEntry();
Unit[] tempu = new Unit[size];
byte[][] tempb = new byte[size][];
for (int i = 0; i < size; ++i)
{
tempb[i] = new byte[mss];
tempu[i] = new Unit();
tempu[i].m_iFlag = 0;
tempu[i].m_Packet.SetDataFromBytes(tempb[i]);
}
tempq.m_pUnit = tempu;
tempq.m_pBuffer = tempb;
tempq.m_iSize = size;
m_iSize = size;
m_iMSS = mss;
m_iIPversion = version;
mEntries.Add(tempq);
return 0;
}
int increase()
{
// adjust/correct m_iCount
int real_count = 0;
for (int q = 0; q < mEntries.Count; ++q)
{
Unit[] units = mEntries[q].m_pUnit;
for (int u = mEntries[q].m_iSize; u < units.Length; ++u)
if (units[u].m_iFlag != 0)
++real_count;
}
m_iCount = real_count;
if ((double)m_iCount / m_iSize < 0.9)
return -1;
// all queues have the same size
int size = mEntries[0].m_iSize;
QEntry tempq = new QEntry();
Unit[] tempu = new Unit[size];
byte[][] tempb = new byte[size][];
for (int i = 0; i < size; ++i)
{
tempb[i] = new byte[m_iMSS];
tempu[i].m_iFlag = 0;
tempu[i].m_Packet.SetDataFromBytes(tempb[i]);
}
tempq.m_pUnit = tempu;
tempq.m_pBuffer = tempb;
tempq.m_iSize = size;
mEntries.Add(tempq);
m_iSize += size;
return 0;
}
int shrink()
{
// currently queue cannot be shrunk.
return -1;
}
Unit getNextAvailUnit()
{
if (m_iCount * 10 > m_iSize * 9)
increase();
if (m_iCount >= m_iSize)
return null;
QEntry entrance = mEntries[m_iCurrEntry];
//do
//{
// QEntry currentEntry = mEntries[m_iCurrEntry];
// Unit sentinel = currentEntry.m_pUnit[currentEntry.m_iSize - 1];
// for (CUnit* sentinel = m_pCurrQueue.m_pUnit + m_pCurrQueue.m_iSize - 1; m_pAvailUnit != sentinel; ++m_pAvailUnit)
// if (m_pAvailUnit.m_iFlag == 0)
// return m_pAvailUnit;
// if (m_pCurrQueue.m_pUnit.m_iFlag == 0)
// {
// m_pAvailUnit = m_pCurrQueue.m_pUnit;
// return m_pAvailUnit;
// }
// m_pCurrQueue = m_pCurrQueue.m_pNext;
// m_pAvailUnit = m_pCurrQueue.m_pUnit;
//} while (m_pCurrQueue != entrance);
increase();
return null;
}
}
public class SndUList
{
object m_ListLock = new object();
public object m_pWindowLock;
public EventWaitHandle m_pWindowCond;
SNode[] m_pHeap; // The heap array
int m_iArrayLength; // physical length of the array
int m_iLastEntry; // position of last entry on the heap array
public Timer m_pTimer;
public SndUList()
{
m_iArrayLength = 4096;
m_iLastEntry = -1;
m_pHeap = new SNode[m_iArrayLength];
}
public void insert(ulong ts, UDT u)
{
lock (m_ListLock)
{
// increase the heap array size if necessary
if (m_iLastEntry == m_iArrayLength - 1)
{
Array.Resize(ref m_pHeap, m_iArrayLength * 2);
m_iArrayLength *= 2;
}
insert_(ts, u);
}
}
public void update(UDT u, bool reschedule = true)
{
lock (m_ListLock)
{
SNode n = u.m_pSNode;
if (n.m_iHeapLoc >= 0)
{
if (!reschedule)
return;
if (n.m_iHeapLoc == 0)
{
n.m_llTimeStamp = 1;
m_pTimer.interrupt();
return;
}
remove_(u);
}
insert_(1, u);
}
}
public int pop(ref IPEndPoint addr, ref Packet pkt)
{
lock (m_ListLock)
{
if (-1 == m_iLastEntry)
return -1;
// no pop until the next schedulled time
ulong ts = Timer.rdtsc();
if (ts < m_pHeap[0].m_llTimeStamp)
return -1;
UDT u = m_pHeap[0].m_pUDT;
remove_(u);
if (!u.m_bConnected || u.m_bBroken)
return -1;
// pack a packet from the socket
if (u.packData(pkt, ref ts) <= 0)
return -1;
addr = u.m_pPeerAddr;
// insert a new entry, ts is the next processing time
if (ts > 0)
insert_(ts, u);
return 1;
}
}
public void remove(UDT u)
{
lock (m_ListLock)
{
remove_(u);
}
}
public ulong getNextProcTime()
{
lock (m_ListLock)
{
if (-1 == m_iLastEntry)
return 0;
return m_pHeap[0].m_llTimeStamp;
}
}
void insert_(ulong ts, UDT u)
{
SNode n = u.m_pSNode;
// do not insert repeated node
if (n.m_iHeapLoc >= 0)
return;
m_iLastEntry++;
m_pHeap[m_iLastEntry] = n;
n.m_llTimeStamp = ts;
int q = m_iLastEntry;
int p = q;
while (p != 0)
{
p = (q - 1) >> 1;
if (m_pHeap[p].m_llTimeStamp > m_pHeap[q].m_llTimeStamp)
{
SNode t = m_pHeap[p];
m_pHeap[p] = m_pHeap[q];
m_pHeap[q] = t;
t.m_iHeapLoc = q;
q = p;
}
else
break;
}
n.m_iHeapLoc = q;
// an earlier event has been inserted, wake up sending worker
if (n.m_iHeapLoc == 0)
m_pTimer.interrupt();
// first entry, activate the sending queue
if (0 == m_iLastEntry)
{
m_pWindowCond.Set();
}
}
void remove_(UDT u)
{
SNode n = u.m_pSNode;
if (n.m_iHeapLoc >= 0)
{
// remove the node from heap
m_pHeap[n.m_iHeapLoc] = m_pHeap[m_iLastEntry];
m_iLastEntry--;
m_pHeap[n.m_iHeapLoc].m_iHeapLoc = n.m_iHeapLoc;
int q = n.m_iHeapLoc;
int p = q * 2 + 1;
while (p <= m_iLastEntry)
{
if ((p + 1 <= m_iLastEntry) && (m_pHeap[p].m_llTimeStamp > m_pHeap[p + 1].m_llTimeStamp))
p++;
if (m_pHeap[q].m_llTimeStamp > m_pHeap[p].m_llTimeStamp)
{
SNode t = m_pHeap[p];
m_pHeap[p] = m_pHeap[q];
m_pHeap[p].m_iHeapLoc = p;
m_pHeap[q] = t;
m_pHeap[q].m_iHeapLoc = q;
q = p;
p = q * 2 + 1;
}
else
break;
}
n.m_iHeapLoc = -1;
}
// the only event has been deleted, wake up immediately
if (0 == m_iLastEntry)
m_pTimer.interrupt();
}
}
public class RendezvousQueue
{
struct CRL
{
internal int m_iID; // UDT socket ID (self)
internal UDT m_pUDT; // UDT instance
internal AddressFamily m_iIPversion; // IP version
internal IPEndPoint m_pPeerAddr; // UDT sonnection peer address
internal ulong m_ullTTL; // the time that this request expires
};
List<CRL> m_lRendezvousID = new List<CRL>(); // The sockets currently in rendezvous mode
object m_RIDVectorLock = new object();
public void insert(int id, UDT u, AddressFamily ipv, IPEndPoint addr, ulong ttl)
{
CRL r;
r.m_iID = id;
r.m_pUDT = u;
r.m_iIPversion = ipv;
r.m_pPeerAddr = addr;
r.m_ullTTL = ttl;
lock (m_RIDVectorLock)
{
m_lRendezvousID.Add(r);
}
}
public void remove(int id)
{
lock (m_RIDVectorLock)
{
for (int i = 0; i < m_lRendezvousID.Count; ++i)
{
if (m_lRendezvousID[i].m_iID == id)
{
m_lRendezvousID.RemoveAt(i);
return;
}
}
}
}
public UDT retrieve(IPEndPoint addr, ref int id)
{
lock (m_RIDVectorLock)
{
foreach (CRL crl in m_lRendezvousID)
{
if (crl.m_pPeerAddr.Equals(addr) && (id == 0) || (id == crl.m_iID))
{
id = crl.m_iID;
return crl.m_pUDT;
}
}
return null;
}
}
public void updateConnStatus()
{
if (m_lRendezvousID.Count == 0)
return;
lock (m_RIDVectorLock)
{
foreach (CRL crl in m_lRendezvousID)
{
// avoid sending too many requests, at most 1 request per 250ms
if (Timer.getTime() - (ulong)crl.m_pUDT.m_llLastReqTime > 250000)
{
//if (Timer.getTime() >= crl.m_ullTTL)
//{
// // connection timer expired, acknowledge app via epoll
// i->m_pUDT->m_bConnecting = false;
// CUDT::s_UDTUnited.m_EPoll.update_events(i->m_iID, i->m_pUDT->m_sPollID, UDT_EPOLL_ERR, true);
// continue;
//}
Packet request = new Packet();
request.pack(crl.m_pUDT.m_ConnReq);
// ID = 0, connection request
request.SetId(!crl.m_pUDT.m_bRendezvous ? 0 : crl.m_pUDT.m_ConnRes.m_iID);
crl.m_pUDT.m_pSndQueue.sendto(crl.m_pPeerAddr, request);
crl.m_pUDT.m_llLastReqTime = (long)Timer.getTime();
}
}
}
}
}
public class SndQueue
{
public SndUList m_pSndUList; // List of UDT instances for data sending
public Channel m_pChannel; // The UDP channel for data sending
Timer m_pTimer; // Timing facility
object m_WindowLock;
EventWaitHandle m_WindowCond;
volatile bool m_bClosing; // closing the worker
EventWaitHandle m_ExitCond;
Thread m_WorkerThread;
public SndQueue()
{
m_WindowLock = new object();
m_WindowCond = new EventWaitHandle(false, EventResetMode.AutoReset);
m_ExitCond = new EventWaitHandle(false, EventResetMode.AutoReset);
}
public void Close()
{
m_bClosing = true;
m_WindowCond.Set();
if (null != m_WorkerThread)
m_ExitCond.WaitOne(Timeout.Infinite);
m_WindowCond.Close();
m_ExitCond.Close();
}
public void init(Channel c, Timer t)
{
m_pChannel = c;
m_pTimer = t;
m_pSndUList = new SndUList();
m_pSndUList.m_pWindowLock = m_WindowLock;
m_pSndUList.m_pWindowCond = m_WindowCond;
m_pSndUList.m_pTimer = m_pTimer;
m_WorkerThread = new Thread(worker);
m_WorkerThread.IsBackground = true;
m_WorkerThread.Start(this);
}
static void worker(object param)
{
SndQueue self = param as SndQueue;
if (self == null)
return;
while (!self.m_bClosing)
{
ulong ts = self.m_pSndUList.getNextProcTime();
if (ts > 0)
{
// wait until next processing time of the first socket on the list
ulong currtime = Timer.rdtsc();
if (currtime < ts)
self.m_pTimer.sleepto(ts);
// it is time to send the next pkt
IPEndPoint addr = null;
Packet pkt = new Packet();
if (self.m_pSndUList.pop(ref addr, ref pkt) < 0)
continue;
self.m_pChannel.sendto(addr, pkt);
}
else
{
// wait here if there is no sockets with data to be sent
self.m_WindowCond.WaitOne(Timeout.Infinite);
}
}
self.m_ExitCond.Set();
}
public int sendto(IPEndPoint addr, Packet packet)
{
// send out the packet immediately (high priority), this is a control packet
m_pChannel.sendto(addr, packet);
return packet.getLength();
}
}
public class RcvUList
{
public List<RNode> m_nodeList = new List<RNode>();
public void insert(UDT u)
{
RNode n = u.m_pRNode;
n.m_llTimeStamp = Timer.rdtsc();
// always insert at the end for RcvUList
m_nodeList.Add(n);
}
public void remove(UDT u)
{
RNode n = u.m_pRNode;
if (!n.m_bOnList)
return;
m_nodeList.Remove(n);
}
public void update(UDT u)
{
RNode n = u.m_pRNode;
if (!n.m_bOnList)
return;
RNode match = m_nodeList.Find(x => x.Equals(n));
if (match.Equals(default(RNode)))
return;
match.m_llTimeStamp = Timer.rdtsc();
}
}
public class RcvQueue
{
RcvUList m_pRcvUList = new RcvUList(); // List of UDT instances that will read packets from the queue
Channel m_pChannel; // UDP channel for receving packets
Timer m_pTimer; // shared timer with the snd queue
int m_iPayloadSize; // packet payload size
volatile bool m_bClosing; // closing the workder
EventWaitHandle m_ExitCond;
object m_LSLock;
UDT m_pListener; // pointer to the (unique, if any) listening UDT entity
RendezvousQueue m_pRendezvousQueue = new RendezvousQueue(); // The list of sockets in rendezvous mode
List<UDT> m_vNewEntry = new List<UDT>(); // newly added entries, to be inserted
object m_IDLock;
Dictionary<int, Queue<Packet>> m_mBuffer = new Dictionary<int, Queue<Packet>>(); // temporary buffer for rendezvous connection request
object m_PassLock;
EventWaitHandle m_PassCond;
Thread m_WorkerThread;
Dictionary<int, UDT> m_hash = new Dictionary<int, UDT>();
public RcvQueue()
{
m_PassLock = new object();
m_PassCond = new EventWaitHandle(false, EventResetMode.AutoReset);
m_LSLock = new object();
m_IDLock = new object();
m_ExitCond = new EventWaitHandle(false, EventResetMode.AutoReset);
}
public void Close()
{
m_bClosing = true;
if (null != m_WorkerThread)
m_ExitCond.WaitOne(Timeout.Infinite);
m_PassCond.Close();
m_ExitCond.Close();
}
public void init(int qsize, int payload, AddressFamily version, int hsize, Channel cc, Timer t)
{
m_iPayloadSize = payload;
m_pChannel = cc;
m_pTimer = t;
m_WorkerThread = new Thread(worker);
m_WorkerThread.IsBackground = true;
m_WorkerThread.Start(this);
}
static void worker(object param)
{
RcvQueue self = param as RcvQueue;
if (self == null)
return;
IPEndPoint addr = new IPEndPoint(IPAddress.Any, 0);
UDT u = null;
int id;
while (!self.m_bClosing)
{
self.m_pTimer.tick();
// check waiting list, if new socket, insert it to the list
while (self.ifNewEntry())
{
UDT ne = self.getNewEntry();
if (null != ne)
{
self.m_pRcvUList.insert(ne);
self.m_hash.Add(ne.m_SocketID, ne);
}
}
// find next available slot for incoming packet
Unit unit = new Unit();
unit.m_Packet.setLength(self.m_iPayloadSize);
// reading next incoming packet, recvfrom returns -1 is nothing has been received
if (self.m_pChannel.recvfrom(ref addr, unit.m_Packet) < 0)
goto TIMER_CHECK;
id = unit.m_Packet.GetId();
// ID 0 is for connection request, which should be passed to the listening socket or rendezvous sockets
if (0 == id)
{
if (null != self.m_pListener)
self.m_pListener.listen(addr, unit.m_Packet);
else if (null != (u = self.m_pRendezvousQueue.retrieve(addr, ref id)))
{
// asynchronous connect: call connect here
// otherwise wait for the UDT socket to retrieve this packet
if (!u.m_bSynRecving)
u.connect(unit.m_Packet);
else
{
Packet newPacket = new Packet();
newPacket.Clone(unit.m_Packet);
self.storePkt(id, newPacket);
}
}
}
else if (id > 0)
{
if (self.m_hash.TryGetValue(id, out u))
{
if (addr.Equals(u.m_pPeerAddr))
{
if (u.m_bConnected && !u.m_bBroken && !u.m_bClosing)
{
if (0 == unit.m_Packet.getFlag())
u.processData(unit);
else
u.processCtrl(unit.m_Packet);
u.checkTimers();
self.m_pRcvUList.update(u);
}
}
}
else if (null != (u = self.m_pRendezvousQueue.retrieve(addr, ref id)))
{
if (!u.m_bSynRecving)
u.connect(unit.m_Packet);
else
{
Packet newPacket = new Packet();
newPacket.Clone(unit.m_Packet);
self.storePkt(id, newPacket);
}
}
}
TIMER_CHECK:
// take care of the timing event for all UDT sockets
ulong currtime = Timer.rdtsc();
ulong ctime = currtime - 100000 * Timer.getCPUFrequency();
for (int i = 0; i < self.m_pRcvUList.m_nodeList.Count; ++i)
{
RNode ul = self.m_pRcvUList.m_nodeList[0];
if (ul.m_llTimeStamp >= ctime)
break;
u = ul.m_pUDT;
if (u.m_bConnected && !u.m_bBroken && !u.m_bClosing)
{
u.checkTimers();
self.m_pRcvUList.update(u);
}
else
{
// the socket must be removed from Hash table first, then RcvUList
self.m_hash.Remove(u.m_SocketID);
self.m_pRcvUList.remove(u);
u.m_pRNode.m_bOnList = false;
}
}
// Check connection requests status for all sockets in the RendezvousQueue.
self.m_pRendezvousQueue.updateConnStatus();
}
self.m_ExitCond.Set();
}
public int recvfrom(int id, Packet packet)
{
bool gotLock = false;
Monitor.Enter(m_PassLock, ref gotLock);
Queue<Packet> packetQueue;
if (!m_mBuffer.TryGetValue(id, out packetQueue))
{
if (gotLock)
Monitor.Exit(m_PassLock);
m_PassCond.WaitOne(1000);
lock (m_PassLock)
{
if (!m_mBuffer.TryGetValue(id, out packetQueue))
{
packet.setLength(-1);
return -1;
}
}
}
if (gotLock && Monitor.IsEntered(m_PassLock))
Monitor.Exit(m_PassLock);
// retrieve the earliest packet
Packet newpkt = packetQueue.Peek();
if (packet.getLength() < newpkt.getLength())
{
packet.setLength(-1);
return -1;
}
// copy packet content
packet.Clone(newpkt);
packetQueue.Dequeue();
if (packetQueue.Count == 0)
{
lock (m_PassLock)
{
m_mBuffer.Remove(id);
}
}
return packet.getLength();
}
public int setListener(UDT u)
{
lock (m_LSLock)
{
if (null != m_pListener)
return -1;
m_pListener = u;
return 0;
}
}
public void removeListener(UDT u)
{
lock (m_LSLock)
{
if (u == m_pListener)
m_pListener = null;
}
}
public void registerConnector(int id, UDT u, AddressFamily ipv, IPEndPoint addr, ulong ttl)
{
m_pRendezvousQueue.insert(id, u, ipv, addr, ttl);
}
public void removeConnector(int id)
{
m_pRendezvousQueue.remove(id);
lock (m_PassLock)
{
m_mBuffer.Remove(id);
}
}
public void setNewEntry(UDT u)
{
lock (m_IDLock)
{
m_vNewEntry.Add(u);
}
}
bool ifNewEntry()
{
return !(m_vNewEntry.Count == 0);
}
UDT getNewEntry()
{
lock (m_IDLock)
{
if (m_vNewEntry.Count == 0)
return null;
UDT u = m_vNewEntry[0];
m_vNewEntry.RemoveAt(0);
return u;
}
}
void storePkt(int id, Packet pkt)
{
lock (m_PassLock)
{
Queue<Packet> packetQueue;
if (!m_mBuffer.TryGetValue(id, out packetQueue))
{
packetQueue = new Queue<Packet>();
packetQueue.Enqueue(pkt);
m_mBuffer.Add(id, packetQueue);
m_PassCond.Set();
}
else
{
//avoid storing too many packets, in case of malfunction or attack
if (packetQueue.Count > 16)
return;
packetQueue.Enqueue(pkt);
}
}
}
}
}

Some files were not shown because too many files have changed in this diff Show More