An example of Unity native plugin of webrtc for Windows OS
Unity native plugin has to use Pinvoke technology in its APIs
This plugin dll can also be used by Windows C# applications other than
Unity.

BUG=webrtc:7389

Review-Url: https://codereview.webrtc.org/2823783002
Cr-Commit-Position: refs/heads/master@{#18108}
diff --git a/webrtc/examples/BUILD.gn b/webrtc/examples/BUILD.gn
index 80b3efa..f812f08 100644
--- a/webrtc/examples/BUILD.gn
+++ b/webrtc/examples/BUILD.gn
@@ -603,6 +603,40 @@
   }
 }
 
+if (is_win) {
+  rtc_shared_library("webrtc_unity_plugin") {
+    testonly = true
+    sources = [
+      "unityplugin/simple_peer_connection.cc",
+      "unityplugin/simple_peer_connection.h",
+      "unityplugin/unity_plugin_apis.cc",
+      "unityplugin/unity_plugin_apis.h",
+    ]
+    if (!build_with_chromium && is_clang) {
+      # Suppress warnings from the Chromium Clang plugin (bugs.webrtc.org/163).
+      suppressed_configs += [ "//build/config/clang:find_bad_constructs" ]
+    }
+    cflags = [ "/wd4245" ]
+    configs += [
+      "//build/config/win:windowed",
+      ":peerconnection_client_warnings_config",
+    ]
+    deps = [
+      "//webrtc/api:libjingle_peerconnection_test_api",
+      "//webrtc/api:video_frame_api",
+      "//webrtc/base:rtc_base",
+      "//webrtc/base:rtc_base_approved",
+      "//webrtc/base:rtc_json",
+      "//webrtc/media:rtc_media",
+      "//webrtc/media:rtc_media_base",
+      "//webrtc/modules/video_capture:video_capture_module",
+      "//webrtc/pc:libjingle_peerconnection",
+      "//webrtc/system_wrappers:field_trial_default",
+      "//webrtc/system_wrappers:metrics_default",
+    ]
+  }
+}
+
 if (!build_with_chromium) {
   # Doesn't build within Chrome on Win.
   rtc_executable("stun_prober") {
diff --git a/webrtc/examples/unityplugin/OWNERS b/webrtc/examples/unityplugin/OWNERS
new file mode 100644
index 0000000..61ea9a9
--- /dev/null
+++ b/webrtc/examples/unityplugin/OWNERS
@@ -0,0 +1 @@
+gyzhou@chromium.org
diff --git a/webrtc/examples/unityplugin/README b/webrtc/examples/unityplugin/README
new file mode 100644
index 0000000..eade9ef
--- /dev/null
+++ b/webrtc/examples/unityplugin/README
@@ -0,0 +1,204 @@
+This directory contains an example Unity native plugin for Windows OS.
+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.
+
+An example of wrapping native plugin into a C# managed class in Unity is given as following:
+
+using System;
+using System.Runtime.InteropServices;
+
+namespace SimplePeerConnectionM {
+  // This is a managed wrap up class for the native c style peer connection APIs.
+  public class PeerConnectionM {
+    //private const string dll_path = "SimplePeerConnection";
+    private const string dll_path = "webrtc_unity_plugin";
+
+    [DllImport(dll_path, CallingConvention = CallingConvention.Cdecl)]
+    private static extern int CreatePeerConnection();
+
+    [DllImport(dll_path, CallingConvention = CallingConvention.Cdecl)]
+    private static extern bool ClosePeerConnection(int peer_connection_id);
+
+    [DllImport(dll_path, CallingConvention = CallingConvention.Cdecl)]
+    private static extern bool AddStream(int peer_connection_id, bool audio_only);
+
+    [DllImport(dll_path, CallingConvention = CallingConvention.Cdecl)]
+    private static extern bool AddDataChannel(int peer_connection_id);
+
+    [DllImport(dll_path, CallingConvention = CallingConvention.Cdecl)]
+    private static extern bool CreateOffer(int peer_connection_id);
+
+    [DllImport(dll_path, CallingConvention = CallingConvention.Cdecl)]
+    private static extern bool CreateAnswer(int peer_connection_id);
+
+    [DllImport(dll_path, CallingConvention = CallingConvention.Cdecl)]
+    private static extern bool SendDataViaDataChannel(int peer_connection_id, string data);
+
+    [DllImport(dll_path, CallingConvention = CallingConvention.Cdecl)]
+    private static extern bool SetAudioControl(int peer_connection_id, bool is_mute, bool is_record);
+
+    [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
+    private delegate void LocalDataChannelReadyInternalDelegate();
+    public delegate void LocalDataChannelReadyDelegate(int id);
+    [DllImport(dll_path, CallingConvention = CallingConvention.Cdecl)]
+    private static extern bool RegisterOnLocalDataChannelReady(int peer_connection_id, LocalDataChannelReadyInternalDelegate callback);
+
+    [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
+    private delegate void DataFromDataChannelReadyInternalDelegate(string s);
+    public delegate void DataFromDataChannelReadyDelegate(int id, string s);
+    [DllImport(dll_path, CallingConvention = CallingConvention.Cdecl)]
+    private static extern bool RegisterOnDataFromDataChannelReady(int peer_connection_id, DataFromDataChannelReadyInternalDelegate callback);
+
+    [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
+    private delegate void FailureMessageInternalDelegate(string msg);
+    public delegate void FailureMessageDelegate(int id, string msg);
+    [DllImport(dll_path, CallingConvention = CallingConvention.Cdecl)]
+    private static extern bool RegisterOnFailure(int peer_connection_id, FailureMessageInternalDelegate callback);
+
+    [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
+    private delegate void AudioBusReadyInternalDelegate(IntPtr data, int bits_per_sample,
+        int sample_rate, int number_of_channels, int number_of_frames);
+    public delegate void AudioBusReadyDelegate(int id, IntPtr data, int bits_per_sample,
+        int sample_rate, int number_of_channels, int number_of_frames);
+    [DllImport(dll_path, CallingConvention = CallingConvention.Cdecl)]
+    private static extern bool RegisterOnAudioBusReady(int peer_connection_id, AudioBusReadyInternalDelegate callback);
+
+    [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
+    private delegate void LocalSdpReadytoSendInternalDelegate(string s);
+    public delegate void LocalSdpReadytoSendDelegate(int id, string s);
+    [DllImport(dll_path, CallingConvention = CallingConvention.Cdecl)]
+    private static extern bool RegisterOnLocalSdpReadytoSend(int peer_connection_id, LocalSdpReadytoSendInternalDelegate callback);
+
+    [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
+    private delegate void IceCandiateReadytoSendInternalDelegate(string s);
+    public delegate void IceCandiateReadytoSendDelegate(int id, string s);
+    [DllImport(dll_path, CallingConvention = CallingConvention.Cdecl)]
+    private static extern bool RegisterOnIceCandiateReadytoSend(int peer_connection_id, IceCandiateReadytoSendInternalDelegate callback);
+
+    [DllImport(dll_path, CallingConvention = CallingConvention.Cdecl)]
+    private static extern int ReceivedSdp(int peer_connection_id, string sdp);
+
+    [DllImport(dll_path, CallingConvention = CallingConvention.Cdecl)]
+    private static extern bool ReceivedIceCandidate(int peer_connection_id, string ice_candidate);
+
+    public void CreatePeerConnectionM() {
+      peer_connection_id_ = CreatePeerConnection();
+      RegisterCallbacks();
+    }
+
+    private void RegisterCallbacks() {
+      localDataChannelReadyDelegate_ = new LocalDataChannelReadyInternalDelegate(RaiseLocalDataChannelReady);
+      RegisterOnLocalDataChannelReady(peer_connection_id_, localDataChannelReadyDelegate_);
+
+      dataFromDataChannelReadyDelegate_ = new DataFromDataChannelReadyInternalDelegate(RaiseDataFromDataChannelReady);
+      RegisterOnDataFromDataChannelReady(peer_connection_id_, dataFromDataChannelReadyDelegate_);
+
+      failureMessageDelegate_ = new FailureMessageInternalDelegate(RaiseFailureMessage);
+      RegisterOnFailure(peer_connection_id_, failureMessageDelegate_);
+
+      audioBusReadyDelegate_ = new AudioBusReadyInternalDelegate(RaiseAudioBusReady);
+      RegisterOnAudioBusReady(peer_connection_id_, audioBusReadyDelegate_);
+
+      localSdpReadytoSendDelegate_ = new LocalSdpReadytoSendInternalDelegate(RaiseLocalSdpReadytoSend);
+      RegisterOnLocalSdpReadytoSend(peer_connection_id_, localSdpReadytoSendDelegate_);
+
+      iceCandiateReadytoSendDelegate_ = new IceCandiateReadytoSendInternalDelegate(RaiseIceCandiateReadytoSend);
+      RegisterOnIceCandiateReadytoSend(peer_connection_id_, iceCandiateReadytoSendDelegate_);
+    }
+
+    public void ClosePeerConnectionM() {
+      ClosePeerConnection(peer_connection_id_);
+      peer_connection_id_ = -1;
+    }
+
+    // Return -1 if Peerconnection is not available.
+    public int GetUniqueId() {
+      return peer_connection_id_;
+    }
+
+    public void AddStreamM(bool audio_only) {
+      AddStream(peer_connection_id_, audio_only);
+    }
+
+    public void AddDataChannelM() {
+      AddDataChannel(peer_connection_id_);
+    }
+
+    public void CreateOfferM() {
+      CreateOffer(peer_connection_id_);
+    }
+
+    public void CreateAnswerM() {
+      CreateAnswer(peer_connection_id_);
+    }
+
+    public void SendDataViaDataChannelM(string data) {
+      SendDataViaDataChannel(peer_connection_id_, data);
+    }
+
+    public void SetAudioControl(bool is_mute, bool is_record) {
+      SetAudioControl(peer_connection_id_, is_mute, is_record);
+    }
+
+    public void ReceivedSdpM(string sdp) {
+      peer_connection_id_ = ReceivedSdp(peer_connection_id_, sdp);
+      RegisterCallbacks();
+    }
+
+    public void ReceivedIceCandidateM(string ice_candidate) {
+      ReceivedIceCandidate(peer_connection_id_, ice_candidate);
+    }
+
+    private void RaiseLocalDataChannelReady() {
+      if (OnLocalDataChannelReady != null)
+        OnLocalDataChannelReady(peer_connection_id_);
+    }
+
+    private void RaiseDataFromDataChannelReady(string data) {
+      if (OnDataFromDataChannelReady != null)
+        OnDataFromDataChannelReady(peer_connection_id_, data);
+    }
+
+    private void RaiseFailureMessage(string msg) {
+      if (OnFailureMessage != null)
+        OnFailureMessage(peer_connection_id_, msg);
+    }
+
+    private void RaiseAudioBusReady(IntPtr data, int bits_per_sample,
+      int sample_rate, int number_of_channels, int number_of_frames) {
+      if (OnAudioBusReady != null)
+        OnAudioBusReady(peer_connection_id_, data, bits_per_sample, sample_rate,
+            number_of_channels, number_of_frames);
+    }
+
+    private void RaiseLocalSdpReadytoSend(string msg) {
+      if (OnLocalSdpReadytoSend != null)
+        OnLocalSdpReadytoSend(peer_connection_id_, msg);
+    }
+
+    private void RaiseIceCandiateReadytoSend(string msg) {
+      if (OnIceCandiateReadytoSend != null)
+        OnIceCandiateReadytoSend(peer_connection_id_, msg);
+    }
+
+    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 LocalSdpReadytoSendInternalDelegate localSdpReadytoSendDelegate_ = null;
+    public event LocalSdpReadytoSendDelegate OnLocalSdpReadytoSend;
+
+    private IceCandiateReadytoSendInternalDelegate iceCandiateReadytoSendDelegate_ = null;
+    public event IceCandiateReadytoSendDelegate OnIceCandiateReadytoSend;
+
+    private int peer_connection_id_ = -1;
+  }
+}
diff --git a/webrtc/examples/unityplugin/simple_peer_connection.cc b/webrtc/examples/unityplugin/simple_peer_connection.cc
new file mode 100644
index 0000000..ee959b7
--- /dev/null
+++ b/webrtc/examples/unityplugin/simple_peer_connection.cc
@@ -0,0 +1,514 @@
+/*
+ *  Copyright (c) 2017 The WebRTC project authors. All Rights Reserved.
+ *
+ *  Use of this source code is governed by a BSD-style license
+ *  that can be found in the LICENSE file in the root of the source
+ *  tree. An additional intellectual property rights grant can be found
+ *  in the file PATENTS.  All contributing project authors may
+ *  be found in the AUTHORS file in the root of the source tree.
+ */
+
+#include "webrtc/examples/unityplugin/simple_peer_connection.h"
+
+#include <utility>
+
+#include "webrtc/api/test/fakeconstraints.h"
+#include "webrtc/base/json.h"
+#include "webrtc/media/engine/webrtcvideocapturerfactory.h"
+#include "webrtc/modules/video_capture/video_capture_factory.h"
+
+// Names used for a IceCandidate JSON object.
+const char kCandidateSdpMidName[] = "sdpMid";
+const char kCandidateSdpMlineIndexName[] = "sdpMLineIndex";
+const char kCandidateSdpName[] = "candidate";
+
+// Names used for a SessionDescription JSON object.
+const char kSessionDescriptionTypeName[] = "type";
+const char kSessionDescriptionSdpName[] = "sdp";
+
+// Names used for media stream labels.
+const char kAudioLabel[] = "audio_label";
+const char kVideoLabel[] = "video_label";
+const char kStreamLabel[] = "stream_label";
+
+namespace {
+static int g_peer_count = 0;
+static std::unique_ptr<rtc::Thread> g_worker_thread;
+static std::unique_ptr<rtc::Thread> g_signaling_thread;
+static rtc::scoped_refptr<webrtc::PeerConnectionFactoryInterface>
+    g_peer_connection_factory;
+
+std::string GetEnvVarOrDefault(const char* env_var_name,
+                               const char* default_value) {
+  std::string value;
+  const char* env_var = getenv(env_var_name);
+  if (env_var)
+    value = env_var;
+
+  if (value.empty())
+    value = default_value;
+
+  return value;
+}
+
+std::string GetPeerConnectionString() {
+  return GetEnvVarOrDefault("WEBRTC_CONNECT", "stun:stun.l.google.com:19302");
+}
+
+class DummySetSessionDescriptionObserver
+    : public webrtc::SetSessionDescriptionObserver {
+ public:
+  static DummySetSessionDescriptionObserver* Create() {
+    return new rtc::RefCountedObject<DummySetSessionDescriptionObserver>();
+  }
+  virtual void OnSuccess() { LOG(INFO) << __FUNCTION__; }
+  virtual void OnFailure(const std::string& error) {
+    LOG(INFO) << __FUNCTION__ << " " << error;
+  }
+
+ protected:
+  DummySetSessionDescriptionObserver() {}
+  ~DummySetSessionDescriptionObserver() {}
+};
+
+}  // namespace
+
+bool SimplePeerConnection::InitializePeerConnection(bool is_receiver) {
+  RTC_DCHECK(peer_connection_.get() == nullptr);
+
+  if (g_peer_connection_factory == nullptr) {
+    g_worker_thread.reset(new rtc::Thread());
+    g_worker_thread->Start();
+    g_signaling_thread.reset(new rtc::Thread());
+    g_signaling_thread->Start();
+
+    g_peer_connection_factory = webrtc::CreatePeerConnectionFactory(
+        g_worker_thread.get(), g_worker_thread.get(), g_signaling_thread.get(),
+        nullptr, nullptr, nullptr);
+  }
+  if (!g_peer_connection_factory.get()) {
+    DeletePeerConnection();
+    return false;
+  }
+
+  g_peer_count++;
+  if (!CreatePeerConnection(is_receiver)) {
+    DeletePeerConnection();
+    return false;
+  }
+  return peer_connection_.get() != nullptr;
+}
+
+bool SimplePeerConnection::CreatePeerConnection(bool is_receiver) {
+  RTC_DCHECK(g_peer_connection_factory.get() != nullptr);
+  RTC_DCHECK(peer_connection_.get() == nullptr);
+
+  webrtc::PeerConnectionInterface::RTCConfiguration config;
+  webrtc::PeerConnectionInterface::IceServer server;
+  server.uri = GetPeerConnectionString();
+  config.servers.push_back(server);
+
+  webrtc::FakeConstraints constraints;
+  constraints.SetAllowDtlsSctpDataChannels();
+
+  if (is_receiver) {
+    constraints.SetMandatoryReceiveAudio(true);
+    constraints.SetMandatoryReceiveVideo(true);
+  }
+
+  peer_connection_ = g_peer_connection_factory->CreatePeerConnection(
+      config, &constraints, nullptr, nullptr, this);
+
+  return peer_connection_.get() != nullptr;
+}
+
+void SimplePeerConnection::DeletePeerConnection() {
+  g_peer_count--;
+
+  CloseDataChannel();
+  peer_connection_ = nullptr;
+  active_streams_.clear();
+
+  if (g_peer_count == 0) {
+    g_peer_connection_factory = nullptr;
+    g_signaling_thread.reset();
+    g_worker_thread.reset();
+  }
+}
+
+bool SimplePeerConnection::CreateOffer() {
+  if (!peer_connection_.get())
+    return false;
+
+  peer_connection_->CreateOffer(this, nullptr);
+  return true;
+}
+
+bool SimplePeerConnection::CreateAnswer() {
+  if (!peer_connection_.get())
+    return false;
+
+  peer_connection_->CreateAnswer(this, nullptr);
+  return true;
+}
+
+void SimplePeerConnection::OnSuccess(
+    webrtc::SessionDescriptionInterface* desc) {
+  peer_connection_->SetLocalDescription(
+      DummySetSessionDescriptionObserver::Create(), desc);
+
+  std::string sdp;
+  desc->ToString(&sdp);
+
+  Json::StyledWriter writer;
+  Json::Value jmessage;
+  jmessage[kSessionDescriptionTypeName] = desc->type();
+  jmessage[kSessionDescriptionSdpName] = sdp;
+
+  if (OnLocalSdpReady)
+    OnLocalSdpReady(writer.write(jmessage).c_str());
+}
+
+void SimplePeerConnection::OnFailure(const std::string& error) {
+  LOG(LERROR) << error;
+
+  if (OnFailureMessage)
+    OnFailureMessage(error.c_str());
+}
+
+void SimplePeerConnection::OnIceCandidate(
+    const webrtc::IceCandidateInterface* candidate) {
+  LOG(INFO) << __FUNCTION__ << " " << candidate->sdp_mline_index();
+
+  Json::StyledWriter writer;
+  Json::Value jmessage;
+
+  jmessage[kCandidateSdpMidName] = candidate->sdp_mid();
+  jmessage[kCandidateSdpMlineIndexName] = candidate->sdp_mline_index();
+  std::string sdp;
+  if (!candidate->ToString(&sdp)) {
+    LOG(LS_ERROR) << "Failed to serialize candidate";
+    return;
+  }
+  jmessage[kCandidateSdpName] = sdp;
+
+  if (OnIceCandiateReady)
+    OnIceCandiateReady(writer.write(jmessage).c_str());
+}
+
+void SimplePeerConnection::RegisterOnVideoFramReady(
+    VIDEOFRAMEREADY_CALLBACK callback) {
+  OnVideoFrameReady = callback;
+}
+
+void SimplePeerConnection::RegisterOnLocalDataChannelReady(
+    LOCALDATACHANNELREADY_CALLBACK callback) {
+  OnLocalDataChannelReady = callback;
+}
+
+void SimplePeerConnection::RegisterOnDataFromDataChannelReady(
+    DATAFROMEDATECHANNELREADY_CALLBACK callback) {
+  OnDataFromDataChannelReady = callback;
+}
+
+void SimplePeerConnection::RegisterOnFailure(FAILURE_CALLBACK callback) {
+  OnFailureMessage = callback;
+}
+
+void SimplePeerConnection::RegisterOnAudioBusReady(
+    AUDIOBUSREADY_CALLBACK callback) {
+  OnAudioReady = callback;
+}
+
+void SimplePeerConnection::RegisterOnLocalSdpReadytoSend(
+    LOCALSDPREADYTOSEND_CALLBACK callback) {
+  OnLocalSdpReady = callback;
+}
+
+void SimplePeerConnection::RegisterOnIceCandiateReadytoSend(
+    ICECANDIDATEREADYTOSEND_CALLBACK callback) {
+  OnIceCandiateReady = callback;
+}
+
+bool SimplePeerConnection::ReceivedSdp(const char* msg) {
+  if (!peer_connection_)
+    return false;
+
+  std::string message(msg);
+
+  Json::Reader reader;
+  Json::Value jmessage;
+  if (!reader.parse(message, jmessage)) {
+    LOG(WARNING) << "Received unknown message. " << message;
+    return false;
+  }
+  std::string type;
+  std::string json_object;
+
+  rtc::GetStringFromJsonObject(jmessage, kSessionDescriptionTypeName, &type);
+  if (type.empty())
+    return false;
+
+  std::string sdp;
+  if (!rtc::GetStringFromJsonObject(jmessage, kSessionDescriptionSdpName,
+                                    &sdp)) {
+    LOG(WARNING) << "Can't parse received session description message.";
+    return false;
+  }
+  webrtc::SdpParseError error;
+  webrtc::SessionDescriptionInterface* session_description(
+      webrtc::CreateSessionDescription(type, sdp, &error));
+  if (!session_description) {
+    LOG(WARNING) << "Can't parse received session description message. "
+                 << "SdpParseError was: " << error.description;
+    return false;
+  }
+  LOG(INFO) << " Received session description :" << message;
+  peer_connection_->SetRemoteDescription(
+      DummySetSessionDescriptionObserver::Create(), session_description);
+
+  return true;
+}
+
+bool SimplePeerConnection::ReceivedIceCandidate(const char* ice_candidate) {
+  if (!peer_connection_)
+    return false;
+
+  std::string message(ice_candidate);
+
+  Json::Reader reader;
+  Json::Value jmessage;
+  if (!reader.parse(message, jmessage)) {
+    LOG(WARNING) << "Received unknown message. " << message;
+    return false;
+  }
+  std::string type;
+  std::string json_object;
+
+  rtc::GetStringFromJsonObject(jmessage, kSessionDescriptionTypeName, &type);
+  if (!type.empty())
+    return false;
+
+  std::string sdp_mid;
+  int sdp_mlineindex = 0;
+  std::string sdp;
+  if (!rtc::GetStringFromJsonObject(jmessage, kCandidateSdpMidName, &sdp_mid) ||
+      !rtc::GetIntFromJsonObject(jmessage, kCandidateSdpMlineIndexName,
+                                 &sdp_mlineindex) ||
+      !rtc::GetStringFromJsonObject(jmessage, kCandidateSdpName, &sdp)) {
+    LOG(WARNING) << "Can't parse received message.";
+    return false;
+  }
+  webrtc::SdpParseError error;
+  std::unique_ptr<webrtc::IceCandidateInterface> candidate(
+      webrtc::CreateIceCandidate(sdp_mid, sdp_mlineindex, sdp, &error));
+  if (!candidate.get()) {
+    LOG(WARNING) << "Can't parse received candidate message. "
+                 << "SdpParseError was: " << error.description;
+    return false;
+  }
+  if (!peer_connection_->AddIceCandidate(candidate.get())) {
+    LOG(WARNING) << "Failed to apply the received candidate";
+    return false;
+  }
+  LOG(INFO) << " Received candidate :" << message;
+  return true;
+}
+
+void SimplePeerConnection::SetAudioControl(bool is_mute, bool is_record) {
+  is_mute_audio_ = is_mute;
+  is_record_audio_ = is_record;
+
+  SetAudioControl();
+}
+
+void SimplePeerConnection::SetAudioControl() {
+  if (!remote_stream_)
+    return;
+  webrtc::AudioTrackVector tracks = remote_stream_->GetAudioTracks();
+  if (tracks.empty())
+    return;
+
+  webrtc::AudioTrackInterface* audio_track = tracks[0];
+  std::string id = audio_track->id();
+  if (is_record_audio_)
+    audio_track->AddSink(this);
+  else
+    audio_track->RemoveSink(this);
+
+  for (auto& track : tracks) {
+    if (is_mute_audio_)
+      track->set_enabled(false);
+    else
+      track->set_enabled(true);
+  }
+}
+
+void SimplePeerConnection::OnAddStream(
+    rtc::scoped_refptr<webrtc::MediaStreamInterface> stream) {
+  LOG(INFO) << __FUNCTION__ << " " << stream->label();
+  remote_stream_ = stream;
+
+  SetAudioControl();
+}
+
+std::unique_ptr<cricket::VideoCapturer>
+SimplePeerConnection::OpenVideoCaptureDevice() {
+  std::vector<std::string> device_names;
+  {
+    std::unique_ptr<webrtc::VideoCaptureModule::DeviceInfo> info(
+        webrtc::VideoCaptureFactory::CreateDeviceInfo());
+    if (!info) {
+      return nullptr;
+    }
+    int num_devices = info->NumberOfDevices();
+    for (int i = 0; i < num_devices; ++i) {
+      const uint32_t kSize = 256;
+      char name[kSize] = {0};
+      char id[kSize] = {0};
+      if (info->GetDeviceName(i, name, kSize, id, kSize) != -1) {
+        device_names.push_back(name);
+      }
+    }
+  }
+
+  cricket::WebRtcVideoDeviceCapturerFactory factory;
+  std::unique_ptr<cricket::VideoCapturer> capturer;
+  for (const auto& name : device_names) {
+    capturer = factory.Create(cricket::Device(name, 0));
+    if (capturer) {
+      break;
+    }
+  }
+  return capturer;
+}
+
+void SimplePeerConnection::AddStreams(bool audio_only) {
+  if (active_streams_.find(kStreamLabel) != active_streams_.end())
+    return;  // Already added.
+
+  rtc::scoped_refptr<webrtc::MediaStreamInterface> stream =
+      g_peer_connection_factory->CreateLocalMediaStream(kStreamLabel);
+
+  rtc::scoped_refptr<webrtc::AudioTrackInterface> audio_track(
+      g_peer_connection_factory->CreateAudioTrack(
+          kAudioLabel, g_peer_connection_factory->CreateAudioSource(nullptr)));
+  std::string id = audio_track->id();
+  stream->AddTrack(audio_track);
+
+  if (!audio_only) {
+    std::unique_ptr<cricket::VideoCapturer> capture = OpenVideoCaptureDevice();
+    if (capture) {
+      rtc::scoped_refptr<webrtc::VideoTrackInterface> video_track(
+          g_peer_connection_factory->CreateVideoTrack(
+              kVideoLabel, g_peer_connection_factory->CreateVideoSource(
+                               OpenVideoCaptureDevice(), nullptr)));
+
+      stream->AddTrack(video_track);
+    }
+  }
+
+  if (!peer_connection_->AddStream(stream)) {
+    LOG(LS_ERROR) << "Adding stream to PeerConnection failed";
+  }
+
+  typedef std::pair<std::string,
+                    rtc::scoped_refptr<webrtc::MediaStreamInterface>>
+      MediaStreamPair;
+  active_streams_.insert(MediaStreamPair(stream->label(), stream));
+}
+
+bool SimplePeerConnection::CreateDataChannel() {
+  struct webrtc::DataChannelInit init;
+  init.ordered = true;
+  init.reliable = true;
+  data_channel_ = peer_connection_->CreateDataChannel("Hello", &init);
+  if (data_channel_.get()) {
+    data_channel_->RegisterObserver(this);
+    LOG(LS_INFO) << "Succeeds to create data channel";
+    return true;
+  } else {
+    LOG(LS_INFO) << "Fails to create data channel";
+    return false;
+  }
+}
+
+void SimplePeerConnection::CloseDataChannel() {
+  if (data_channel_.get()) {
+    data_channel_->UnregisterObserver();
+    data_channel_->Close();
+  }
+  data_channel_ = nullptr;
+}
+
+bool SimplePeerConnection::SendDataViaDataChannel(const std::string& data) {
+  if (!data_channel_.get()) {
+    LOG(LS_INFO) << "Data channel is not established";
+    return false;
+  }
+  webrtc::DataBuffer buffer(data);
+  data_channel_->Send(buffer);
+  return true;
+}
+
+// Peerconnection observer
+void SimplePeerConnection::OnDataChannel(
+    rtc::scoped_refptr<webrtc::DataChannelInterface> channel) {
+  channel->RegisterObserver(this);
+}
+
+void SimplePeerConnection::OnStateChange() {
+  if (data_channel_) {
+    webrtc::DataChannelInterface::DataState state = data_channel_->state();
+    if (state == webrtc::DataChannelInterface::kOpen) {
+      if (OnLocalDataChannelReady)
+        OnLocalDataChannelReady();
+      LOG(LS_INFO) << "Data channel is open";
+    }
+  }
+}
+
+//  A data buffer was successfully received.
+void SimplePeerConnection::OnMessage(const webrtc::DataBuffer& buffer) {
+  size_t size = buffer.data.size();
+  char* msg = new char[size + 1];
+  memcpy(msg, buffer.data.data(), size);
+  msg[size] = 0;
+  if (OnDataFromDataChannelReady)
+    OnDataFromDataChannelReady(msg);
+  delete[] msg;
+}
+
+// AudioTrackSinkInterface implementation.
+void SimplePeerConnection::OnData(const void* audio_data,
+                                  int bits_per_sample,
+                                  int sample_rate,
+                                  size_t number_of_channels,
+                                  size_t number_of_frames) {
+  if (OnAudioReady)
+    OnAudioReady(audio_data, bits_per_sample, sample_rate,
+                 static_cast<int>(number_of_channels),
+                 static_cast<int>(number_of_frames));
+}
+
+std::vector<uint32_t> SimplePeerConnection::GetRemoteAudioTrackSsrcs() {
+  std::vector<rtc::scoped_refptr<webrtc::RtpReceiverInterface>> receivers =
+      peer_connection_->GetReceivers();
+
+  std::vector<uint32_t> ssrcs;
+  for (const auto& receiver : receivers) {
+    if (receiver->media_type() != cricket::MEDIA_TYPE_AUDIO)
+      continue;
+
+    std::vector<webrtc::RtpEncodingParameters> params =
+        receiver->GetParameters().encodings;
+
+    for (const auto& param : params) {
+      uint32_t ssrc = param.ssrc.value_or(0);
+      if (ssrc > 0)
+        ssrcs.push_back(ssrc);
+    }
+  }
+
+  return ssrcs;
+}
diff --git a/webrtc/examples/unityplugin/simple_peer_connection.h b/webrtc/examples/unityplugin/simple_peer_connection.h
new file mode 100644
index 0000000..2950e12
--- /dev/null
+++ b/webrtc/examples/unityplugin/simple_peer_connection.h
@@ -0,0 +1,125 @@
+/*
+ *  Copyright (c) 2017 The WebRTC project authors. All Rights Reserved.
+ *
+ *  Use of this source code is governed by a BSD-style license
+ *  that can be found in the LICENSE file in the root of the source
+ *  tree. An additional intellectual property rights grant can be found
+ *  in the file PATENTS.  All contributing project authors may
+ *  be found in the AUTHORS file in the root of the source tree.
+ */
+
+#ifndef WEBRTC_EXAMPLES_UNITYPLUGIN_SIMPLE_PEER_CONNECTION_H_
+#define WEBRTC_EXAMPLES_UNITYPLUGIN_SIMPLE_PEER_CONNECTION_H_
+
+#include <map>
+#include <memory>
+#include <string>
+#include <vector>
+
+#include "webrtc/api/datachannelinterface.h"
+#include "webrtc/api/mediastreaminterface.h"
+#include "webrtc/api/peerconnectioninterface.h"
+#include "webrtc/examples/unityplugin/unity_plugin_apis.h"
+
+class SimplePeerConnection : public webrtc::PeerConnectionObserver,
+                             public webrtc::CreateSessionDescriptionObserver,
+                             public webrtc::DataChannelObserver,
+                             public webrtc::AudioTrackSinkInterface {
+ public:
+  SimplePeerConnection() {}
+  ~SimplePeerConnection() {}
+
+  bool InitializePeerConnection(bool is_receiver);
+  void DeletePeerConnection();
+  void AddStreams(bool audio_only);
+  bool CreateDataChannel();
+  bool CreateOffer();
+  bool CreateAnswer();
+  bool SendDataViaDataChannel(const std::string& data);
+  void SetAudioControl(bool is_mute, bool is_record);
+
+  // Register callback functions.
+  void RegisterOnVideoFramReady(VIDEOFRAMEREADY_CALLBACK callback);
+  void RegisterOnLocalDataChannelReady(LOCALDATACHANNELREADY_CALLBACK callback);
+  void RegisterOnDataFromDataChannelReady(
+      DATAFROMEDATECHANNELREADY_CALLBACK callback);
+  void RegisterOnFailure(FAILURE_CALLBACK callback);
+  void RegisterOnAudioBusReady(AUDIOBUSREADY_CALLBACK callback);
+  void RegisterOnLocalSdpReadytoSend(LOCALSDPREADYTOSEND_CALLBACK callback);
+  void RegisterOnIceCandiateReadytoSend(
+      ICECANDIDATEREADYTOSEND_CALLBACK callback);
+  bool ReceivedSdp(const char* sdp);
+  bool ReceivedIceCandidate(const char* ice_candidate);
+
+  bool SetHeadPosition(float x, float y, float z);
+  bool SetHeadRotation(float rx, float ry, float rz, float rw);
+  bool SetRemoteAudioPosition(float x, float y, float z);
+  bool SetRemoteAudioRotation(float rx, float ry, float rz, float rw);
+
+ protected:
+  bool CreatePeerConnection(bool receiver);
+  void CloseDataChannel();
+  std::unique_ptr<cricket::VideoCapturer> OpenVideoCaptureDevice();
+  void SetAudioControl();
+
+  // PeerConnectionObserver implementation.
+  void OnSignalingChange(
+      webrtc::PeerConnectionInterface::SignalingState new_state) override {}
+  void OnAddStream(
+      rtc::scoped_refptr<webrtc::MediaStreamInterface> stream) override;
+  void OnRemoveStream(
+      rtc::scoped_refptr<webrtc::MediaStreamInterface> stream) override {}
+  void OnDataChannel(
+      rtc::scoped_refptr<webrtc::DataChannelInterface> channel) override;
+  void OnRenegotiationNeeded() override {}
+  void OnIceConnectionChange(
+      webrtc::PeerConnectionInterface::IceConnectionState new_state) override {}
+  void OnIceGatheringChange(
+      webrtc::PeerConnectionInterface::IceGatheringState new_state) override {}
+  void OnIceCandidate(const webrtc::IceCandidateInterface* candidate) override;
+  void OnIceConnectionReceivingChange(bool receiving) override {}
+
+  // CreateSessionDescriptionObserver implementation.
+  void OnSuccess(webrtc::SessionDescriptionInterface* desc) override;
+  void OnFailure(const std::string& error) override;
+
+  // DataChannelObserver implementation.
+  void OnStateChange() override;
+  void OnMessage(const webrtc::DataBuffer& buffer) override;
+
+  // AudioTrackSinkInterface implementation.
+  void OnData(const void* audio_data,
+              int bits_per_sample,
+              int sample_rate,
+              size_t number_of_channels,
+              size_t number_of_frames) override;
+
+  // Get remote audio tracks ssrcs.
+  std::vector<uint32_t> GetRemoteAudioTrackSsrcs();
+
+ private:
+  rtc::scoped_refptr<webrtc::PeerConnectionInterface> peer_connection_;
+  rtc::scoped_refptr<webrtc::DataChannelInterface> data_channel_;
+  std::map<std::string, rtc::scoped_refptr<webrtc::MediaStreamInterface> >
+      active_streams_;
+
+  webrtc::MediaStreamInterface* remote_stream_ = nullptr;
+
+  VIDEOFRAMEREADY_CALLBACK OnVideoFrameReady = nullptr;
+  LOCALDATACHANNELREADY_CALLBACK OnLocalDataChannelReady = nullptr;
+  DATAFROMEDATECHANNELREADY_CALLBACK OnDataFromDataChannelReady = nullptr;
+  FAILURE_CALLBACK OnFailureMessage = nullptr;
+  AUDIOBUSREADY_CALLBACK OnAudioReady = nullptr;
+
+  LOCALSDPREADYTOSEND_CALLBACK OnLocalSdpReady = nullptr;
+  ICECANDIDATEREADYTOSEND_CALLBACK OnIceCandiateReady = nullptr;
+
+  bool is_mute_audio_ = false;
+  bool is_record_audio_ = false;
+
+  // disallow copy-and-assign
+  SimplePeerConnection(const SimplePeerConnection&) = delete;
+  SimplePeerConnection& operator=(const SimplePeerConnection&) = delete;
+};
+
+#endif  // WEBRTC_EXAMPLES_UNITYPLUGIN_SIMPLE_PEER_CONNECTION_H_
diff --git a/webrtc/examples/unityplugin/unity_plugin_apis.cc b/webrtc/examples/unityplugin/unity_plugin_apis.cc
new file mode 100644
index 0000000..7b510bd
--- /dev/null
+++ b/webrtc/examples/unityplugin/unity_plugin_apis.cc
@@ -0,0 +1,185 @@
+/*
+ *  Copyright (c) 2017 The WebRTC project authors. All Rights Reserved.
+ *
+ *  Use of this source code is governed by a BSD-style license
+ *  that can be found in the LICENSE file in the root of the source
+ *  tree. An additional intellectual property rights grant can be found
+ *  in the file PATENTS.  All contributing project authors may
+ *  be found in the AUTHORS file in the root of the source tree.
+ */
+
+#include "webrtc/examples/unityplugin/unity_plugin_apis.h"
+
+#include <map>
+#include <string>
+
+#include "webrtc/examples/unityplugin/simple_peer_connection.h"
+
+namespace {
+static int g_peer_connection_id = 1;
+static std::map<int, rtc::scoped_refptr<SimplePeerConnection>>
+    g_peer_connection_map;
+}  // namespace
+
+int CreatePeerConnection() {
+  g_peer_connection_map[g_peer_connection_id] =
+      new rtc::RefCountedObject<SimplePeerConnection>();
+
+  if (!g_peer_connection_map[g_peer_connection_id]->InitializePeerConnection(
+          false))
+    return -1;
+
+  return g_peer_connection_id++;
+}
+
+bool ClosePeerConnection(int peer_connection_id) {
+  if (!g_peer_connection_map.count(peer_connection_id))
+    return false;
+
+  g_peer_connection_map[peer_connection_id]->DeletePeerConnection();
+  g_peer_connection_map.erase(peer_connection_id);
+  return true;
+}
+
+bool AddStream(int peer_connection_id, bool audio_only) {
+  if (!g_peer_connection_map.count(peer_connection_id))
+    return false;
+
+  g_peer_connection_map[peer_connection_id]->AddStreams(audio_only);
+  return true;
+}
+
+bool AddDataChannel(int peer_connection_id) {
+  if (!g_peer_connection_map.count(peer_connection_id))
+    return false;
+
+  return g_peer_connection_map[peer_connection_id]->CreateDataChannel();
+}
+
+bool CreateOffer(int peer_connection_id) {
+  if (!g_peer_connection_map.count(peer_connection_id))
+    return false;
+
+  return g_peer_connection_map[peer_connection_id]->CreateOffer();
+}
+
+bool CreateAnswer(int peer_connection_id) {
+  if (!g_peer_connection_map.count(peer_connection_id))
+    return false;
+
+  return g_peer_connection_map[peer_connection_id]->CreateAnswer();
+}
+
+bool SendDataViaDataChannel(int peer_connection_id, const char* data) {
+  if (!g_peer_connection_map.count(peer_connection_id))
+    return false;
+
+  std::string s(data);
+  g_peer_connection_map[peer_connection_id]->SendDataViaDataChannel(s);
+
+  return true;
+}
+
+bool SetAudioControl(int peer_connection_id, bool is_mute, bool is_record) {
+  if (!g_peer_connection_map.count(peer_connection_id))
+    return false;
+
+  g_peer_connection_map[peer_connection_id]->SetAudioControl(is_mute,
+                                                             is_record);
+  return true;
+}
+
+// Register callback functions.
+bool RegisterOnVideoFramReady(int peer_connection_id,
+                              VIDEOFRAMEREADY_CALLBACK callback) {
+  if (!g_peer_connection_map.count(peer_connection_id))
+    return false;
+
+  g_peer_connection_map[peer_connection_id]->RegisterOnVideoFramReady(callback);
+  return true;
+}
+
+bool RegisterOnLocalDataChannelReady(int peer_connection_id,
+                                     LOCALDATACHANNELREADY_CALLBACK callback) {
+  if (!g_peer_connection_map.count(peer_connection_id))
+    return false;
+
+  g_peer_connection_map[peer_connection_id]->RegisterOnLocalDataChannelReady(
+      callback);
+  return true;
+}
+
+bool RegisterOnDataFromDataChannelReady(
+    int peer_connection_id,
+    DATAFROMEDATECHANNELREADY_CALLBACK callback) {
+  if (!g_peer_connection_map.count(peer_connection_id))
+    return false;
+
+  g_peer_connection_map[peer_connection_id]->RegisterOnDataFromDataChannelReady(
+      callback);
+  return true;
+}
+
+bool RegisterOnFailure(int peer_connection_id, FAILURE_CALLBACK callback) {
+  if (!g_peer_connection_map.count(peer_connection_id))
+    return false;
+
+  g_peer_connection_map[peer_connection_id]->RegisterOnFailure(callback);
+  return true;
+}
+
+bool RegisterOnAudioBusReady(int peer_connection_id,
+                             AUDIOBUSREADY_CALLBACK callback) {
+  if (!g_peer_connection_map.count(peer_connection_id))
+    return false;
+
+  g_peer_connection_map[peer_connection_id]->RegisterOnAudioBusReady(callback);
+  return true;
+}
+
+// Singnaling channel related functions.
+bool RegisterOnLocalSdpReadytoSend(int peer_connection_id,
+                                   LOCALSDPREADYTOSEND_CALLBACK callback) {
+  if (!g_peer_connection_map.count(peer_connection_id))
+    return false;
+
+  g_peer_connection_map[peer_connection_id]->RegisterOnLocalSdpReadytoSend(
+      callback);
+  return true;
+}
+
+bool RegisterOnIceCandiateReadytoSend(
+    int peer_connection_id,
+    ICECANDIDATEREADYTOSEND_CALLBACK callback) {
+  if (!g_peer_connection_map.count(peer_connection_id))
+    return false;
+
+  g_peer_connection_map[peer_connection_id]->RegisterOnIceCandiateReadytoSend(
+      callback);
+  return true;
+}
+
+int ReceivedSdp(int peer_connection_id, const char* sdp) {
+  // Create a peer_connection if no one exists.
+  int id = -1;
+  if (g_peer_connection_map.count(peer_connection_id)) {
+    id = peer_connection_id;
+  } else {
+    id = g_peer_connection_id++;
+    g_peer_connection_map[id] =
+        new rtc::RefCountedObject<SimplePeerConnection>();
+    if (!g_peer_connection_map[id]->InitializePeerConnection(true))
+      return -1;
+  }
+
+  g_peer_connection_map[id]->ReceivedSdp(sdp);
+  return id;
+}
+
+bool ReceivedIceCandidate(int peer_connection_id, const char* ice_candidate) {
+  if (!g_peer_connection_map.count(peer_connection_id))
+    return false;
+
+  return g_peer_connection_map[peer_connection_id]->ReceivedIceCandidate(
+      ice_candidate);
+}
diff --git a/webrtc/examples/unityplugin/unity_plugin_apis.h b/webrtc/examples/unityplugin/unity_plugin_apis.h
new file mode 100644
index 0000000..bcd1af3
--- /dev/null
+++ b/webrtc/examples/unityplugin/unity_plugin_apis.h
@@ -0,0 +1,83 @@
+/*
+ *  Copyright (c) 2017 The WebRTC project authors. All Rights Reserved.
+ *
+ *  Use of this source code is governed by a BSD-style license
+ *  that can be found in the LICENSE file in the root of the source
+ *  tree. An additional intellectual property rights grant can be found
+ *  in the file PATENTS.  All contributing project authors may
+ *  be found in the AUTHORS file in the root of the source tree.
+ */
+
+// This file provides an example of unity native plugin APIs.
+
+#ifndef WEBRTC_EXAMPLES_UNITYPLUGIN_UNITY_PLUGIN_APIS_H_
+#define WEBRTC_EXAMPLES_UNITYPLUGIN_UNITY_PLUGIN_APIS_H_
+
+#include <stdint.h>
+
+// Defintions of callback functions.
+typedef void (*VIDEOFRAMEREADY_CALLBACK)(uint8_t* buffer,
+                                         uint32_t width,
+                                         uint32_t height,
+                                         uint32_t stride);
+typedef void (*LOCALDATACHANNELREADY_CALLBACK)();
+typedef void (*DATAFROMEDATECHANNELREADY_CALLBACK)(const char* msg);
+typedef void (*FAILURE_CALLBACK)(const char* msg);
+typedef void (*LOCALSDPREADYTOSEND_CALLBACK)(const char* msg);
+typedef void (*ICECANDIDATEREADYTOSEND_CALLBACK)(const char* msg);
+typedef void (*AUDIOBUSREADY_CALLBACK)(const void* audio_data,
+                                       int bits_per_sample,
+                                       int sample_rate,
+                                       int number_of_channels,
+                                       int number_of_frames);
+
+#define WEBRTC_PLUGIN_API __declspec(dllexport)
+extern "C" {
+// Create a peerconnection and return a unique peer connection id.
+WEBRTC_PLUGIN_API int CreatePeerConnection();
+// Close a peerconnection.
+WEBRTC_PLUGIN_API bool ClosePeerConnection(int peer_connection_id);
+// Add a audio stream. If audio_only is true, the stream only has an audio
+// track and no video track.
+WEBRTC_PLUGIN_API bool AddStream(int peer_connection_id, bool audio_only);
+// Add a data channel to peer connection.
+WEBRTC_PLUGIN_API bool AddDataChannel(int peer_connection_id);
+// Create a peer connection offer.
+WEBRTC_PLUGIN_API bool CreateOffer(int peer_connection_id);
+// Create a peer connection answer.
+WEBRTC_PLUGIN_API bool CreateAnswer(int peer_connection_id);
+// Send data through data channel.
+WEBRTC_PLUGIN_API bool SendDataViaDataChannel(int peer_connection_id,
+                                              const char* data);
+// Set audio control. If is_mute=true, no audio will playout. If is_record=true,
+// AUDIOBUSREADY_CALLBACK will be called every 10 ms.
+WEBRTC_PLUGIN_API bool SetAudioControl(int peer_connection_id,
+                                       bool is_mute,
+                                       bool is_record);
+
+// Register callback functions.
+WEBRTC_PLUGIN_API bool RegisterOnVideoFramReady(
+    int peer_connection_id,
+    VIDEOFRAMEREADY_CALLBACK callback);
+WEBRTC_PLUGIN_API bool RegisterOnLocalDataChannelReady(
+    int peer_connection_id,
+    LOCALDATACHANNELREADY_CALLBACK callback);
+WEBRTC_PLUGIN_API bool RegisterOnDataFromDataChannelReady(
+    int peer_connection_id,
+    DATAFROMEDATECHANNELREADY_CALLBACK callback);
+WEBRTC_PLUGIN_API bool RegisterOnFailure(int peer_connection_id,
+                                         FAILURE_CALLBACK callback);
+WEBRTC_PLUGIN_API bool RegisterOnAudioBusReady(int peer_connection_id,
+                                               AUDIOBUSREADY_CALLBACK callback);
+WEBRTC_PLUGIN_API bool RegisterOnLocalSdpReadytoSend(
+    int peer_connection_id,
+    LOCALSDPREADYTOSEND_CALLBACK callback);
+WEBRTC_PLUGIN_API bool RegisterOnIceCandiateReadytoSend(
+    int peer_connection_id,
+    ICECANDIDATEREADYTOSEND_CALLBACK callback);
+WEBRTC_PLUGIN_API int ReceivedSdp(int peer_connection_id, const char* sdp);
+WEBRTC_PLUGIN_API bool ReceivedIceCandidate(int peer_connection_id,
+                                            const char* ice_candidate);
+}
+
+#endif  // WEBRTC_EXAMPLES_UNITYPLUGIN_UNITY_PLUGIN_APIS_H_