hawkeye camera support(not tested)
This commit is contained in:
159
framework/Inspectron.HawkEye/Camera/I2CLinux.cs
Normal file
159
framework/Inspectron.HawkEye/Camera/I2CLinux.cs
Normal 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 = ®Addr)
|
||||
{
|
||||
int ret = (int)Syscall.write(fd, ®Addr, 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
112
framework/Inspectron.HawkEye/Camera/InspectronCamera.cs
Normal file
112
framework/Inspectron.HawkEye/Camera/InspectronCamera.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
21
framework/Inspectron.HawkEye/Camera/LunixNatives.cs
Normal file
21
framework/Inspectron.HawkEye/Camera/LunixNatives.cs
Normal 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;
|
||||
|
||||
}
|
||||
}
|
||||
104
framework/Inspectron.HawkEye/DefragmentedPacket.cs
Normal file
104
framework/Inspectron.HawkEye/DefragmentedPacket.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
105
framework/Inspectron.HawkEye/FragmentedPacket.cs
Normal file
105
framework/Inspectron.HawkEye/FragmentedPacket.cs
Normal 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);
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
21
framework/Inspectron.HawkEye/Inspectron.HawkEye.csproj
Normal file
21
framework/Inspectron.HawkEye/Inspectron.HawkEye.csproj
Normal 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>
|
||||
9
framework/Inspectron.HawkEye/Packets/EPacketType.cs
Normal file
9
framework/Inspectron.HawkEye/Packets/EPacketType.cs
Normal file
@@ -0,0 +1,9 @@
|
||||
namespace Inspectron.HawkEye
|
||||
{
|
||||
public enum EPacketType
|
||||
{
|
||||
Test,
|
||||
ImageData,
|
||||
ImageRequest
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
namespace Inspectron.HawkEye
|
||||
{
|
||||
public class ImageRequestPacket
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
10
framework/Inspectron.HawkEye/Packets/Packet.cs
Normal file
10
framework/Inspectron.HawkEye/Packets/Packet.cs
Normal file
@@ -0,0 +1,10 @@
|
||||
using System;
|
||||
|
||||
namespace Inspectron.HawkEye
|
||||
{
|
||||
public class Packet
|
||||
{
|
||||
public EPacketType PacketType { get; set; }
|
||||
public byte[] Payload { get; set; }
|
||||
}
|
||||
}
|
||||
12
framework/Inspectron.HawkEye/Packets/PacketImage.cs
Normal file
12
framework/Inspectron.HawkEye/Packets/PacketImage.cs
Normal 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; }
|
||||
}
|
||||
}
|
||||
22
framework/Inspectron.HawkEye/Protocol/CameraSettings.cs
Normal file
22
framework/Inspectron.HawkEye/Protocol/CameraSettings.cs
Normal 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; }
|
||||
}
|
||||
}
|
||||
@@ -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; }
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
13
framework/Inspectron.HawkEye/Protocol/ECommand.cs
Normal file
13
framework/Inspectron.HawkEye/Protocol/ECommand.cs
Normal file
@@ -0,0 +1,13 @@
|
||||
namespace Inspectron.HawkEye.Protocol
|
||||
{
|
||||
public enum ECommand
|
||||
{
|
||||
Connect,
|
||||
Trigger, StartContinuous, StopContinuous,
|
||||
Settings,SaveSettings,
|
||||
OK,
|
||||
SaveCalibration,
|
||||
GetCalibration,
|
||||
NotOK
|
||||
}
|
||||
}
|
||||
9
framework/Inspectron.HawkEye/Protocol/EData.cs
Normal file
9
framework/Inspectron.HawkEye/Protocol/EData.cs
Normal file
@@ -0,0 +1,9 @@
|
||||
namespace Inspectron.HawkEye.Protocol
|
||||
{
|
||||
public enum EData
|
||||
{
|
||||
Image,
|
||||
|
||||
OK
|
||||
}
|
||||
}
|
||||
114
framework/Inspectron.HawkEye/Protocol/ImageClient.cs
Normal file
114
framework/Inspectron.HawkEye/Protocol/ImageClient.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
192
framework/Inspectron.HawkEye/Protocol/ImageClientTCP.cs
Normal file
192
framework/Inspectron.HawkEye/Protocol/ImageClientTCP.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
164
framework/Inspectron.HawkEye/Protocol/ImageServer.cs
Normal file
164
framework/Inspectron.HawkEye/Protocol/ImageServer.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
269
framework/Inspectron.HawkEye/Protocol/ImageServerTCP.cs
Normal file
269
framework/Inspectron.HawkEye/Protocol/ImageServerTCP.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
17
framework/Inspectron.HawkEye/Protocol/ImageSettings.cs
Normal file
17
framework/Inspectron.HawkEye/Protocol/ImageSettings.cs
Normal 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; }
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
namespace Inspectron.HawkEye.Protocol.Interfaces
|
||||
{
|
||||
public interface ICameraControl
|
||||
{
|
||||
|
||||
void SetParameters(CameraSettings imageSettings);
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
namespace Inspectron.HawkEye.Protocol.Interfaces
|
||||
{
|
||||
public interface IImageSource
|
||||
{
|
||||
byte[] GetImage();
|
||||
void CancelTrigger();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
namespace Inspectron.HawkEye.Protocol.Interfaces
|
||||
{
|
||||
public interface ILightControl
|
||||
{
|
||||
void SetLight(int pwm1, int pwm2);
|
||||
}
|
||||
}
|
||||
23
framework/Inspectron.HawkEye/Protocol/SocketExtensions.cs
Normal file
23
framework/Inspectron.HawkEye/Protocol/SocketExtensions.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
136
framework/Inspectron.HawkEye/RTSP/AACPayload.cs
Normal file
136
framework/Inspectron.HawkEye/RTSP/AACPayload.cs
Normal 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;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
34
framework/Inspectron.HawkEye/RTSP/AMRPayload.cs
Normal file
34
framework/Inspectron.HawkEye/RTSP/AMRPayload.cs
Normal 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;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
183
framework/Inspectron.HawkEye/RTSP/Authentication.cs
Normal file
183
framework/Inspectron.HawkEye/RTSP/Authentication.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
88
framework/Inspectron.HawkEye/RTSP/BitStream.cs
Normal file
88
framework/Inspectron.HawkEye/RTSP/BitStream.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
1128
framework/Inspectron.HawkEye/RTSP/Client/RTSPClient.cs
Normal file
1128
framework/Inspectron.HawkEye/RTSP/Client/RTSPClient.cs
Normal file
File diff suppressed because it is too large
Load Diff
64
framework/Inspectron.HawkEye/RTSP/G711Payload.cs
Normal file
64
framework/Inspectron.HawkEye/RTSP/G711Payload.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
184
framework/Inspectron.HawkEye/RTSP/H264Payload.cs
Normal file
184
framework/Inspectron.HawkEye/RTSP/H264Payload.cs
Normal 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;
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
224
framework/Inspectron.HawkEye/RTSP/H265Payload.cs
Normal file
224
framework/Inspectron.HawkEye/RTSP/H265Payload.cs
Normal 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;
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
41
framework/Inspectron.HawkEye/RTSP/IRTSPTransport.cs
Normal file
41
framework/Inspectron.HawkEye/RTSP/IRTSPTransport.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
103
framework/Inspectron.HawkEye/RTSP/Messages/PortCouple.cs
Normal file
103
framework/Inspectron.HawkEye/RTSP/Messages/PortCouple.cs
Normal 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);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
49
framework/Inspectron.HawkEye/RTSP/Messages/RTSPChunk.cs
Normal file
49
framework/Inspectron.HawkEye/RTSP/Messages/RTSPChunk.cs
Normal 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
|
||||
}
|
||||
}
|
||||
45
framework/Inspectron.HawkEye/RTSP/Messages/RTSPData.cs
Normal file
45
framework/Inspectron.HawkEye/RTSP/Messages/RTSPData.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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";
|
||||
}
|
||||
}
|
||||
309
framework/Inspectron.HawkEye/RTSP/Messages/RTSPMessage.cs
Normal file
309
framework/Inspectron.HawkEye/RTSP/Messages/RTSPMessage.cs
Normal 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;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
191
framework/Inspectron.HawkEye/RTSP/Messages/RTSPRequest.cs
Normal file
191
framework/Inspectron.HawkEye/RTSP/Messages/RTSPRequest.cs
Normal 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; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
namespace Inspectron.HawkEye.RTSP.Messages
|
||||
{
|
||||
public class RtspRequestAnnounce : RtspRequest
|
||||
{
|
||||
// constructor
|
||||
|
||||
public RtspRequestAnnounce()
|
||||
{
|
||||
Command = "ANNOUNCE * RTSP/1.0";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
namespace Inspectron.HawkEye.RTSP.Messages
|
||||
{
|
||||
public class RtspRequestDescribe : RtspRequest
|
||||
{
|
||||
|
||||
// constructor
|
||||
|
||||
public RtspRequestDescribe()
|
||||
{
|
||||
Command = "DESCRIBE * RTSP/1.0";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
namespace Inspectron.HawkEye.RTSP.Messages
|
||||
{
|
||||
public class RtspRequestGetParameter : RtspRequest
|
||||
{
|
||||
|
||||
// Constructor
|
||||
public RtspRequestGetParameter()
|
||||
{
|
||||
Command = "GET_PARAMETER * RTSP/1.0";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
namespace Inspectron.HawkEye.RTSP.Messages
|
||||
{
|
||||
public class RtspRequestPause : RtspRequest
|
||||
{
|
||||
|
||||
// Constructor
|
||||
public RtspRequestPause()
|
||||
{
|
||||
Command = "PAUSE * RTSP/1.0";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
namespace Inspectron.HawkEye.RTSP.Messages
|
||||
{
|
||||
public class RtspRequestPlay : RtspRequest
|
||||
{
|
||||
|
||||
// Constructor
|
||||
public RtspRequestPlay()
|
||||
{
|
||||
Command = "PLAY * RTSP/1.0";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
namespace Inspectron.HawkEye.RTSP.Messages
|
||||
{
|
||||
public class RtspRequestRecord : RtspRequest
|
||||
{
|
||||
public RtspRequestRecord()
|
||||
{
|
||||
Command = "RECORD * RTSP/1.0";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
namespace Inspectron.HawkEye.RTSP.Messages
|
||||
{
|
||||
public class RtspRequestTeardown : RtspRequest
|
||||
{
|
||||
|
||||
// Constructor
|
||||
public RtspRequestTeardown()
|
||||
{
|
||||
Command = "TEARDOWN * RTSP/1.0";
|
||||
}
|
||||
}
|
||||
}
|
||||
228
framework/Inspectron.HawkEye/RTSP/Messages/RTSPResponse.cs
Normal file
228
framework/Inspectron.HawkEye/RTSP/Messages/RTSPResponse.cs
Normal 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; }
|
||||
}
|
||||
}
|
||||
367
framework/Inspectron.HawkEye/RTSP/Messages/RTSPTransport.cs
Normal file
367
framework/Inspectron.HawkEye/RTSP/Messages/RTSPTransport.cs
Normal 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();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
557
framework/Inspectron.HawkEye/RTSP/RTSPListener.cs
Normal file
557
framework/Inspectron.HawkEye/RTSP/RTSPListener.cs
Normal 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
|
||||
}
|
||||
}
|
||||
27
framework/Inspectron.HawkEye/RTSP/RTSPMessageEventArgs.cs
Normal file
27
framework/Inspectron.HawkEye/RTSP/RTSPMessageEventArgs.cs
Normal 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; }
|
||||
}
|
||||
}
|
||||
118
framework/Inspectron.HawkEye/RTSP/RTSPTCPTransport.cs
Normal file
118
framework/Inspectron.HawkEye/RTSP/RTSPTCPTransport.cs
Normal 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;
|
||||
}*/
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
16
framework/Inspectron.HawkEye/RTSP/RTSPUtils.cs
Normal file
16
framework/Inspectron.HawkEye/RTSP/RTSPUtils.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
77
framework/Inspectron.HawkEye/RTSP/Sdp/Attribut.cs
Normal file
77
framework/Inspectron.HawkEye/RTSP/Sdp/Attribut.cs
Normal 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;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
75
framework/Inspectron.HawkEye/RTSP/Sdp/AttributFmtp.cs
Normal file
75
framework/Inspectron.HawkEye/RTSP/Sdp/AttributFmtp.cs
Normal 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 "";
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
76
framework/Inspectron.HawkEye/RTSP/Sdp/AttributRtpMap.cs
Normal file
76
framework/Inspectron.HawkEye/RTSP/Sdp/AttributRtpMap.cs
Normal 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];
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
15
framework/Inspectron.HawkEye/RTSP/Sdp/Bandwidth.cs
Normal file
15
framework/Inspectron.HawkEye/RTSP/Sdp/Bandwidth.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
48
framework/Inspectron.HawkEye/RTSP/Sdp/Connection.cs
Normal file
48
framework/Inspectron.HawkEye/RTSP/Sdp/Connection.cs
Normal 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]));
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
40
framework/Inspectron.HawkEye/RTSP/Sdp/ConnectionIP4.cs
Normal file
40
framework/Inspectron.HawkEye/RTSP/Sdp/ConnectionIP4.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
31
framework/Inspectron.HawkEye/RTSP/Sdp/ConnectionIP6.cs
Normal file
31
framework/Inspectron.HawkEye/RTSP/Sdp/ConnectionIP6.cs
Normal 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;
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
22
framework/Inspectron.HawkEye/RTSP/Sdp/EncriptionKey.cs
Normal file
22
framework/Inspectron.HawkEye/RTSP/Sdp/EncriptionKey.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
136
framework/Inspectron.HawkEye/RTSP/Sdp/H264Parameter.cs
Normal file
136
framework/Inspectron.HawkEye/RTSP/Sdp/H264Parameter.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
149
framework/Inspectron.HawkEye/RTSP/Sdp/H265Parameter.cs
Normal file
149
framework/Inspectron.HawkEye/RTSP/Sdp/H265Parameter.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
60
framework/Inspectron.HawkEye/RTSP/Sdp/Media.cs
Normal file
60
framework/Inspectron.HawkEye/RTSP/Sdp/Media.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
101
framework/Inspectron.HawkEye/RTSP/Sdp/Origin.cs
Normal file
101
framework/Inspectron.HawkEye/RTSP/Sdp/Origin.cs
Normal 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,
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
267
framework/Inspectron.HawkEye/RTSP/Sdp/SdpFile.cs
Normal file
267
framework/Inspectron.HawkEye/RTSP/Sdp/SdpFile.cs
Normal 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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
26
framework/Inspectron.HawkEye/RTSP/Sdp/SdpTimeZone.cs
Normal file
26
framework/Inspectron.HawkEye/RTSP/Sdp/SdpTimeZone.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
15
framework/Inspectron.HawkEye/RTSP/Sdp/Timing.cs
Normal file
15
framework/Inspectron.HawkEye/RTSP/Sdp/Timing.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
401
framework/Inspectron.HawkEye/RTSP/Server/CJOCh264bitstream.cs
Normal file
401
framework/Inspectron.HawkEye/RTSP/Server/CJOCh264bitstream.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
498
framework/Inspectron.HawkEye/RTSP/Server/CJOCh264encoder.cs
Normal file
498
framework/Inspectron.HawkEye/RTSP/Server/CJOCh264encoder.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
34
framework/Inspectron.HawkEye/RTSP/Server/RTPPacketUtil.cs
Normal file
34
framework/Inspectron.HawkEye/RTSP/Server/RTPPacketUtil.cs
Normal 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));
|
||||
}
|
||||
}
|
||||
}
|
||||
800
framework/Inspectron.HawkEye/RTSP/Server/RtspServer.cs
Normal file
800
framework/Inspectron.HawkEye/RTSP/Server/RtspServer.cs
Normal 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
|
||||
}
|
||||
}
|
||||
}
|
||||
109
framework/Inspectron.HawkEye/RTSP/Server/SimpleH264Encoder.cs
Normal file
109
framework/Inspectron.HawkEye/RTSP/Server/SimpleH264Encoder.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
209
framework/Inspectron.HawkEye/RTSP/Server/TestCard.cs
Normal file
209
framework/Inspectron.HawkEye/RTSP/Server/TestCard.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
166
framework/Inspectron.HawkEye/RTSP/Server/TinyH264Encoder.cs
Normal file
166
framework/Inspectron.HawkEye/RTSP/Server/TinyH264Encoder.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
231
framework/Inspectron.HawkEye/RTSP/UdpSocket.cs
Normal file
231
framework/Inspectron.HawkEye/RTSP/UdpSocket.cs
Normal 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);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
106
framework/Inspectron.HawkEye/TCPSocket.cs
Normal file
106
framework/Inspectron.HawkEye/TCPSocket.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
9
framework/Inspectron.HawkEye/UDPB/Block.cs
Normal file
9
framework/Inspectron.HawkEye/UDPB/Block.cs
Normal file
@@ -0,0 +1,9 @@
|
||||
namespace Inspectron.HawkEye.UDPB
|
||||
{
|
||||
public class Block
|
||||
{
|
||||
public byte[] Data;
|
||||
public uint Length;
|
||||
public uint MessageNumber;
|
||||
}
|
||||
}
|
||||
91
framework/Inspectron.HawkEye/UDPB/UDPBPacket.cs
Normal file
91
framework/Inspectron.HawkEye/UDPB/UDPBPacket.cs
Normal 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);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
77
framework/Inspectron.HawkEye/UDPB/UDPBQueue.cs
Normal file
77
framework/Inspectron.HawkEye/UDPB/UDPBQueue.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
127
framework/Inspectron.HawkEye/UDPB/UDPBReceiveBuffer.cs
Normal file
127
framework/Inspectron.HawkEye/UDPB/UDPBReceiveBuffer.cs
Normal 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;
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
142
framework/Inspectron.HawkEye/UDPB/UDPBSequence.cs
Normal file
142
framework/Inspectron.HawkEye/UDPB/UDPBSequence.cs
Normal 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; }
|
||||
}
|
||||
}
|
||||
220
framework/Inspectron.HawkEye/UDPB/UDPBSocket.cs
Normal file
220
framework/Inspectron.HawkEye/UDPB/UDPBSocket.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
17
framework/Inspectron.HawkEye/UDPServer.cs
Normal file
17
framework/Inspectron.HawkEye/UDPServer.cs
Normal 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)
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
106
framework/Inspectron.HawkEye/UDPSocket.cs
Normal file
106
framework/Inspectron.HawkEye/UDPSocket.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
14
framework/Inspectron.HawkEye/UDT/AckNumber.cs
Normal file
14
framework/Inspectron.HawkEye/UDT/AckNumber.cs
Normal 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
|
||||
}
|
||||
}
|
||||
476
framework/Inspectron.HawkEye/UDT/Buffer.cs
Normal file
476
framework/Inspectron.HawkEye/UDT/Buffer.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
217
framework/Inspectron.HawkEye/UDT/Channel.cs
Normal file
217
framework/Inspectron.HawkEye/UDT/Channel.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
362
framework/Inspectron.HawkEye/UDT/CongestionControl.cs
Normal file
362
framework/Inspectron.HawkEye/UDT/CongestionControl.cs
Normal 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;
|
||||
*/
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
22
framework/Inspectron.HawkEye/UDT/CongestionControlFactory.cs
Normal file
22
framework/Inspectron.HawkEye/UDT/CongestionControlFactory.cs
Normal 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>();
|
||||
}
|
||||
}
|
||||
}
|
||||
2530
framework/Inspectron.HawkEye/UDT/Core.cs
Normal file
2530
framework/Inspectron.HawkEye/UDT/Core.cs
Normal file
File diff suppressed because it is too large
Load Diff
19
framework/Inspectron.HawkEye/UDT/CoreExtensions.cs
Normal file
19
framework/Inspectron.HawkEye/UDT/CoreExtensions.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
7
framework/Inspectron.HawkEye/UDT/ERequestType.cs
Normal file
7
framework/Inspectron.HawkEye/UDT/ERequestType.cs
Normal file
@@ -0,0 +1,7 @@
|
||||
namespace UdtSharp
|
||||
{
|
||||
public enum ERequestType
|
||||
{
|
||||
Trigger
|
||||
}
|
||||
}
|
||||
111
framework/Inspectron.HawkEye/UDT/InfoBlock.cs
Normal file
111
framework/Inspectron.HawkEye/UDT/InfoBlock.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
672
framework/Inspectron.HawkEye/UDT/LossList.cs
Normal file
672
framework/Inspectron.HawkEye/UDT/LossList.cs
Normal 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];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
38
framework/Inspectron.HawkEye/UDT/MessageNumber.cs
Normal file
38
framework/Inspectron.HawkEye/UDT/MessageNumber.cs
Normal 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
|
||||
}
|
||||
}
|
||||
764
framework/Inspectron.HawkEye/UDT/Packet.cs
Normal file
764
framework/Inspectron.HawkEye/UDT/Packet.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
903
framework/Inspectron.HawkEye/UDT/Queue.cs
Normal file
903
framework/Inspectron.HawkEye/UDT/Queue.cs
Normal 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
Reference in New Issue
Block a user