var Hwi = xdc.useModule('ti.sysbios.family.arm.m3.Hwi');
var Clock = xdc.useModule('ti.sysbios.knl.Clock');
var Seconds = xdc.useModule('ti.sysbios.hal.Seconds');
var BIOS = xdc.useModule('ti.sysbios.BIOS');
var SysMin = xdc.useModule('xdc.runtime.SysMin');
var System = xdc.useModule('xdc.runtime.System');
var Error = xdc.useModule('xdc.runtime.Error');
var ti_sysbios_hal_Hwi = xdc.useModule('ti.sysbios.hal.Hwi');
var Timestamp = xdc.useModule('xdc.runtime.Timestamp');
var TimestampProvider = xdc.useModule('ti.sysbios.family.arm.lm4.TimestampProvider');
var SyncGeneric = xdc.useModule('xdc.runtime.knl.SyncGeneric');
var Mailbox = xdc.useModule('ti.sysbios.knl.Mailbox');
//var HeapStd = xdc.useModule('xdc.runtime.HeapStd');
var GIO = xdc.useModule('ti.sysbios.io.GIO');
var Semaphore = xdc.useModule('ti.sysbios.knl.Semaphore');
var Task = xdc.useModule('ti.sysbios.knl.Task');
//var HeapMem = xdc.useModule('ti.sysbios.heaps.HeapMem');
var HeapTrack = xdc.useModule('ti.sysbios.heaps.HeapTrack');
//var Memory = xdc.useModule('xdc.runtime.Memory');
BIOS.heapSize = 60000; // bios heapmem
//Memory.defaultHeapSize = 20000;
//Program.heap = 40000;
//Program.stack = 4096;
Clock.timerId = 7;
var hwi0Params = new Hwi.Params();
hwi0Params.instance.name =pre { line-height: 125%; }
td.linenos .normal { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 5px; }
span.linenos { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 5px; }
td.linenos .special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; }
span.linenos.special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; }
.highlight .hll { background-color: #ffffcc }
.highlight .c { color: #888888 } /* Comment */
.highlight .err { color: #a61717; background-color: #e3d2d2 } /* Error */
.highlight .k { color: #008800; font-weight: bold } /* Keyword */
.highlight .ch { color: #888888 } /* Comment.Hashbang */
.highlight .cm { color: #888888 } /* Comment.Multiline */
.highlight .cp { color: #cc0000; font-weight: bold } /* Comment.Preproc */
.highlight .cpf { color: #888888 } /* Comment.PreprocFile */
.highlight .c1 { color: #888888 } /* Comment.Single */
.highlight .cs { color: #cc0000; font-weight: bold; background-color: #fff0f0 } /* Comment.Special */
.highlight .gd { color: #000000; background-color: #ffdddd } /* Generic.Deleted */
.highlight .ge { font-style: italic } /* Generic.Emph */
.highlight .ges { font-weight: bold; font-style: italic } /* Generic.EmphStrong */
.highlight .gr { color: #aa0000 } /* Generic.Error */
.highlight .gh { color: #333333 } /* Generic.Heading */
.highlight .gi { color: #000000; background-color: #ddffdd } /* Generic.Inserted */
.highlight .go { color: #888888 } /* Generic.Output */
.highlight .gp { color: #555555 } /* Generic.Prompt */
.highlight .gs { font-weight: bold } /* Generic.Strong */
.highlight .gu { color: #666666 } /* Generic.Subheading */
.highlight .gt { color: #aa0000 } /* Generic.Traceback */
.highlight .kc { color: #008800; font-weight: bold } /* Keyword.Constant */
.highlight .kd { color: #008800; font-weight: bold } /* Keyword.Declaration */
.highlight .kn { color: #008800; font-weight: bold } /* Keyword.Namespace */
.highlight .kp { color: #008800 } /* Keyword.Pseudo */
.highlight .kr { color: #008800; font-weight: bold } /* Keyword.Reserved */
.highlight .kt { color: #888888; font-weight: bold } /* Keyword.Type */
.highlight .m { color: #0000DD; font-weight: bold } /* Literal.Number */
.highlight .s { color: #dd2200; background-color: #fff0f0 } /* Literal.String */
.highlight .na { color: #336699 } /* Name.Attribute */
.highlight .nb { color: #003388 } /* Name.Builtin */
.highlight .nc { color: #bb0066; font-weight: bold } /* Name.Class */
.highlight .no { color: #003366; font-weight: bold } /* Name.Constant */
.highlight .nd { color: #555555 } /* Name.Decorator */
.highlight .ne { color: #bb0066; font-weight: bold } /* Name.Exception */
.highlight .nf { color: #0066bb; font-weight: bold } /* Name.Function */
.highlight .nl { color: #336699; font-style: italic } /* Name.Label */
.highlight .nn { color: #bb0066; font-weight: bold } /* Name.Namespace */
.highlight .py { color: #336699; font-weight: bold } /* Name.Property */
.highlight .nt { color: #bb0066; font-weight: bold } /* Name.Tag */
.highlight .nv { color: #336699 } /* Name.Variable */
.highlight .ow { color: #008800 } /* Operator.Word */
.highlight .w { color: #bbbbbb } /* Text.Whitespace */
.highlight .mb { color: #0000DD; font-weight: bold } /* Literal.Number.Bin */
.highlight .mf { color: #0000DD; font-weight: bold } /* Literal.Number.Float */
.highlight .mh { color: #0000DD; font-weight: bold } /* Literal.Number.Hex */
.highlight .mi { color: #0000DD; font-weight: bold } /* Literal.Number.Integer */
.highlight .mo { color: #0000DD; font-weight: bold } /* Literal.Number.Oct */
.highlight .sa { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Affix */
.highlight .sb { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Backtick */
.highlight .sc { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Char */
.highlight .dl { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Delimiter */
.highlight .sd { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Doc */
.highlight .s2 { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Double */
.highlight .se { color: #0044dd; background-color: #fff0f0 } /* Literal.String.Escape */
.highlight .sh { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Heredoc */
.highlight .si { color: #3333bb; background-color: #fff0f0 } /* Literal.String.Interpol */
.highlight .sx { color: #22bb22; background-color: #f0fff0 } /* Literal.String.Other */
.highlight .sr { color: #008800; background-color: #fff0ff } /* Literal.String.Regex */
.highlight .s1 { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Single */
.highlight .ss { color: #aa6600; background-color: #fff0f0 } /* Literal.String.Symbol */
.highlight .bp { color: #003388 } /* Name.Builtin.Pseudo */
.highlight .fm { color: #0066bb; font-weight: bold } /* Name.Function.Magic */
.highlight .vc { color: #336699 } /* Name.Variable.Class */
.highlight .vg { color: #dd7700 } /* Name.Variable.Global */
.highlight .vi { color: #3333bb } /* Name.Variable.Instance */
.highlight .vm { color: #336699 } /* Name.Variable.Magic */
.highlight .il { color: #0000DD; font-weight: bold } /* Literal.Number.Integer.Long */using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Drawing;
using System.Drawing.Imaging;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Tango.Core.Threading;
using WebRtc.NET;
namespace Tango.WebRTC
{
public class WebRtcClient : IDisposable
{
private ManagedConductor _conductor;
private Thread _conductorThread;
private TurboJpegEncoder _encoder;
private byte[] _bgrBufflocal;
private byte[] _imgBuf;
private GCHandle _bufHandle;
private IntPtr _imgBufPtr = IntPtr.Zero;
private bool _isDisposed;
private Bitmap _sendFrame;
private TaskCompletionSource<bool> _readyCompletionSource;
#region Events
public event EventHandler<NewIceCandidateEventArgs> NewIceCandidate;
public event EventHandler Ready;
public event EventHandler<DataMessageReceivedEventArgs<String>> TextMessageReceived;
public event EventHandler<DataMessageReceivedEventArgs<byte[]>> BinaryMessageReceived;
public event EventHandler<VideoFrameReceivedEventArgs> FrameReceived;
public event EventHandler<ErrorEventArgs> Error;
public event EventHandler Disconnected;
#endregion
#region Properties
private int _frameWidth;
public int FrameWidth
{
get { return _frameWidth; }
set
{
if (IsInitialized)
{
throw new InvalidOperationException("The frame height must be set before calling Init();");
}
_frameWidth = value;
}
}
private int _frameHeight;
public int FrameHeight
{
get { return _frameHeight; }
set
{
if (IsInitialized)
{
throw new InvalidOperationException("The frame width must be set before calling Init();");
}
_frameHeight = value;
}
}
private int _frameRate;
public int FrameRate
{
get { return _frameRate; }
set
{
if (IsInitialized)
{
throw new InvalidOperationException("The frame rate must be set before calling Init();");
}
_frameRate = value;
}
}
private String _dataChannelName;
public String DataChannelName
{
get { return _dataChannelName; }
set
{
if (IsInitialized)
{
throw new InvalidOperationException("The data channel must be set before calling Init();");
}
_dataChannelName = value;
}
}
public bool IsInitialized { get; private set; }
public bool IsReady { get; private set; }
#endregion
#region Constructors
public WebRtcClient()
{
FrameWidth = 640;
FrameHeight = 480;
FrameRate = 5;
DataChannelName = "DefaultChannelName";
}
public WebRtcClient(int frameWidth, int frameHeight, int frameRate) : this()
{
FrameWidth = frameWidth;
FrameHeight = frameHeight;
FrameRate = frameRate;
}
public WebRtcClient(int frameWidth, int frameHeight, int frameRate, String dataChannelName) : this(frameWidth, frameHeight, frameRate)
{
DataChannelName = dataChannelName;
}
#endregion
#region Init
public Task Init()
{
if (_isDisposed)
{
throw new ObjectDisposedException("This instance was already disposed.");
}
if (IsInitialized)
{
throw new InvalidOperationException("This instance was already initialized.");
}
TaskCompletionSource<bool> completion = new TaskCompletionSource<bool>();
_conductorThread = new Thread(() =>
{
Thread.Sleep(5); //Wait for function to return at least!
_conductor = new ManagedConductor();
_encoder = TurboJpegEncoder.CreateEncoder();
try
{
ManagedConductor.InitializeSSL();
//Stun
_conductor.AddServerConfig("stun:stun.l.google.com:19302", String.Empty, String.Empty);
_conductor.AddServerConfig("stun:stun1.l.google.com:19302", String.Empty, String.Empty);
_conductor.AddServerConfig("stun:stun2.l.google.com:19302", String.Empty, String.Empty);
_conductor.AddServerConfig("stun:stun3.l.google.com:19302", String.Empty, String.Empty);
_conductor.AddServerConfig("stun:stun4.l.google.com:19302", String.Empty, String.Empty);
_conductor.AddServerConfig("stun:eu-turn3.xirsys.com", "mjyn-kODdallq7iIZN1-eCYHo4GZy36urKu-8GTtdKwcuEUe8i4LjeHVoej-OePwAAAAAF56hb1Sb3liZW4=", "83b30e94-6e1c-11ea-b4c3-72c9c257b255");
//Turn
_conductor.AddServerConfig("turn:eu-turn3.xirsys.com:80?transport=udp", "mjyn-kODdallq7iIZN1-eCYHo4GZy36urKu-8GTtdKwcuEUe8i4LjeHVoej-OePwAAAAAF56hb1Sb3liZW4=", "83b30e94-6e1c-11ea-b4c3-72c9c257b255");
_conductor.AddServerConfig("turn:eu-turn3.xirsys.com:3478?transport=udp", "mjyn-kODdallq7iIZN1-eCYHo4GZy36urKu-8GTtdKwcuEUe8i4LjeHVoej-OePwAAAAAF56hb1Sb3liZW4=", "83b30e94-6e1c-11ea-b4c3-72c9c257b255");
_conductor.AddServerConfig("turn:eu-turn3.xirsys.com:80?transport=tcp", "mjyn-kODdallq7iIZN1-eCYHo4GZy36urKu-8GTtdKwcuEUe8i4LjeHVoej-OePwAAAAAF56hb1Sb3liZW4=", "83b30e94-6e1c-11ea-b4c3-72c9c257b255");
_conductor.AddServerConfig("turn:eu-turn3.xirsys.com:3478?transport=tcp", "mjyn-kODdallq7iIZN1-eCYHo4GZy36urKu-8GTtdKwcuEUe8i4LjeHVoej-OePwAAAAAF56hb1Sb3liZW4=", "83b30e94-6e1c-11ea-b4c3-72c9c257b255");
_conductor.AddServerConfig("turn:eu-turn3.xirsys.com:443?transport=tcp", "mjyn-kODdallq7iIZN1-eCYHo4GZy36urKu-8GTtdKwcuEUe8i4LjeHVoej-OePwAAAAAF56hb1Sb3liZW4=", "83b30e94-6e1c-11ea-b4c3-72c9c257b255");
_conductor.AddServerConfig("turn:eu-turn3.xirsys.com:5349?transport=tcp", "mjyn-kODdallq7iIZN1-eCYHo4GZy36urKu-8GTtdKwcuEUe8i4LjeHVoej-OePwAAAAAF56hb1Sb3liZW4=", "83b30e94-6e1c-11ea-b4c3-72c9c257b255");
_conductor.SetAudio(false);
_conductor.SetVideoCapturer(FrameWidth, FrameHeight, FrameRate, false);
if (!_conductor.InitializePeerConnection())
{
completion.SetException(new ApplicationException("Error initializing peer connection."));
return;
}
_conductor.CreateDataChannel(DataChannelName);
_conductor.OnIceCandidate += _conductor_OnIceCandidate;
_conductor.OnDataMessage += _conductor_OnDataMessage;
_conductor.OnDataBinaryMessage += _conductor_OnDataBinaryMessage;
_conductor.OnError += _conductor_OnError;
_conductor.OnFailure += _conductor_OnFailure;
_conductor.OnIceStateChanged += _conductor_OnIceStateChanged;
unsafe
{
_conductor.OnRenderRemote += _conductor_OnRenderRemote;
}
_conductor.ProcessMessages(1000);
}
catch (Exception ex)
{
completion.SetException(ex);
return;
}
IsInitialized = true;
completion.SetResult(true);
while (!_isDisposed)
{
_conductor.ProcessMessages(1000);
Thread.Sleep(10);
}
IsInitialized = false;
_conductor.OnIceCandidate -= _conductor_OnIceCandidate;
_conductor.OnDataMessage -= _conductor_OnDataMessage;
_conductor.OnDataBinaryMessage -= _conductor_OnDataBinaryMessage;
_conductor.OnError -= _conductor_OnError;
_conductor.OnFailure -= _conductor_OnFailure;
_conductor.OnIceStateChanged -= _conductor_OnIceStateChanged;
unsafe
{
_conductor.OnRenderRemote -= _conductor_OnRenderRemote;
}
_conductor.Dispose();
try
{
if (_sendFrame != null)
{
_sendFrame.Dispose();
}
}
catch { }
try
{
if (_bufHandle != null)
{
_bufHandle.Free();
}
}
catch { }
});
_conductorThread.SetApartmentState(ApartmentState.STA);
_conductorThread.IsBackground = true;
_conductorThread.Start();
return completion.Task;
}
private void _conductor_OnIceStateChanged(IceConnectionStates state)
{
if (!_isDisposed)
{
if (state == IceConnectionStates.kIceConnectionConnected)
{
OnReady();
}
else if (state == IceConnectionStates.kIceConnectionFailed)
{
Disconnected?.Invoke(this, new EventArgs());
}
}
}
#endregion
#region WebRTC Event Handlers
private void _conductor_OnIceCandidate(string sdp_mid, int sdp_mline_index, string sdp)
{
NewIceCandidate?.Invoke(this, new NewIceCandidateEventArgs()
{
IceCandidate = new IceCandidate()
{
SdpMid = sdp_mid,
SdpMLineIndex = sdp_mline_index,
Sdp = sdp
}
});
}
private void _conductor_OnDataMessage(string text)
{
OnTextMessageReceived(text);
}
private void _conductor_OnDataBinaryMessage(byte[] data)
{
OnBinaryMessageReceived(data);
}
unsafe private void _conductor_OnRenderRemote(byte* frame_buffer, uint w, uint h)
{
if (_isDisposed) return;
try
{
if (_encoder.EncodeI420toBGR24(frame_buffer, w, h, ref _bgrBufflocal, true) == 0)
{
var bufHandle = GCHandle.Alloc(_bgrBufflocal, GCHandleType.Pinned);
var bmp = new Bitmap((int)w, (int)h, (int)w * 3, PixelFormat.Format24bppRgb, bufHandle.AddrOfPinnedObject());
FrameReceived?.Invoke(this, new VideoFrameReceivedEventArgs()
{
Bitmap = bmp
});
}
}
catch (Exception ex)
{
Debug.WriteLine($"Error occurred while receiving the remote video frame.\n{ex.Message}");
}
}
private void _conductor_OnFailure(string error)
{
OnError(error);
}
private void _conductor_OnError()
{
OnError("Unspecified error.");
}
#endregion
#region Public Methods
public Task<Offer> CreateOffer()
{
EnsureInitialized();
TaskCompletionSource<Offer> completion = new TaskCompletionSource<Offer>();
ManagedConductor.OnCallbackSdp del = null;
bool completed = false;
del = (sdp) =>
{
if (!completed)
{
completed = true;
_conductor.OnSuccessOffer -= del;
completion.SetResult(new Offer() { Sdp = sdp });
}
};
_conductor.OnSuccessOffer += del;
TimeoutTask.StartNew(() =>
{
if (!completed)
{
completed = true;
completion.SetException(new TimeoutException("The offer was not created within the given time."));
}
}, TimeSpan.FromSeconds(10));
_conductor.CreateOffer();
return completion.Task;
}
public Task<Answer> CreateAnswer(Offer offer)
{
EnsureInitialized();
TaskCompletionSource<Answer> completion = new TaskCompletionSource<Answer>();
ManagedConductor.OnCallbackSdp del = null;
bool completed = false;
del = (sdp) =>
{
if (!completed)
{
completed = true;
_conductor.OnSuccessAnswer -= del;
completion.SetResult(new Answer() { Sdp = sdp });
}
};
_conductor.OnSuccessAnswer += del;
TimeoutTask.StartNew(() =>
{
if (!completed)
{
completed = true;
completion.SetException(new TimeoutException("The answer was not created within the given time."));
}
}, TimeSpan.FromSeconds(10));
_conductor.OnOfferRequest(offer.Sdp);
return completion.Task;
}
public void SetAnswer(Answer answer)
{
EnsureInitialized();
_conductor.OnOfferReply("answer", answer.Sdp);
}
public void SendText(String msg)
{
EnsureInitialized();
_conductor.DataChannelSendText(msg);
}
public void SendBinary(byte[] data)
{
EnsureInitialized();
_conductor.DataChannelSendData(data);
}
public void AddIceCandidate(IceCandidate ice)
{
EnsureInitialized();
ThreadFactory.StartNew(() =>
{
_conductor.AddIceCandidate(ice.SdpMid, ice.SdpMLineIndex, ice.Sdp);
});
}
public unsafe void PushFrame(Bitmap bitmap)
{
if (_isDisposed) return;
EnsureInitialized();
try
{
if (_sendFrame == null)
{
_imgBuf = new byte[FrameWidth * 3 * FrameHeight];
_bufHandle = GCHandle.Alloc(_imgBuf, GCHandleType.Pinned);
_imgBufPtr = _bufHandle.AddrOfPinnedObject();
_sendFrame = new Bitmap(FrameWidth, FrameHeight, FrameWidth * 3, PixelFormat.Format24bppRgb, _imgBufPtr);
}
using (var g = Graphics.FromImage(_sendFrame))
{
g.DrawImage(bitmap, new Rectangle(0, 0, _sendFrame.Width, _sendFrame.Height));
}
byte* firstYuv = _conductor.VideoCapturerI420Buffer();
int yuvSize = _encoder.EncodeI420((byte*)_imgBufPtr.ToPointer(), FrameWidth, FrameHeight, (int)TJPF.TJPF_BGR, 0, true, firstYuv);
_conductor.PushFrame();
}
catch (Exception ex)
{
Debug.WriteLine($"Error occurred while pushing the frame.\n{ex.Message}");
}
}
public Task WaitForReady(TimeSpan? timeout = null)
{
if (!IsReady)
{
if (timeout != null)
{
TimeoutTask.StartNew(() =>
{
if (!IsReady)
{
_readyCompletionSource.SetException(new TimeoutException("The connection was not ready within the given time."));
_readyCompletionSource = null;
}
}, timeout.Value);
}
_readyCompletionSource = new TaskCompletionSource<bool>();
return _readyCompletionSource.Task;
}
else
{
return Task.FromResult(true);
}
}
public void Dispose()
{
if (!_isDisposed)
{
_isDisposed = true;
}
}
#endregion
#region Virtual Methods
protected virtual void OnTextMessageReceived(String text)
{
TextMessageReceived?.Invoke(this, new DataMessageReceivedEventArgs<string>() { Data = text });
}
protected virtual void OnBinaryMessageReceived(byte[] data)
{
BinaryMessageReceived?.Invoke(this, new DataMessageReceivedEventArgs<byte[]>() { Data = data });
}
protected virtual void OnReady()
{
if (!IsReady)
{
IsReady = true;
Ready?.Invoke(this, new EventArgs());
if (_readyCompletionSource != null)
{
_readyCompletionSource.SetResult(true);
_readyCompletionSource = null;
}
}
}
protected virtual void OnError(String error)
{
Error?.Invoke(this, new ErrorEventArgs() { Error = error });
}
#endregion
#region Private Methods
private void EnsureInitialized()
{
if (!IsInitialized)
{
throw new InvalidOperationException("Invalid operation. The instance was not initialized using Init();");
}
}
#endregion
}
}