diff --git a/test/src/webrtcdemo/sample_easy/.gitignore b/test/src/webrtcdemo/sample_easy/.gitignore new file mode 100644 index 0000000..7673e08 --- /dev/null +++ b/test/src/webrtcdemo/sample_easy/.gitignore @@ -0,0 +1,7 @@ +cmake_install.cmake +CMakeCache.txt +Makefile +sample +build/* +CMakeFiles/* +xcode/* diff --git a/test/src/webrtcdemo/sample_easy/CMakeLists.txt b/test/src/webrtcdemo/sample_easy/CMakeLists.txt new file mode 100644 index 0000000..2cb2107 --- /dev/null +++ b/test/src/webrtcdemo/sample_easy/CMakeLists.txt @@ -0,0 +1,50 @@ +cmake_minimum_required(VERSION 2.8) + +include_directories("${LIBWEBRTC_PATH}/include") +include_directories("${LIBWEBRTC_PATH}/include/webrtc") + +link_directories("${LIBWEBRTC_PATH}/lib") + +if(${CMAKE_SYSTEM_NAME} MATCHES "Darwin") + # macOS + file(READ ${LIBWEBRTC_PATH}/exports_libwebrtc.txt webrtc_libs) + string(REGEX REPLACE "lib([^.]+).a[\r\n]*" "\\1;" webrtc_libs "${webrtc_libs}") + + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DWEBRTC_MAC=1 -DWEBRTC_POSIX=1") + find_library(CORE_FOUNDATION CoreFoundation) + list(APPEND webrtc_libs ${CORE_FOUNDATION}) + find_library(FOUNDATION Foundation) + list(APPEND webrtc_libs ${FOUNDATION}) + find_library(CORE_AUDIO CoreAudio) + list(APPEND webrtc_libs ${CORE_AUDIO}) + find_library(AUDIO_TOOLBOX AudioToolbox) + list(APPEND webrtc_libs ${AUDIO_TOOLBOX}) + find_library(CORE_GRAPHICS CoreGraphics) + list(APPEND webrtc_libs ${CORE_GRAPHICS}) + +elseif(${CMAKE_SYSTEM_NAME} MATCHES "Linux" AND ${CMAKE_SYSTEM_PROCESSOR} MATCHES "^arm") + # Linux + list(APPEND webrtc_libs "-Wl,--start-group") + + file(READ ${LIBWEBRTC_PATH}/exports_libwebrtc.txt webrtc_read_tmp) + string(REGEX REPLACE "lib([^.]+)\.a[\r\n]*" "\\1;" webrtc_read_tmp "${webrtc_read_tmp}") + list(APPEND webrtc_libs "${webrtc_read_tmp}") + + list(APPEND webrtc_libs "-Wl,--end-group") + + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DWEBRTC_LINUX=1 -DWEBRTC_POSIX=1 -D_GLIBCXX_USE_CXX11_ABI=0") + pkg_search_module(X11 REQUIRED x11) + list(APPEND webrtc_libs ${X11_LIBRARIES}) +endif() + +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11") +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -W -Wall -Wno-unused-parameter") +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -O0 -g") + +add_executable(sample + main.cpp + ) + +target_link_libraries(sample + ${webrtc_libs} + ) diff --git a/test/src/webrtcdemo/sample_easy/LICENSE b/test/src/webrtcdemo/sample_easy/LICENSE new file mode 100644 index 0000000..f30bc4c --- /dev/null +++ b/test/src/webrtcdemo/sample_easy/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2016 Yuji Ito + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/test/src/webrtcdemo/sample_easy/README.md b/test/src/webrtcdemo/sample_easy/README.md new file mode 100644 index 0000000..547740a --- /dev/null +++ b/test/src/webrtcdemo/sample_easy/README.md @@ -0,0 +1,120 @@ +# WebRTC C++ sample +Sample program for using WebRTC(DataChannel) on C++. + +# Requirement + +* Mac OSX +* Please download libwebrtc(It is precompiled chrome's WebRTC) and unarchive it.(https://github.com/llamerada-jp/libwebrtc) + +# Compile + +```sh +$ cd +$ git clone --depth 1 https://github.com/llamerada-jp/webrtc-cpp-sample.git +$ cd webrtc-cpp-sample +$ git submodule init +$ git submodule update +$ cmake -DLIBWEBRTC_PATH= . +$ make +``` + +# Run + +This sample use two consoles to try interprocess communication by WebRTC. +It maybe cannot communicate over NAT each other, because it does not use ICE server. + +## Connection + +memo : On this sample, Some commands requireing parameter need line of only a semicolon after parameter. + +At CONSOLE-1. + +```sh +$ cd +$ ./sample +0x7fff791c9000:Main thread +0x700000081000:RTC thread +sdp1 +0x700000081000:PeerConnectionObserver::RenegotiationNeeded +0x700000081000:CreateSessionDescriptionObserver::OnSuccess +0x700000081000:PeerConnectionObserver::SignalingChange(1) +Offer SDP:begin + +Offer SDP:end +0x700000081000:SetSessionDescriptionObserver::OnSuccess +0x700000081000:PeerConnectionObserver::IceGatheringChange(1) +0x700000081000:PeerConnectionObserver::IceCandidate +0x700000081000:PeerConnectionObserver::IceCandidate +0x700000081000:PeerConnectionObserver::IceCandidate +0x700000081000:PeerConnectionObserver::IceGatheringChange(2) +sdp3 + +; +0x700000081000:PeerConnectionObserver::SignalingChange(0) +0x700000081000:PeerConnectionObserver::IceConnectionChange(1) +0x700000081000:SetSessionDescriptionObserver::OnSuccess +ice1 + +0x700000081000:PeerConnectionObserver::IceConnectionChange(2) +0x700000081000:PeerConnectionObserver::IceConnectionChange(3) +0x700000081000:DataChannelObserver::StateChange +0x700000081000:PeerConnectionObserver::DataChannel(0x7fd8cb608750, 0x7fd8cb71bef0) +ice2 + +; +``` + +At CONSOLE-2. + +```sh +$ cd +$ ./sample +0x7fff791c9000:Main thread +0x700000081000:RTC thread +sdp2 + +; +0x700000081000:PeerConnectionObserver::RenegotiationNeeded +0x700000081000:PeerConnectionObserver::SignalingChange(3) +0x700000081000:SetSessionDescriptionObserver::OnSuccess +0x700000081000:CreateSessionDescriptionObserver::OnSuccess +0x700000081000:PeerConnectionObserver::SignalingChange(0) +Answer SDP:begin + +Answer SDP:end +0x700000081000:SetSessionDescriptionObserver::OnSuccess +0x700000081000:PeerConnectionObserver::IceGatheringChange(1) +0x700000081000:PeerConnectionObserver::IceCandidate +0x700000081000:PeerConnectionObserver::IceCandidate +0x700000081000:PeerConnectionObserver::IceCandidate +0x700000081000:PeerConnectionObserver::IceGatheringChange(2) +ice2 + +; +0x700000081000:PeerConnectionObserver::IceConnectionChange(1) +0x700000081000:PeerConnectionObserver::IceConnectionChange(2) +0x700000081000:DataChannelObserver::StateChange +0x700000081000:PeerConnectionObserver::DataChannel(0x7fa739e0c0d0, 0x7fa739e08b80) +ice1 + +``` + +## Send message + +You can send messages, after connection is enabled. + +``` +send +Hello world. +; +``` + +## Quit + +You can watch sequence of quit by typing of "quit". + +``` +quit +``` + +EOD diff --git a/test/src/webrtcdemo/sample_easy/main.cpp b/test/src/webrtcdemo/sample_easy/main.cpp new file mode 100644 index 0000000..0965f57 --- /dev/null +++ b/test/src/webrtcdemo/sample_easy/main.cpp @@ -0,0 +1,399 @@ + +#include +#include +#include +#include +#include +#include + +// 環境に合わせてマクロ定義が必要 +//#define WEBRTC_ANDROID 1 +//#define WEBRTC_IOS 1 +//#define WEBRTC_LINUX 1 +#define WEBRTC_MAC 1 +#define WEBRTC_POSIX 1 +//#define WEBRTC_WIN 1 + +// WebRTC関連のヘッダ +#include +#include +#include +#include +#include +#include +#include + +// picojsonはコピペ用データ構造を作るために使う +#include "picojson/picojson.h" + +class Connection { + public: + rtc::scoped_refptr peer_connection; + rtc::scoped_refptr data_channel; + std::string sdp_type; + picojson::array ice_array; + + // Offer/Answerの作成が成功したら、LocalDescriptionとして設定 & 相手に渡す文字列として表示 + void onSuccessCSD(webrtc::SessionDescriptionInterface* desc) { + peer_connection->SetLocalDescription(ssdo, desc); + + std::string sdp; + desc->ToString(&sdp); + std::cout << sdp_type << " SDP:begin" << std::endl << sdp << sdp_type << " SDP:end" << std::endl; + } + + // ICEを取得したら、表示用JSON配列の末尾に追加 + void onIceCandidate(const webrtc::IceCandidateInterface* candidate) { + picojson::object ice; + std::string candidate_str; + candidate->ToString(&candidate_str); + ice.insert(std::make_pair("candidate", picojson::value(candidate_str))); + ice.insert(std::make_pair("sdpMid", picojson::value(candidate->sdp_mid()))); + ice.insert(std::make_pair("sdpMLineIndex", picojson::value(static_cast(candidate->sdp_mline_index())))); + ice_array.push_back(picojson::value(ice)); + } + + class PCO : public webrtc::PeerConnectionObserver { + private: + Connection& parent; + + public: + PCO(Connection& parent) : parent(parent) { + } + + void OnSignalingChange(webrtc::PeerConnectionInterface::SignalingState new_state) override { + std::cout << std::this_thread::get_id() << ":" + << "PeerConnectionObserver::SignalingChange(" << new_state << ")" << std::endl; + }; + + void OnAddStream(rtc::scoped_refptr stream) override { + std::cout << std::this_thread::get_id() << ":" + << "PeerConnectionObserver::AddStream" << std::endl; + }; + + void OnRemoveStream(rtc::scoped_refptr stream) override { + std::cout << std::this_thread::get_id() << ":" + << "PeerConnectionObserver::RemoveStream" << std::endl; + }; + + void OnDataChannel(rtc::scoped_refptr data_channel) override { + std::cout << std::this_thread::get_id() << ":" + << "PeerConnectionObserver::DataChannel(" << data_channel + << ", " << parent.data_channel.get() << ")" << std::endl; + // Answer送信側は、onDataChannelでDataChannelの接続を受け付ける + parent.data_channel = data_channel; + parent.data_channel->RegisterObserver(&parent.dco); + }; + + void OnRenegotiationNeeded() override { + std::cout << std::this_thread::get_id() << ":" + << "PeerConnectionObserver::RenegotiationNeeded" << std::endl; + }; + + void OnIceConnectionChange(webrtc::PeerConnectionInterface::IceConnectionState new_state) override { + std::cout << std::this_thread::get_id() << ":" + << "PeerConnectionObserver::IceConnectionChange(" << new_state << ")" << std::endl; + }; + + void OnIceGatheringChange(webrtc::PeerConnectionInterface::IceGatheringState new_state) override { + std::cout << std::this_thread::get_id() << ":" + << "PeerConnectionObserver::IceGatheringChange(" << new_state << ")" << std::endl; + }; + + void OnIceCandidate(const webrtc::IceCandidateInterface* candidate) override { + std::cout << std::this_thread::get_id() << ":" + << "PeerConnectionObserver::IceCandidate" << std::endl; + parent.onIceCandidate(candidate); + }; + }; + + class DCO : public webrtc::DataChannelObserver { + private: + Connection& parent; + + public: + DCO(Connection& parent) : parent(parent) { + } + + // 接続状況が変化した時に発火する。切断は発火タイミングで値を確認して検知可能 + void OnStateChange() override { + std::cout << std::this_thread::get_id() << ":" + << "DataChannelObserver::StateChange" << std::endl; + }; + + // メッセージ受信 + void OnMessage(const webrtc::DataBuffer& buffer) override { + std::cout << std::this_thread::get_id() << ":" + << "DataChannelObserver::Message" << std::endl; + std::cout << std::string(buffer.data.data(), buffer.data.size()) << std::endl; + }; + + void OnBufferedAmountChange(uint64_t previous_amount) override { + std::cout << std::this_thread::get_id() << ":" + << "DataChannelObserver::BufferedAmountChange(" << previous_amount << ")" << std::endl; + }; + }; + + class CSDO : public webrtc::CreateSessionDescriptionObserver { + private: + Connection& parent; + + public: + CSDO(Connection& parent) : parent(parent) { + } + + void OnSuccess(webrtc::SessionDescriptionInterface* desc) override { + std::cout << std::this_thread::get_id() << ":" + << "CreateSessionDescriptionObserver::OnSuccess" << std::endl; + parent.onSuccessCSD(desc); + }; + + void OnFailure(const std::string& error) override { + std::cout << std::this_thread::get_id() << ":" + << "CreateSessionDescriptionObserver::OnFailure" << std::endl << error << std::endl; + }; + }; + + class SSDO : public webrtc::SetSessionDescriptionObserver { + private: + Connection& parent; + + public: + SSDO(Connection& parent) : parent(parent) { + } + + void OnSuccess() override { + std::cout << std::this_thread::get_id() << ":" + << "SetSessionDescriptionObserver::OnSuccess" << std::endl; + }; + + void OnFailure(const std::string& error) override { + std::cout << std::this_thread::get_id() << ":" + << "SetSessionDescriptionObserver::OnFailure" << std::endl << error << std::endl; + }; + }; + + PCO pco; + DCO dco; + rtc::scoped_refptr csdo; + rtc::scoped_refptr ssdo; + + Connection() : + pco(*this), + dco(*this), + csdo(new rtc::RefCountedObject(*this)), + ssdo(new rtc::RefCountedObject(*this)) { + } +}; + +std::unique_ptr thread; +rtc::scoped_refptr peer_connection_factory; +webrtc::PeerConnectionInterface::RTCConfiguration configuration; +Connection connection; +rtc::PhysicalSocketServer socket_server; + +class CustomRunnable : public rtc::Runnable { + public: + void Run(rtc::Thread* subthread) override { + peer_connection_factory = webrtc::CreatePeerConnectionFactory( + webrtc::CreateBuiltinAudioEncoderFactory(), + webrtc::CreateBuiltinAudioDecoderFactory()); + if (peer_connection_factory.get() == nullptr) { + std::cout << "Error on CreatePeerConnectionFactory." << std::endl; + return; + } + + subthread->Run(); + } +}; + +void cmd_sdp1() { + connection.peer_connection = peer_connection_factory->CreatePeerConnection(configuration, nullptr, nullptr, &connection.pco); + + webrtc::DataChannelInit config; + // DataChannelの設定 + + connection.data_channel = connection.peer_connection->CreateDataChannel("data_channel", &config); + connection.data_channel->RegisterObserver(&connection.dco); + + if (connection.peer_connection.get() == nullptr) { + peer_connection_factory = nullptr; + std::cout << "Error on CreatePeerConnection." << std::endl; + return; + } + connection.sdp_type = "Offer"; // 表示用の文字列、webrtcの動作には関係ない + connection.peer_connection->CreateOffer(connection.csdo, nullptr); +} + +void cmd_sdp2(const std::string& parameter) { + connection.peer_connection = peer_connection_factory->CreatePeerConnection(configuration, nullptr, nullptr, &connection.pco); + + if (connection.peer_connection.get() == nullptr) { + peer_connection_factory = nullptr; + std::cout << "Error on CreatePeerConnection." << std::endl; + return; + } + webrtc::SdpParseError error; + webrtc::SessionDescriptionInterface* session_description( + webrtc::CreateSessionDescription("offer", parameter, &error)); + if (session_description == nullptr) { + std::cout << "Error on CreateSessionDescription." << std::endl + << error.line << std::endl + << error.description << std::endl; + std::cout << "Offer SDP:begin" << std::endl << parameter << std::endl << "Offer SDP:end" << std::endl; + } + connection.peer_connection->SetRemoteDescription(connection.ssdo, session_description); + + connection.sdp_type = "Answer"; // 表示用の文字列、webrtcの動作には関係ない + connection.peer_connection->CreateAnswer(connection.csdo, nullptr); +} + +void cmd_sdp3(const std::string& parameter) { + webrtc::SdpParseError error; + webrtc::SessionDescriptionInterface* session_description( + webrtc::CreateSessionDescription("answer", parameter, &error)); + if (session_description == nullptr) { + std::cout << "Error on CreateSessionDescription." << std::endl + << error.line << std::endl + << error.description << std::endl; + std::cout << "Answer SDP:begin" << std::endl << parameter << std::endl << "Answer SDP:end" << std::endl; + } + connection.peer_connection->SetRemoteDescription(connection.ssdo, session_description); +} + +void cmd_ice1() { + std::cout << picojson::value(connection.ice_array).serialize(true) << std::endl; + connection.ice_array.clear(); +} + +void cmd_ice2(const std::string& parameter) { + picojson::value v; + std::string err = picojson::parse(v, parameter); + if (!err.empty()) { + std::cout << "Error on parse json : " << err << std::endl; + return; + } + + webrtc::SdpParseError err_sdp; + for (auto& ice_it : v.get()) { + picojson::object& ice_json = ice_it.get(); + webrtc::IceCandidateInterface* ice = + CreateIceCandidate(ice_json.at("sdpMid").get(), + static_cast(ice_json.at("sdpMLineIndex").get()), + ice_json.at("candidate").get(), + &err_sdp); + if (!err_sdp.line.empty() && !err_sdp.description.empty()) { + std::cout << "Error on CreateIceCandidate" << std::endl + << err_sdp.line << std::endl + << err_sdp.description << std::endl; + return; + } + connection.peer_connection->AddIceCandidate(ice); + } +} + +void cmd_send(const std::string& parameter) { + webrtc::DataBuffer buffer(rtc::CopyOnWriteBuffer(parameter.c_str(), parameter.size()), true); + std::cout << "Send(" << connection.data_channel->state() << ")" << std::endl; + connection.data_channel->Send(buffer); +} + +void cmd_quit() { + // スレッドを活かしながらCloseしないと、別スレッドからのイベント待ちになり終了できなくなる + connection.peer_connection->Close(); + connection.peer_connection = nullptr; + connection.data_channel = nullptr; + peer_connection_factory = nullptr; + // リソースを開放したらスレッドを止めてOK + thread->Quit(); +} + +int main(int argc, char* argv[]) { + // 第三引数にtrueを指定すると、WebRTC関連の引数をargvから削除してくれるらしい + rtc::FlagList::SetFlagsFromCommandLine(&argc, argv, true); + rtc::FlagList::Print(nullptr, false); + + std::cout << std::this_thread::get_id() << ":" + << "Main thread" << std::endl; + + // GoogleのSTUNサーバを利用 + webrtc::PeerConnectionInterface::IceServer ice_server; + ice_server.uri = "stun:stun.l.google.com:19302"; + configuration.servers.push_back(ice_server); + + thread.reset(new rtc::Thread(&socket_server)); + + rtc::InitializeSSL(); + + CustomRunnable runnable; + thread->Start(&runnable); + + std::string line; + std::string command; + std::string parameter; + bool is_cmd_mode = true; + + while (std::getline(std::cin, line)) { + if (is_cmd_mode) { + if (line == "") { + continue; + + } else if (line == "sdp1") { + cmd_sdp1(); + + } else if (line == "sdp2") { + command = "sdp2"; + is_cmd_mode = false; + + } else if (line == "sdp3") { + command = "sdp3"; + is_cmd_mode = false; + + } else if (line == "ice1") { + cmd_ice1(); + + } else if (line == "ice2") { + command = "ice2"; + is_cmd_mode = false; + + } else if (line == "send") { + command = "send"; + is_cmd_mode = false; + + } else if (line == "quit") { + cmd_quit(); + break; + + } else { + std::cout << "?" << line << std::endl; + } + + } else { + if (line == ";") { + if (command == "sdp2") { + cmd_sdp2(parameter); + + } else if (command == "sdp3") { + cmd_sdp3(parameter); + + } else if (command == "ice2") { + cmd_ice2(parameter); + + } else if (command == "send") { + cmd_send(parameter); + } + + parameter = ""; + is_cmd_mode = true; + + } else { + parameter += line + "\n"; + } + } + } + + thread.reset(); + rtc::CleanupSSL(); + + return 0; +} diff --git a/test/src/webrtcdemo/sample_easy/sample.html b/test/src/webrtcdemo/sample_easy/sample.html new file mode 100644 index 0000000..9029120 --- /dev/null +++ b/test/src/webrtcdemo/sample_easy/sample.html @@ -0,0 +1,168 @@ + + + + WebRTC C++ sample + + +
+ +
+
+ + + + + + + +
+
+ +
+ + +