hawkeye camera support(not tested)

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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