This directory contains an example Unity native plugin for Windows OS and Android. The APIs use Platform Invoke (P/Invoke) technology as required by Unity native plugin. This plugin dll can also be used by Windows C# applications other than Unity. For detailed build instruction on Android, see ANDROID_INSTRUCTION An example of wrapping native plugin into a C# managed class in Unity is given as following: using System; using System.Collections.Generic; using System.Runtime.InteropServices; namespace SimplePeerConnectionM { // A class for ice candidate. public class IceCandidate { public IceCandidate(string candidate, int sdpMlineIndex, string sdpMid) { mCandidate = candidate; mSdpMlineIndex = sdpMlineIndex; mSdpMid = sdpMid; } string mCandidate; int mSdpMlineIndex; string mSdpMid; public string Candidate { get { return mCandidate; } set { mCandidate = value; } } public int SdpMlineIndex { get { return mSdpMlineIndex; } set { mSdpMlineIndex = value; } } public string SdpMid { get { return mSdpMid; } set { mSdpMid = value; } } } // A managed wrapper up class for the native c style peer connection APIs. public class PeerConnectionM { private const string dllPath = "webrtc_unity_plugin"; //create a peerconnection with turn servers [DllImport(dllPath, CallingConvention = CallingConvention.Cdecl)] private static extern int CreatePeerConnection(string[] turnUrls, int noOfUrls, string username, string credential); [DllImport(dllPath, CallingConvention = CallingConvention.Cdecl)] private static extern bool ClosePeerConnection(int peerConnectionId); [DllImport(dllPath, CallingConvention = CallingConvention.Cdecl)] private static extern bool AddStream(int peerConnectionId, bool audioOnly); [DllImport(dllPath, CallingConvention = CallingConvention.Cdecl)] private static extern bool AddDataChannel(int peerConnectionId); [DllImport(dllPath, CallingConvention = CallingConvention.Cdecl)] private static extern bool CreateOffer(int peerConnectionId); [DllImport(dllPath, CallingConvention = CallingConvention.Cdecl)] private static extern bool CreateAnswer(int peerConnectionId); [DllImport(dllPath, CallingConvention = CallingConvention.Cdecl)] private static extern bool SendDataViaDataChannel(int peerConnectionId, string data); [DllImport(dllPath, CallingConvention = CallingConvention.Cdecl)] private static extern bool SetAudioControl(int peerConnectionId, bool isMute, bool isRecord); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] private delegate void LocalDataChannelReadyInternalDelegate(); public delegate void LocalDataChannelReadyDelegate(int id); [DllImport(dllPath, CallingConvention = CallingConvention.Cdecl)] private static extern bool RegisterOnLocalDataChannelReady( int peerConnectionId, LocalDataChannelReadyInternalDelegate callback); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] private delegate void DataFromDataChannelReadyInternalDelegate(string s); public delegate void DataFromDataChannelReadyDelegate(int id, string s); [DllImport(dllPath, CallingConvention = CallingConvention.Cdecl)] private static extern bool RegisterOnDataFromDataChannelReady( int peerConnectionId, DataFromDataChannelReadyInternalDelegate callback); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] private delegate void FailureMessageInternalDelegate(string msg); public delegate void FailureMessageDelegate(int id, string msg); [DllImport(dllPath, CallingConvention = CallingConvention.Cdecl)] private static extern bool RegisterOnFailure(int peerConnectionId, FailureMessageInternalDelegate callback); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] private delegate void AudioBusReadyInternalDelegate(IntPtr data, int bitsPerSample, int sampleRate, int numberOfChannels, int numberOfFrames); public delegate void AudioBusReadyDelegate(int id, IntPtr data, int bitsPerSample, int sampleRate, int numberOfChannels, int numberOfFrames); [DllImport(dllPath, CallingConvention = CallingConvention.Cdecl)] private static extern bool RegisterOnAudioBusReady(int peerConnectionId, AudioBusReadyInternalDelegate callback); // Video callbacks. [UnmanagedFunctionPointer(CallingConvention.Cdecl)] private delegate void I420FrameReadyInternalDelegate( IntPtr dataY, IntPtr dataU, IntPtr dataV, int strideY, int strideU, int strideV, uint width, uint height); public delegate void I420FrameReadyDelegate(int id, IntPtr dataY, IntPtr dataU, IntPtr dataV, int strideY, int strideU, int strideV, uint width, uint height); [DllImport(dllPath, CallingConvention = CallingConvention.Cdecl)] private static extern bool RegisterOnLocalI420FrameReady(int peerConnectionId, I420FrameReadyInternalDelegate callback); [DllImport(dllPath, CallingConvention = CallingConvention.Cdecl)] private static extern bool RegisterOnRemoteI420FrameReady(int peerConnectionId, I420FrameReadyInternalDelegate callback); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] private delegate void LocalSdpReadytoSendInternalDelegate(string type, string sdp); public delegate void LocalSdpReadytoSendDelegate(int id, string type, string sdp); [DllImport(dllPath, CallingConvention = CallingConvention.Cdecl)] private static extern bool RegisterOnLocalSdpReadytoSend(int peerConnectionId, LocalSdpReadytoSendInternalDelegate callback); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] private delegate void IceCandidateReadytoSendInternalDelegate( string candidate, int sdpMlineIndex, string sdpMid); public delegate void IceCandidateReadytoSendDelegate( int id, string candidate, int sdpMlineIndex, string sdpMid); [DllImport(dllPath, CallingConvention = CallingConvention.Cdecl)] private static extern bool RegisterOnIceCandidateReadytoSend( int peerConnectionId, IceCandidateReadytoSendInternalDelegate callback); [DllImport(dllPath, CallingConvention = CallingConvention.Cdecl)] private static extern bool SetRemoteDescription(int peerConnectionId, string type, string sdp); [DllImport(dllPath, CallingConvention = CallingConvention.Cdecl)] private static extern bool AddIceCandidate(int peerConnectionId, string sdp, int sdpMlineindex, string sdpMid); public PeerConnectionM(List turnUrls, string username, string credential) { string[] urls = turnUrls != null ? turnUrls.ToArray() : null; int length = turnUrls != null ? turnUrls.Count : 0; mPeerConnectionId = CreatePeerConnection(urls, length, username, credential); RegisterCallbacks(); } public void ClosePeerConnection() { ClosePeerConnection(mPeerConnectionId); mPeerConnectionId = -1; } // Return -1 if Peerconnection is not available. public int GetUniqueId() { return mPeerConnectionId; } public void AddStream(bool audioOnly) { AddStream(mPeerConnectionId, audioOnly); } public void AddDataChannel() { AddDataChannel(mPeerConnectionId); } public void CreateOffer() { CreateOffer(mPeerConnectionId); } public void CreateAnswer() { CreateAnswer(mPeerConnectionId); } public void SendDataViaDataChannel(string data) { SendDataViaDataChannel(mPeerConnectionId, data); } public void SetAudioControl(bool isMute, bool isRecord) { SetAudioControl(mPeerConnectionId, isMute, isRecord); } public void SetRemoteDescription(string type, string sdp) { SetRemoteDescription(mPeerConnectionId, type, sdp); } public void AddIceCandidate(string candidate, int sdpMlineindex, string sdpMid) { AddIceCandidate(mPeerConnectionId, candidate, sdpMlineindex, sdpMid); } private void RegisterCallbacks() { localDataChannelReadyDelegate = new LocalDataChannelReadyInternalDelegate( RaiseLocalDataChannelReady); RegisterOnLocalDataChannelReady(mPeerConnectionId, localDataChannelReadyDelegate); dataFromDataChannelReadyDelegate = new DataFromDataChannelReadyInternalDelegate( RaiseDataFromDataChannelReady); RegisterOnDataFromDataChannelReady(mPeerConnectionId, dataFromDataChannelReadyDelegate); failureMessageDelegate = new FailureMessageInternalDelegate(RaiseFailureMessage); RegisterOnFailure(mPeerConnectionId, failureMessageDelegate); audioBusReadyDelegate = new AudioBusReadyInternalDelegate(RaiseAudioBusReady); RegisterOnAudioBusReady(mPeerConnectionId, audioBusReadyDelegate); localI420FrameReadyDelegate = new I420FrameReadyInternalDelegate( RaiseLocalVideoFrameReady); RegisterOnLocalI420FrameReady(mPeerConnectionId, localI420FrameReadyDelegate); remoteI420FrameReadyDelegate = new I420FrameReadyInternalDelegate( RaiseRemoteVideoFrameReady); RegisterOnRemoteI420FrameReady(mPeerConnectionId, remoteI420FrameReadyDelegate); localSdpReadytoSendDelegate = new LocalSdpReadytoSendInternalDelegate( RaiseLocalSdpReadytoSend); RegisterOnLocalSdpReadytoSend(mPeerConnectionId, localSdpReadytoSendDelegate); iceCandidateReadytoSendDelegate = new IceCandidateReadytoSendInternalDelegate(RaiseIceCandidateReadytoSend); RegisterOnIceCandidateReadytoSend( mPeerConnectionId, iceCandidateReadytoSendDelegate); } private void RaiseLocalDataChannelReady() { if (OnLocalDataChannelReady != null) OnLocalDataChannelReady(mPeerConnectionId); } private void RaiseDataFromDataChannelReady(string data) { if (OnDataFromDataChannelReady != null) OnDataFromDataChannelReady(mPeerConnectionId, data); } private void RaiseFailureMessage(string msg) { if (OnFailureMessage != null) OnFailureMessage(mPeerConnectionId, msg); } private void RaiseAudioBusReady(IntPtr data, int bitsPerSample, int sampleRate, int numberOfChannels, int numberOfFrames) { if (OnAudioBusReady != null) OnAudioBusReady(mPeerConnectionId, data, bitsPerSample, sampleRate, numberOfChannels, numberOfFrames); } private void RaiseLocalVideoFrameReady( IntPtr dataY, IntPtr dataU, IntPtr dataV, int strideY, int strideU, int strideV, uint width, uint height) { if (OnLocalVideoFrameReady != null) OnLocalVideoFrameReady(mPeerConnectionId, dataY, dataU, dataV, strideY, strideU, strideV, width, height); } private void RaiseRemoteVideoFrameReady( IntPtr dataY, IntPtr dataU, IntPtr dataV, int strideY, int strideU, int strideV, uint width, uint height) { if (OnRemoteVideoFrameReady != null) OnRemoteVideoFrameReady(mPeerConnectionId, dataY, dataU, dataV, strideY, strideU, strideV, width, height); } private void RaiseLocalSdpReadytoSend(string type, string sdp) { if (OnLocalSdpReadytoSend != null) OnLocalSdpReadytoSend(mPeerConnectionId, type, sdp); } private void RaiseIceCandidateReadytoSend(string candidate, int sdpMlineIndex, string sdpMid) { if (OnIceCandidateReadytoSend != null) OnIceCandidateReadytoSend(mPeerConnectionId, candidate, sdpMlineIndex, sdpMid); } public void AddQueuedIceCandidate(List iceCandidateQueue) { if (iceCandidateQueue != null) { foreach (IceCandidate ic in iceCandidateQueue) { AddIceCandidate(mPeerConnectionId, ic.Candidate, ic.SdpMlineIndex, ic.SdpMid); } } } private LocalDataChannelReadyInternalDelegate localDataChannelReadyDelegate = null; public event LocalDataChannelReadyDelegate OnLocalDataChannelReady; private DataFromDataChannelReadyInternalDelegate dataFromDataChannelReadyDelegate = null; public event DataFromDataChannelReadyDelegate OnDataFromDataChannelReady; private FailureMessageInternalDelegate failureMessageDelegate = null; public event FailureMessageDelegate OnFailureMessage; private AudioBusReadyInternalDelegate audioBusReadyDelegate = null; public event AudioBusReadyDelegate OnAudioBusReady; private I420FrameReadyInternalDelegate localI420FrameReadyDelegate = null; public event I420FrameReadyDelegate OnLocalVideoFrameReady; private I420FrameReadyInternalDelegate remoteI420FrameReadyDelegate = null; public event I420FrameReadyDelegate OnRemoteVideoFrameReady; private LocalSdpReadytoSendInternalDelegate localSdpReadytoSendDelegate = null; public event LocalSdpReadytoSendDelegate OnLocalSdpReadytoSend; private IceCandidateReadytoSendInternalDelegate iceCandidateReadytoSendDelegate = null; public event IceCandidateReadytoSendDelegate OnIceCandidateReadytoSend; private int mPeerConnectionId = -1; } }