From ad7cad8aba7ec1973be263ab5fd330b4a2244e19 Mon Sep 17 00:00:00 2001 From: gyzhou Date: Thu, 11 May 2017 16:10:03 -0700 Subject: [PATCH] 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} --- webrtc/examples/BUILD.gn | 34 ++ webrtc/examples/unityplugin/OWNERS | 1 + webrtc/examples/unityplugin/README | 204 +++++++ .../unityplugin/simple_peer_connection.cc | 514 ++++++++++++++++++ .../unityplugin/simple_peer_connection.h | 125 +++++ .../examples/unityplugin/unity_plugin_apis.cc | 185 +++++++ .../examples/unityplugin/unity_plugin_apis.h | 83 +++ 7 files changed, 1146 insertions(+) create mode 100644 webrtc/examples/unityplugin/OWNERS create mode 100644 webrtc/examples/unityplugin/README create mode 100644 webrtc/examples/unityplugin/simple_peer_connection.cc create mode 100644 webrtc/examples/unityplugin/simple_peer_connection.h create mode 100644 webrtc/examples/unityplugin/unity_plugin_apis.cc create mode 100644 webrtc/examples/unityplugin/unity_plugin_apis.h diff --git a/webrtc/examples/BUILD.gn b/webrtc/examples/BUILD.gn index 80b3efaf81..f812f08475 100644 --- a/webrtc/examples/BUILD.gn +++ b/webrtc/examples/BUILD.gn @@ -603,6 +603,40 @@ if (is_linux || is_win) { } } +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 0000000000..61ea9a97f5 --- /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 0000000000..eade9ef5fa --- /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 0000000000..ee959b7aab --- /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 + +#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 g_worker_thread; +static std::unique_ptr g_signaling_thread; +static rtc::scoped_refptr + 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(); + } + 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 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 stream) { + LOG(INFO) << __FUNCTION__ << " " << stream->label(); + remote_stream_ = stream; + + SetAudioControl(); +} + +std::unique_ptr +SimplePeerConnection::OpenVideoCaptureDevice() { + std::vector device_names; + { + std::unique_ptr 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 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 stream = + g_peer_connection_factory->CreateLocalMediaStream(kStreamLabel); + + rtc::scoped_refptr 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 capture = OpenVideoCaptureDevice(); + if (capture) { + rtc::scoped_refptr 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> + 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 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(number_of_channels), + static_cast(number_of_frames)); +} + +std::vector SimplePeerConnection::GetRemoteAudioTrackSsrcs() { + std::vector> receivers = + peer_connection_->GetReceivers(); + + std::vector ssrcs; + for (const auto& receiver : receivers) { + if (receiver->media_type() != cricket::MEDIA_TYPE_AUDIO) + continue; + + std::vector 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 0000000000..2950e12749 --- /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 +#include +#include +#include + +#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 OpenVideoCaptureDevice(); + void SetAudioControl(); + + // PeerConnectionObserver implementation. + void OnSignalingChange( + webrtc::PeerConnectionInterface::SignalingState new_state) override {} + void OnAddStream( + rtc::scoped_refptr stream) override; + void OnRemoveStream( + rtc::scoped_refptr stream) override {} + void OnDataChannel( + rtc::scoped_refptr 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 GetRemoteAudioTrackSsrcs(); + + private: + rtc::scoped_refptr peer_connection_; + rtc::scoped_refptr data_channel_; + std::map > + 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 0000000000..7b510bdf09 --- /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 +#include + +#include "webrtc/examples/unityplugin/simple_peer_connection.h" + +namespace { +static int g_peer_connection_id = 1; +static std::map> + g_peer_connection_map; +} // namespace + +int CreatePeerConnection() { + g_peer_connection_map[g_peer_connection_id] = + new rtc::RefCountedObject(); + + 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(); + 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 0000000000..bcd1af362d --- /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 + +// 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_