no message
parent
770cda1e03
commit
27b515b763
|
@ -0,0 +1,7 @@
|
||||||
|
cmake_install.cmake
|
||||||
|
CMakeCache.txt
|
||||||
|
Makefile
|
||||||
|
sample
|
||||||
|
build/*
|
||||||
|
CMakeFiles/*
|
||||||
|
xcode/*
|
|
@ -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}
|
||||||
|
)
|
|
@ -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.
|
|
@ -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 <path to work>
|
||||||
|
$ 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=<path to unarchived webrtc> .
|
||||||
|
$ 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 <path to work>
|
||||||
|
$ ./sample
|
||||||
|
0x7fff791c9000:Main thread
|
||||||
|
0x700000081000:RTC thread
|
||||||
|
sdp1
|
||||||
|
0x700000081000:PeerConnectionObserver::RenegotiationNeeded
|
||||||
|
0x700000081000:CreateSessionDescriptionObserver::OnSuccess
|
||||||
|
0x700000081000:PeerConnectionObserver::SignalingChange(1)
|
||||||
|
Offer SDP:begin
|
||||||
|
<Copy displayed string to the clipboard as STRING-A.>
|
||||||
|
Offer SDP:end
|
||||||
|
0x700000081000:SetSessionDescriptionObserver::OnSuccess
|
||||||
|
0x700000081000:PeerConnectionObserver::IceGatheringChange(1)
|
||||||
|
0x700000081000:PeerConnectionObserver::IceCandidate
|
||||||
|
0x700000081000:PeerConnectionObserver::IceCandidate
|
||||||
|
0x700000081000:PeerConnectionObserver::IceCandidate
|
||||||
|
0x700000081000:PeerConnectionObserver::IceGatheringChange(2)
|
||||||
|
sdp3
|
||||||
|
<Paste STRING-B that it displayed on CONSOLE-2.>
|
||||||
|
;
|
||||||
|
0x700000081000:PeerConnectionObserver::SignalingChange(0)
|
||||||
|
0x700000081000:PeerConnectionObserver::IceConnectionChange(1)
|
||||||
|
0x700000081000:SetSessionDescriptionObserver::OnSuccess
|
||||||
|
ice1
|
||||||
|
<Copy displayed string to the clipboard as STRING-C.>
|
||||||
|
0x700000081000:PeerConnectionObserver::IceConnectionChange(2)
|
||||||
|
0x700000081000:PeerConnectionObserver::IceConnectionChange(3)
|
||||||
|
0x700000081000:DataChannelObserver::StateChange
|
||||||
|
0x700000081000:PeerConnectionObserver::DataChannel(0x7fd8cb608750, 0x7fd8cb71bef0)
|
||||||
|
ice2
|
||||||
|
<Paste STRING-D that it displayed on CONSOLE-2.>
|
||||||
|
;
|
||||||
|
```
|
||||||
|
|
||||||
|
At CONSOLE-2.
|
||||||
|
|
||||||
|
```sh
|
||||||
|
$ cd <path to work>
|
||||||
|
$ ./sample
|
||||||
|
0x7fff791c9000:Main thread
|
||||||
|
0x700000081000:RTC thread
|
||||||
|
sdp2
|
||||||
|
<Paste STRING-A that it displayed on CONSOLE-1.>
|
||||||
|
;
|
||||||
|
0x700000081000:PeerConnectionObserver::RenegotiationNeeded
|
||||||
|
0x700000081000:PeerConnectionObserver::SignalingChange(3)
|
||||||
|
0x700000081000:SetSessionDescriptionObserver::OnSuccess
|
||||||
|
0x700000081000:CreateSessionDescriptionObserver::OnSuccess
|
||||||
|
0x700000081000:PeerConnectionObserver::SignalingChange(0)
|
||||||
|
Answer SDP:begin
|
||||||
|
<Copy displayed string to the clipboard as STRING-B.>
|
||||||
|
Answer SDP:end
|
||||||
|
0x700000081000:SetSessionDescriptionObserver::OnSuccess
|
||||||
|
0x700000081000:PeerConnectionObserver::IceGatheringChange(1)
|
||||||
|
0x700000081000:PeerConnectionObserver::IceCandidate
|
||||||
|
0x700000081000:PeerConnectionObserver::IceCandidate
|
||||||
|
0x700000081000:PeerConnectionObserver::IceCandidate
|
||||||
|
0x700000081000:PeerConnectionObserver::IceGatheringChange(2)
|
||||||
|
ice2
|
||||||
|
<Paste STRING-C that it displayed on CONSOLE-1.>
|
||||||
|
;
|
||||||
|
0x700000081000:PeerConnectionObserver::IceConnectionChange(1)
|
||||||
|
0x700000081000:PeerConnectionObserver::IceConnectionChange(2)
|
||||||
|
0x700000081000:DataChannelObserver::StateChange
|
||||||
|
0x700000081000:PeerConnectionObserver::DataChannel(0x7fa739e0c0d0, 0x7fa739e08b80)
|
||||||
|
ice1
|
||||||
|
<Copy displayed string to the clipboard as STRING-D.>
|
||||||
|
```
|
||||||
|
|
||||||
|
## 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
|
|
@ -0,0 +1,399 @@
|
||||||
|
|
||||||
|
#include <iostream>
|
||||||
|
#include <memory>
|
||||||
|
#include <mutex>
|
||||||
|
#include <queue>
|
||||||
|
#include <string>
|
||||||
|
#include <thread>
|
||||||
|
|
||||||
|
// 環境に合わせてマクロ定義が必要
|
||||||
|
//#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 <webrtc/api/audio_codecs/builtin_audio_decoder_factory.h>
|
||||||
|
#include <webrtc/api/audio_codecs/builtin_audio_encoder_factory.h>
|
||||||
|
#include <webrtc/api/peerconnectioninterface.h>
|
||||||
|
#include <webrtc/rtc_base/flags.h>
|
||||||
|
#include <webrtc/rtc_base/physicalsocketserver.h>
|
||||||
|
#include <webrtc/rtc_base/ssladapter.h>
|
||||||
|
#include <webrtc/rtc_base/thread.h>
|
||||||
|
|
||||||
|
// picojsonはコピペ用データ構造を作るために使う
|
||||||
|
#include "picojson/picojson.h"
|
||||||
|
|
||||||
|
class Connection {
|
||||||
|
public:
|
||||||
|
rtc::scoped_refptr<webrtc::PeerConnectionInterface> peer_connection;
|
||||||
|
rtc::scoped_refptr<webrtc::DataChannelInterface> 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<double>(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<webrtc::MediaStreamInterface> stream) override {
|
||||||
|
std::cout << std::this_thread::get_id() << ":"
|
||||||
|
<< "PeerConnectionObserver::AddStream" << std::endl;
|
||||||
|
};
|
||||||
|
|
||||||
|
void OnRemoveStream(rtc::scoped_refptr<webrtc::MediaStreamInterface> stream) override {
|
||||||
|
std::cout << std::this_thread::get_id() << ":"
|
||||||
|
<< "PeerConnectionObserver::RemoveStream" << std::endl;
|
||||||
|
};
|
||||||
|
|
||||||
|
void OnDataChannel(rtc::scoped_refptr<webrtc::DataChannelInterface> 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<char>(), 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> csdo;
|
||||||
|
rtc::scoped_refptr<SSDO> ssdo;
|
||||||
|
|
||||||
|
Connection() :
|
||||||
|
pco(*this),
|
||||||
|
dco(*this),
|
||||||
|
csdo(new rtc::RefCountedObject<CSDO>(*this)),
|
||||||
|
ssdo(new rtc::RefCountedObject<SSDO>(*this)) {
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
std::unique_ptr<rtc::Thread> thread;
|
||||||
|
rtc::scoped_refptr<webrtc::PeerConnectionFactoryInterface> 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::array>()) {
|
||||||
|
picojson::object& ice_json = ice_it.get<picojson::object>();
|
||||||
|
webrtc::IceCandidateInterface* ice =
|
||||||
|
CreateIceCandidate(ice_json.at("sdpMid").get<std::string>(),
|
||||||
|
static_cast<int>(ice_json.at("sdpMLineIndex").get<double>()),
|
||||||
|
ice_json.at("candidate").get<std::string>(),
|
||||||
|
&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;
|
||||||
|
}
|
|
@ -0,0 +1,168 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>WebRTC C++ sample</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div>
|
||||||
|
<textarea id="stdout" cols="100" rows="32" readonly>output</textarea>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<button type="button" onclick="sdp1();">sdp1</button>
|
||||||
|
<button type="button" onclick="sdp2();">sdp2</button>
|
||||||
|
<button type="button" onclick="sdp3();">sdp3</button>
|
||||||
|
<button type="button" onclick="ice1();">ice1</button>
|
||||||
|
<button type="button" onclick="ice2();">ice2</button>
|
||||||
|
<button type="button" onclick="send();">send</button>
|
||||||
|
<button type="button" onclick="quit();">quit</button>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<textarea id="stdin" cols="100" rows="8"></textarea>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
<script type="text/javascript">
|
||||||
|
var dataChannel = null;
|
||||||
|
var peerConnection = null;
|
||||||
|
var pcConfig = {
|
||||||
|
iceServers:[
|
||||||
|
{url:'stun:stun.l.google.com:19302'}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
var iceArray = [];
|
||||||
|
|
||||||
|
function output(log) {
|
||||||
|
var stdout = document.getElementById('stdout');
|
||||||
|
stdout.value = stdout.value + log + '\n';
|
||||||
|
}
|
||||||
|
|
||||||
|
function input() {
|
||||||
|
var stdin = document.getElementById('stdin');
|
||||||
|
var input = stdin.value;
|
||||||
|
stdin.value = '';
|
||||||
|
return input;
|
||||||
|
}
|
||||||
|
|
||||||
|
function onDataChannel(evt) {
|
||||||
|
output('onDataChannel');
|
||||||
|
dataChannel = evt.channel;
|
||||||
|
setDataChannelEvents(dataChannel);
|
||||||
|
}
|
||||||
|
|
||||||
|
function onIceCandidate(evt) {
|
||||||
|
if (evt.candidate) {
|
||||||
|
iceArray.push(evt.candidate);
|
||||||
|
} else {
|
||||||
|
output("end of ice candidate" + evt.eventPhase);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function onIceConnectionStateChange(evt) {
|
||||||
|
output("onIceConnectionStateChange:" + peerConnection.iceConnectionState);
|
||||||
|
}
|
||||||
|
|
||||||
|
function setDataChannelEvents(dataChannel) {
|
||||||
|
dataChannel.onerror = function (error) {
|
||||||
|
ouptput('Data Channel onerror:' + error);
|
||||||
|
};
|
||||||
|
|
||||||
|
dataChannel.onmessage = function (event) {
|
||||||
|
output('Data Channel onmessage:' + event.data);
|
||||||
|
output(String.fromCharCode.apply("", new Uint8Array(event.data)));
|
||||||
|
};
|
||||||
|
|
||||||
|
dataChannel.onopen = function () {
|
||||||
|
output('Data Channel onopen');
|
||||||
|
};
|
||||||
|
|
||||||
|
dataChannel.onclose = function () {
|
||||||
|
output('Data Channel onclose');
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function sdp1() {
|
||||||
|
function onOfferSuccess(sessionDescription) {
|
||||||
|
peerConnection.setLocalDescription(sessionDescription);
|
||||||
|
output('createOffer -> onOfferSuccess');
|
||||||
|
output('Offer SDP:begin');
|
||||||
|
output(sessionDescription.sdp);
|
||||||
|
output('Offer SDP:end');
|
||||||
|
}
|
||||||
|
|
||||||
|
function onOfferFailure() {
|
||||||
|
output('createOffer -> onOfferFailure');
|
||||||
|
}
|
||||||
|
|
||||||
|
peerConnection = new RTCPeerConnection(pcConfig);
|
||||||
|
peerConnection.onicecandidate = onIceCandidate;
|
||||||
|
peerConnection.ondatachannel = onDataChannel;
|
||||||
|
peerConnection.oniceconnectionstatechange = onIceConnectionStateChange;
|
||||||
|
var dataChannelOptions = {
|
||||||
|
ordered: true, // 順序を保証する
|
||||||
|
maxRetransmitTime: 3000 // ミリ秒
|
||||||
|
};
|
||||||
|
dataChannel = peerConnection.createDataChannel('data_channel', dataChannelOptions);
|
||||||
|
setDataChannelEvents(dataChannel);
|
||||||
|
peerConnection.createOffer(onOfferSuccess, onOfferFailure, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
function sdp2() {
|
||||||
|
function onAnswerSuccess(sessionDescription) {
|
||||||
|
peerConnection.setLocalDescription(sessionDescription);
|
||||||
|
output('createAnswer -> onAnswerSuccess');
|
||||||
|
output('Answer SDP:begin');
|
||||||
|
output(sessionDescription.sdp);
|
||||||
|
output('Answer SDP:end');
|
||||||
|
}
|
||||||
|
|
||||||
|
function onAnswerFailure() {
|
||||||
|
output('createAnswer -> onAnswerFailure');
|
||||||
|
}
|
||||||
|
|
||||||
|
var sdp = new RTCSessionDescription({
|
||||||
|
type: 'offer',
|
||||||
|
sdp: input()
|
||||||
|
});
|
||||||
|
peerConnection = new RTCPeerConnection(pcConfig);
|
||||||
|
peerConnection.onicecandidate = onIceCandidate;
|
||||||
|
peerConnection.ondatachannel = onDataChannel;
|
||||||
|
peerConnection.oniceconnectionstatechange = onIceConnectionStateChange;
|
||||||
|
peerConnection.setRemoteDescription(sdp);
|
||||||
|
peerConnection.createAnswer(onAnswerSuccess, onAnswerFailure, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
function sdp3() {
|
||||||
|
var sdp = new RTCSessionDescription({
|
||||||
|
type: 'answer',
|
||||||
|
sdp: input()
|
||||||
|
});
|
||||||
|
peerConnection.setRemoteDescription(sdp);
|
||||||
|
}
|
||||||
|
|
||||||
|
function ice1() {
|
||||||
|
output('ICE:begin');
|
||||||
|
output(JSON.stringify(iceArray));
|
||||||
|
iceArray = [];
|
||||||
|
output('ICE:end');
|
||||||
|
}
|
||||||
|
|
||||||
|
function ice2() {
|
||||||
|
var ices = JSON.parse(input());
|
||||||
|
for (var ice of ices) {
|
||||||
|
var iceObj = new RTCIceCandidate(ice);
|
||||||
|
peerConnection.addIceCandidate(iceObj);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function send() {
|
||||||
|
var data = input();
|
||||||
|
dataChannel.send(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
function quit() {
|
||||||
|
dataChannel.close();
|
||||||
|
dataChannel = null;
|
||||||
|
peerConnection.close();
|
||||||
|
peerConnection = null;
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
</html>
|
Loading…
Reference in New Issue