hawkeye camera support(not tested)
This commit is contained in:
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);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user