From 582d048c4221beddb4e36ce7e78efd0d01592f0b Mon Sep 17 00:00:00 2001 From: zcy <290198252@qq.com> Date: Tue, 19 Oct 2021 00:42:29 +0800 Subject: [PATCH] no message --- test/src/webrtcdemo/CMakeLists.txt | 14 +- .../webrtcdemo/peerconnection/CMakeLists.txt | 45 ++ test/src/webrtcdemo/peerconnection/OWNERS | 1 + .../PeerConnectionClientDemo.sln | 31 + .../PeerConnectionClientDemo/MyCapturer.cc | 65 ++ .../PeerConnectionClientDemo/MyCapturer.h | 41 ++ .../PeerConnectionClientDemo.vcxproj | 168 +++++ .../PeerConnectionClientDemo.vcxproj.filters | 57 ++ .../PeerConnectionClientDemo.vcxproj.user | 4 + .../PeerConnectionClientDemo/conductor.cc | 579 ++++++++++++++++ .../PeerConnectionClientDemo/conductor.h | 146 ++++ .../PeerConnectionClientDemo/defaults.cc | 59 ++ .../PeerConnectionClientDemo/defaults.h | 29 + .../PeerConnectionClientDemo/flag_defs.h | 47 ++ .../PeerConnectionClientDemo/main.cc | 95 +++ .../PeerConnectionClientDemo/main_wnd.cc | 633 ++++++++++++++++++ .../PeerConnectionClientDemo/main_wnd.h | 206 ++++++ .../peer_connection_client.cc | 511 ++++++++++++++ .../peer_connection_client.h | 134 ++++ .../peerconnection/server/data_socket.cc | 333 +++++++++ .../peerconnection/server/data_socket.h | 150 +++++ .../webrtcdemo/peerconnection/server/main.cc | 213 ++++++ .../peerconnection/server/peer_channel.cc | 412 ++++++++++++ .../peerconnection/server/peer_channel.h | 129 ++++ .../peerconnection/server/server_test.html | 237 +++++++ .../webrtcdemo/peerconnection/server/utils.cc | 27 + .../webrtcdemo/peerconnection/server/utils.h | 25 + test/src/webrtcdemo/webrtcdemo.cpp | 11 +- 28 files changed, 4393 insertions(+), 9 deletions(-) create mode 100644 test/src/webrtcdemo/peerconnection/CMakeLists.txt create mode 100644 test/src/webrtcdemo/peerconnection/OWNERS create mode 100644 test/src/webrtcdemo/peerconnection/PeerConnectionClientDemo.sln create mode 100644 test/src/webrtcdemo/peerconnection/PeerConnectionClientDemo/MyCapturer.cc create mode 100644 test/src/webrtcdemo/peerconnection/PeerConnectionClientDemo/MyCapturer.h create mode 100644 test/src/webrtcdemo/peerconnection/PeerConnectionClientDemo/PeerConnectionClientDemo.vcxproj create mode 100644 test/src/webrtcdemo/peerconnection/PeerConnectionClientDemo/PeerConnectionClientDemo.vcxproj.filters create mode 100644 test/src/webrtcdemo/peerconnection/PeerConnectionClientDemo/PeerConnectionClientDemo.vcxproj.user create mode 100644 test/src/webrtcdemo/peerconnection/PeerConnectionClientDemo/conductor.cc create mode 100644 test/src/webrtcdemo/peerconnection/PeerConnectionClientDemo/conductor.h create mode 100644 test/src/webrtcdemo/peerconnection/PeerConnectionClientDemo/defaults.cc create mode 100644 test/src/webrtcdemo/peerconnection/PeerConnectionClientDemo/defaults.h create mode 100644 test/src/webrtcdemo/peerconnection/PeerConnectionClientDemo/flag_defs.h create mode 100644 test/src/webrtcdemo/peerconnection/PeerConnectionClientDemo/main.cc create mode 100644 test/src/webrtcdemo/peerconnection/PeerConnectionClientDemo/main_wnd.cc create mode 100644 test/src/webrtcdemo/peerconnection/PeerConnectionClientDemo/main_wnd.h create mode 100644 test/src/webrtcdemo/peerconnection/PeerConnectionClientDemo/peer_connection_client.cc create mode 100644 test/src/webrtcdemo/peerconnection/PeerConnectionClientDemo/peer_connection_client.h create mode 100644 test/src/webrtcdemo/peerconnection/server/data_socket.cc create mode 100644 test/src/webrtcdemo/peerconnection/server/data_socket.h create mode 100644 test/src/webrtcdemo/peerconnection/server/main.cc create mode 100644 test/src/webrtcdemo/peerconnection/server/peer_channel.cc create mode 100644 test/src/webrtcdemo/peerconnection/server/peer_channel.h create mode 100644 test/src/webrtcdemo/peerconnection/server/server_test.html create mode 100644 test/src/webrtcdemo/peerconnection/server/utils.cc create mode 100644 test/src/webrtcdemo/peerconnection/server/utils.h diff --git a/test/src/webrtcdemo/CMakeLists.txt b/test/src/webrtcdemo/CMakeLists.txt index 4cd8224..fa1d702 100644 --- a/test/src/webrtcdemo/CMakeLists.txt +++ b/test/src/webrtcdemo/CMakeLists.txt @@ -1,7 +1,11 @@ cmake_minimum_required(VERSION 3.12) project(webrtcdemo) +project(stunserver) +project(turnserver) +project(stunprober) -add_definitions(-std=c++11 "/DWIN32_LEAN_AND_MEAN" "/Mdd") + +add_definitions(-std=c++11 "/DWIN32_LEAN_AND_MEAN" "/Mdd" "/DWEBRTC_WIN") message("current dir" ${CMAKE_CURRENT_SOURCE_DIR}) # set(CMAKE_CXX_FLAGS "-fno-elide-constructors") @@ -10,12 +14,14 @@ message(info ${SOURCE}) link_directories("${CMAKE_CURRENT_SOURCE_DIR}/third/lib") link_directories("${CMAKE_CURRENT_SOURCE_DIR}/third/opencv/staticlib") -link_libraries( libwebrtc.lib ws2_32.lib winmm.lib DXGI.lib - ) +link_libraries( libwebrtc.lib ws2_32.lib winmm.lib DXGI.lib) message(${CMAKE_CURRENT_SOURCE_DIR}/third/include) include_directories("${CMAKE_CURRENT_SOURCE_DIR}/third/include") include_directories("${CMAKE_CURRENT_SOURCE_DIR}/third/opencv/include") include_directories("${CMAKE_CURRENT_SOURCE_DIR}/third/opencv/staticlib") -add_executable(webrtcdemo webrtcdemo.cpp) \ No newline at end of file +add_executable(webrtcdemo webrtcdemo.cpp) +add_executable(stunserver stunserver/stunserver_main.cc) +add_executable(turnserver turnserver/read_auth_file.cc turnserver/turnserver_main.cc) +add_executable(stunprober stunprober/main.cc) \ No newline at end of file diff --git a/test/src/webrtcdemo/peerconnection/CMakeLists.txt b/test/src/webrtcdemo/peerconnection/CMakeLists.txt new file mode 100644 index 0000000..4dad886 --- /dev/null +++ b/test/src/webrtcdemo/peerconnection/CMakeLists.txt @@ -0,0 +1,45 @@ +cmake_minimum_required(VERSION 3.12) +project(peer_client) +project(peer_server) + +add_definitions("/DWIN32_LEAN_AND_MEAN" "/DWIN32" "/DWEBRTC_WIN" "/D_SILENCE_ALL_CXX17_DEPRECATION_WARNINGS ") +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++14") +message("current dir" ${CMAKE_CURRENT_SOURCE_DIR}) +aux_source_directory(./client CLIENT_SOURCE) +message(info ${CLIENT_SOURCE}) +aux_source_directory(./server SERVER_SOURCE) +message(info ${SERVER_SOURCE}) + +link_directories("D:/project/c++/generallib/test/src/webrtcdemo/third/lib") +link_directories("../third/opencv/staticlib") +link_directories("../third/absl/lib") + + + +message(info "D:/project/c++/generallib/test/src/webrtcdemo/third/include") +include_directories("D:/project/c++/generallib/test/src/webrtcdemo/third/include") + +add_executable(peer_client ${CLIENT_SOURCE}) +add_executable(peer_server ${SERVER_SOURCE}) + +target_link_libraries(peer_client libwebrtc.lib ws2_32.lib winmm.lib DXGI.lib) +target_link_libraries(peer_server libwebrtc.lib ws2_32.lib winmm.lib + DXGI.lib + absl_base.lib + absl_flags_config.lib + absl_flags.lib + absl_flags_commandlineflag.lib + absl_flags_commandlineflag_internal.lib + absl_flags_internal.lib + absl_flags_marshalling.lib + absl_flags_parse.lib + absl_flags_usage_internal.lib + absl_flags_private_handle_accessor.lib + absl_flags_program_name.lib + absl_flags_reflection.lib + absl_flags_usage.lib + absl_hash.lib + absl_hashtablez_sampler.lib + absl_raw_hash_set.lib + absl_low_level_hash.lib + ) diff --git a/test/src/webrtcdemo/peerconnection/OWNERS b/test/src/webrtcdemo/peerconnection/OWNERS new file mode 100644 index 0000000..0fba125 --- /dev/null +++ b/test/src/webrtcdemo/peerconnection/OWNERS @@ -0,0 +1 @@ +tommi@webrtc.org diff --git a/test/src/webrtcdemo/peerconnection/PeerConnectionClientDemo.sln b/test/src/webrtcdemo/peerconnection/PeerConnectionClientDemo.sln new file mode 100644 index 0000000..95ffd12 --- /dev/null +++ b/test/src/webrtcdemo/peerconnection/PeerConnectionClientDemo.sln @@ -0,0 +1,31 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 15 +VisualStudioVersion = 15.0.28307.960 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "PeerConnectionClientDemo", "PeerConnectionClientDemo\PeerConnectionClientDemo.vcxproj", "{C9FC272C-2B9B-4EEC-A91E-976C68294B87}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|x64 = Debug|x64 + Debug|x86 = Debug|x86 + Release|x64 = Release|x64 + Release|x86 = Release|x86 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {C9FC272C-2B9B-4EEC-A91E-976C68294B87}.Debug|x64.ActiveCfg = Debug|x64 + {C9FC272C-2B9B-4EEC-A91E-976C68294B87}.Debug|x64.Build.0 = Debug|x64 + {C9FC272C-2B9B-4EEC-A91E-976C68294B87}.Debug|x86.ActiveCfg = Debug|Win32 + {C9FC272C-2B9B-4EEC-A91E-976C68294B87}.Debug|x86.Build.0 = Debug|Win32 + {C9FC272C-2B9B-4EEC-A91E-976C68294B87}.Release|x64.ActiveCfg = Release|x64 + {C9FC272C-2B9B-4EEC-A91E-976C68294B87}.Release|x64.Build.0 = Release|x64 + {C9FC272C-2B9B-4EEC-A91E-976C68294B87}.Release|x86.ActiveCfg = Release|Win32 + {C9FC272C-2B9B-4EEC-A91E-976C68294B87}.Release|x86.Build.0 = Release|Win32 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {449DBFFE-8AF9-418C-A8E0-9667D4270C73} + EndGlobalSection +EndGlobal diff --git a/test/src/webrtcdemo/peerconnection/PeerConnectionClientDemo/MyCapturer.cc b/test/src/webrtcdemo/peerconnection/PeerConnectionClientDemo/MyCapturer.cc new file mode 100644 index 0000000..96af0e5 --- /dev/null +++ b/test/src/webrtcdemo/peerconnection/PeerConnectionClientDemo/MyCapturer.cc @@ -0,0 +1,65 @@ +#include "MyCapturer.h" +#include "rtc_base/thread.h" +#include +#include + +MyCapturer::MyCapturer() { + +} + +void MyCapturer::startCapturer() { + auto options = webrtc::DesktopCaptureOptions::CreateDefault(); + options.set_allow_directx_capturer(true); + capturer_ = webrtc::DesktopCapturer::CreateScreenCapturer(options); + capturer_->Start(this); + CaptureFrame(); +} + +webrtc::MediaSourceInterface::SourceState MyCapturer::state() const { + return webrtc::MediaSourceInterface::kLive; +} + +bool MyCapturer::remote() const { + return false; +} + +bool MyCapturer::is_screencast() const { + return true; +} + +absl::optional MyCapturer::needs_denoising() const { + return false; +} + +void MyCapturer::OnCaptureResult(webrtc::DesktopCapturer::Result result, + std::unique_ptr frame) { + if (result != webrtc::DesktopCapturer::Result::SUCCESS) + return; + + int width = frame->size().width(); + int height = frame->size().height(); + + if (!i420_buffer_.get() || + i420_buffer_->width() * i420_buffer_->height() < width * height) { + i420_buffer_ = webrtc::I420Buffer::Create(width, height); + } + libyuv::ConvertToI420(frame->data(), 0, i420_buffer_->MutableDataY(), + i420_buffer_->StrideY(), i420_buffer_->MutableDataU(), + i420_buffer_->StrideU(), i420_buffer_->MutableDataV(), + i420_buffer_->StrideV(), 0, 0, width, height, width, + height, libyuv::kRotate0, libyuv::FOURCC_ARGB); + + OnFrame(webrtc::VideoFrame(i420_buffer_, 0, 0, webrtc::kVideoRotation_0)); +} + +void MyCapturer::OnMessage(rtc::Message* msg) { + if (msg->message_id == 0) + CaptureFrame(); +} + +void MyCapturer::CaptureFrame() { + capturer_->CaptureFrame(); + + rtc::Location loc(__FUNCTION__, __FILE__); + rtc::Thread::Current()->PostDelayed(loc, 33, this, 0); +} \ No newline at end of file diff --git a/test/src/webrtcdemo/peerconnection/PeerConnectionClientDemo/MyCapturer.h b/test/src/webrtcdemo/peerconnection/PeerConnectionClientDemo/MyCapturer.h new file mode 100644 index 0000000..96a4da1 --- /dev/null +++ b/test/src/webrtcdemo/peerconnection/PeerConnectionClientDemo/MyCapturer.h @@ -0,0 +1,41 @@ +#pragma once +/** + *WindowsĻ¼ģ + */ + +#include +#include +#include +#include +#include "rtc_base/thread.h" +#include "media/base/adapted_video_track_source.h" +#include "rtc_base/message_handler.h" + + +class MyCapturer : public rtc::AdaptedVideoTrackSource, + public rtc::MessageHandler, + public webrtc::DesktopCapturer::Callback { + public: + MyCapturer(); + + void startCapturer(); + + void CaptureFrame(); + + bool is_screencast() const override; + + absl::optional needs_denoising() const override; + + webrtc::MediaSourceInterface::SourceState state() const override; + + bool remote() const override; + + void OnCaptureResult(webrtc::DesktopCapturer::Result result, + std::unique_ptr frame) override; + void OnMessage(rtc::Message* msg) override; + + private: + std::unique_ptr capturer_; + rtc::scoped_refptr i420_buffer_; + //mutable volatile int ref_count_; +}; diff --git a/test/src/webrtcdemo/peerconnection/PeerConnectionClientDemo/PeerConnectionClientDemo.vcxproj b/test/src/webrtcdemo/peerconnection/PeerConnectionClientDemo/PeerConnectionClientDemo.vcxproj new file mode 100644 index 0000000..d3da8c5 --- /dev/null +++ b/test/src/webrtcdemo/peerconnection/PeerConnectionClientDemo/PeerConnectionClientDemo.vcxproj @@ -0,0 +1,168 @@ + + + + + Debug + Win32 + + + Release + Win32 + + + Debug + x64 + + + Release + x64 + + + + 15.0 + {C9FC272C-2B9B-4EEC-A91E-976C68294B87} + Win32Proj + PeerConnectionClientDemo + 10.0 + + + + Application + true + v142 + Unicode + + + Application + false + v142 + true + Unicode + + + Application + true + v142 + Unicode + + + Application + false + v142 + true + Unicode + + + + + + + + + + + + + + + + + + + + + true + + + true + + + false + + + false + + + + Level3 + Disabled + true + WIN32;_DEBUG;_WINDOWS;%(PreprocessorDefinitions) + true + + + Windows + true + + + + + Level3 + Disabled + true + _DEBUG;_WINDOWS;NOMINMAX;WIN32_LEAN_AND_MEAN;WEBRTC_WIN;_CRT_SECURE_NO_WARNINGS;WIN32;%(PreprocessorDefinitions) + true + ..\webrtc\include\;..\webrtc\include\third_party\abseil-cpp;..\webrtc\include\third_party\jsoncpp\source\include;..\webrtc\include\third_party\libyuv\include;%(AdditionalIncludeDirectories) + MultiThreadedDebug + + + Windows + true + webrtc.lib;json.obj;json_reader.obj;json_value.obj;json_writer.obj;field_trial.obj;%(AdditionalDependencies) + ..\webrtc\lib\;%(AdditionalLibraryDirectories) + + + + + Level3 + MaxSpeed + true + true + true + WIN32;NDEBUG;_WINDOWS;%(PreprocessorDefinitions) + true + + + Windows + true + true + true + + + + + Level3 + MaxSpeed + true + true + true + NDEBUG;_WINDOWS;%(PreprocessorDefinitions) + true + + + Windows + true + true + true + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/test/src/webrtcdemo/peerconnection/PeerConnectionClientDemo/PeerConnectionClientDemo.vcxproj.filters b/test/src/webrtcdemo/peerconnection/PeerConnectionClientDemo/PeerConnectionClientDemo.vcxproj.filters new file mode 100644 index 0000000..e4dcca0 --- /dev/null +++ b/test/src/webrtcdemo/peerconnection/PeerConnectionClientDemo/PeerConnectionClientDemo.vcxproj.filters @@ -0,0 +1,57 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hh;hpp;hxx;hm;inl;inc;ipp;xsd + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms + + + + + 头文件 + + + 头文件 + + + 头文件 + + + 头文件 + + + 头文件 + + + 头文件 + + + + + 源文件 + + + 源文件 + + + 源文件 + + + 源文件 + + + 源文件 + + + 源文件 + + + \ No newline at end of file diff --git a/test/src/webrtcdemo/peerconnection/PeerConnectionClientDemo/PeerConnectionClientDemo.vcxproj.user b/test/src/webrtcdemo/peerconnection/PeerConnectionClientDemo/PeerConnectionClientDemo.vcxproj.user new file mode 100644 index 0000000..88a5509 --- /dev/null +++ b/test/src/webrtcdemo/peerconnection/PeerConnectionClientDemo/PeerConnectionClientDemo.vcxproj.user @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/test/src/webrtcdemo/peerconnection/PeerConnectionClientDemo/conductor.cc b/test/src/webrtcdemo/peerconnection/PeerConnectionClientDemo/conductor.cc new file mode 100644 index 0000000..60a4592 --- /dev/null +++ b/test/src/webrtcdemo/peerconnection/PeerConnectionClientDemo/conductor.cc @@ -0,0 +1,579 @@ +/* + * Copyright 2012 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 "conductor.h" +#include +#include +#include +#include +#include +#include + +#include "absl/memory/memory.h" +#include "absl/types/optional.h" +#include "api/audio/audio_mixer.h" +#include "api/audio_codecs/audio_decoder_factory.h" +#include "api/audio_codecs/audio_encoder_factory.h" +#include "api/audio_codecs/builtin_audio_decoder_factory.h" +#include "api/audio_codecs/builtin_audio_encoder_factory.h" +#include "api/audio_options.h" +#include "api/create_peerconnection_factory.h" +#include "api/rtp_sender_interface.h" +#include "api/video_codecs/builtin_video_decoder_factory.h" +#include "api/video_codecs/builtin_video_encoder_factory.h" +#include "api/video_codecs/video_decoder_factory.h" +#include "api/video_codecs/video_encoder_factory.h" +#include "defaults.h" +#include "modules/audio_device/include/audio_device.h" +#include "modules/audio_processing/include/audio_processing.h" +#include "modules/video_capture/video_capture.h" +#include "modules/video_capture/video_capture_factory.h" +#include "p2p/base/port_allocator.h" +#include "pc/video_track_source.h" +#include "rtc_base/checks.h" +#include "rtc_base/logging.h" +#include "rtc_base/ref_counted_object.h" +#include "rtc_base/rtc_certificate_generator.h" +#include "rtc_base/strings/json.h" +#include "test/vcm_capturer.h" +#include "MyCapturer.h" + +namespace { +// 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"; + +class DummySetSessionDescriptionObserver + : public webrtc::SetSessionDescriptionObserver { + public: + static DummySetSessionDescriptionObserver* Create() { + return new rtc::RefCountedObject(); + } + virtual void OnSuccess() { RTC_LOG(INFO) << __FUNCTION__; } + virtual void OnFailure(webrtc::RTCError error) { + RTC_LOG(INFO) << __FUNCTION__ << " " << ToString(error.type()) << ": " + << error.message(); + } +}; + +class CapturerTrackSource : public webrtc::VideoTrackSource { + public: + static rtc::scoped_refptr Create() { + const size_t kWidth = 640; + const size_t kHeight = 480; + const size_t kFps = 30; + std::unique_ptr capturer; + std::unique_ptr info( + webrtc::VideoCaptureFactory::CreateDeviceInfo()); + if (!info) { + return nullptr; + } + int num_devices = info->NumberOfDevices(); + for (int i = 0; i < num_devices; ++i) { + capturer = absl::WrapUnique( + webrtc::test::VcmCapturer::Create(kWidth, kHeight, kFps, i)); + if (capturer) { + return new + rtc::RefCountedObject(std::move(capturer)); + } + } + + return nullptr; + } + + protected: + explicit CapturerTrackSource( + std::unique_ptr capturer) + : VideoTrackSource(/*remote=*/false), capturer_(std::move(capturer)) {} + + private: + rtc::VideoSourceInterface* source() override { + return capturer_.get(); + } + std::unique_ptr capturer_; +}; + +} // namespace + +Conductor::Conductor(PeerConnectionClient* client, MainWindow* main_wnd) + : peer_id_(-1), loopback_(false), client_(client), main_wnd_(main_wnd) { + client_->RegisterObserver(this); + main_wnd->RegisterObserver(this); +} + +Conductor::~Conductor() { + RTC_DCHECK(!peer_connection_); +} + +bool Conductor::connection_active() const { + return peer_connection_ != nullptr; +} + +void Conductor::Close() { + client_->SignOut(); + DeletePeerConnection(); +} + +bool Conductor::InitializePeerConnection() { + RTC_DCHECK(!peer_connection_factory_); + RTC_DCHECK(!peer_connection_); + + peer_connection_factory_ = webrtc::CreatePeerConnectionFactory( + nullptr /* network_thread */, nullptr /* worker_thread */, + nullptr /* signaling_thread */, nullptr /* default_adm */, + webrtc::CreateBuiltinAudioEncoderFactory(), + webrtc::CreateBuiltinAudioDecoderFactory(), + webrtc::CreateBuiltinVideoEncoderFactory(), + webrtc::CreateBuiltinVideoDecoderFactory(), nullptr /* audio_mixer */, + nullptr /* audio_processing */); + + if (!peer_connection_factory_) { + main_wnd_->MessageBox("Error", "Failed to initialize PeerConnectionFactory", + true); + DeletePeerConnection(); + return false; + } + + if (!CreatePeerConnection(/*dtls=*/true)) { + main_wnd_->MessageBox("Error", "CreatePeerConnection failed", true); + DeletePeerConnection(); + } + + AddTracks(); + + return peer_connection_ != nullptr; +} + +bool Conductor::ReinitializePeerConnectionForLoopback() { + loopback_ = true; + std::vector> senders = + peer_connection_->GetSenders(); + peer_connection_ = nullptr; + if (CreatePeerConnection(/*dtls=*/false)) { + for (const auto& sender : senders) { + peer_connection_->AddTrack(sender->track(), sender->stream_ids()); + } + peer_connection_->CreateOffer( + this, webrtc::PeerConnectionInterface::RTCOfferAnswerOptions()); + } + return peer_connection_ != nullptr; +} + +bool Conductor::CreatePeerConnection(bool dtls) { + RTC_DCHECK(peer_connection_factory_); + RTC_DCHECK(!peer_connection_); + + webrtc::PeerConnectionInterface::RTCConfiguration config; + config.sdp_semantics = webrtc::SdpSemantics::kUnifiedPlan; + config.enable_dtls_srtp = dtls; + webrtc::PeerConnectionInterface::IceServer server; + server.uri = GetPeerConnectionString(); + config.servers.push_back(server); + + peer_connection_ = peer_connection_factory_->CreatePeerConnection( + config, nullptr, nullptr, this); + return peer_connection_ != nullptr; +} + +void Conductor::DeletePeerConnection() { + main_wnd_->StopLocalRenderer(); + main_wnd_->StopRemoteRenderer(); + peer_connection_ = nullptr; + peer_connection_factory_ = nullptr; + peer_id_ = -1; + loopback_ = false; +} + +void Conductor::EnsureStreamingUI() { + RTC_DCHECK(peer_connection_); + if (main_wnd_->IsWindow()) { + if (main_wnd_->current_ui() != MainWindow::STREAMING) + main_wnd_->SwitchToStreamingUI(); + } +} + +// +// PeerConnectionObserver implementation. +// + +void Conductor::OnAddTrack( + rtc::scoped_refptr receiver, + const std::vector>& + streams) { + RTC_LOG(INFO) << __FUNCTION__ << " " << receiver->id(); + main_wnd_->QueueUIThreadCallback(NEW_TRACK_ADDED, + receiver->track().release()); +} + +void Conductor::OnRemoveTrack( + rtc::scoped_refptr receiver) { + RTC_LOG(INFO) << __FUNCTION__ << " " << receiver->id(); + main_wnd_->QueueUIThreadCallback(TRACK_REMOVED, receiver->track().release()); +} + +void Conductor::OnIceCandidate(const webrtc::IceCandidateInterface* candidate) { + RTC_LOG(INFO) << __FUNCTION__ << " " << candidate->sdp_mline_index(); + // For loopback test. To save some connecting delay. + if (loopback_) { + if (!peer_connection_->AddIceCandidate(candidate)) { + RTC_LOG(WARNING) << "Failed to apply the received candidate"; + } + return; + } + + Json::StyledWriter writer; + Json::Value jmessage; + + jmessage[kCandidateSdpMidName] = candidate->sdp_mid(); + jmessage[kCandidateSdpMlineIndexName] = candidate->sdp_mline_index(); + std::string sdp; + if (!candidate->ToString(&sdp)) { + RTC_LOG(LS_ERROR) << "Failed to serialize candidate"; + return; + } + jmessage[kCandidateSdpName] = sdp; + SendMessage(writer.write(jmessage)); +} + +// +// PeerConnectionClientObserver implementation. +// + +void Conductor::OnSignedIn() { + RTC_LOG(INFO) << __FUNCTION__; + main_wnd_->SwitchToPeerList(client_->peers()); +} + +void Conductor::OnDisconnected() { + RTC_LOG(INFO) << __FUNCTION__; + + DeletePeerConnection(); + + if (main_wnd_->IsWindow()) + main_wnd_->SwitchToConnectUI(); +} + +void Conductor::OnPeerConnected(int id, const std::string& name) { + RTC_LOG(INFO) << __FUNCTION__; + // Refresh the list if we're showing it. + if (main_wnd_->current_ui() == MainWindow::LIST_PEERS) + main_wnd_->SwitchToPeerList(client_->peers()); +} + +void Conductor::OnPeerDisconnected(int id) { + RTC_LOG(INFO) << __FUNCTION__; + if (id == peer_id_) { + RTC_LOG(INFO) << "Our peer disconnected"; + main_wnd_->QueueUIThreadCallback(PEER_CONNECTION_CLOSED, NULL); + } else { + // Refresh the list if we're showing it. + if (main_wnd_->current_ui() == MainWindow::LIST_PEERS) + main_wnd_->SwitchToPeerList(client_->peers()); + } +} + +void Conductor::OnMessageFromPeer(int peer_id, const std::string& message) { + RTC_DCHECK(peer_id_ == peer_id || peer_id_ == -1); + RTC_DCHECK(!message.empty()); + + if (!peer_connection_.get()) { + RTC_DCHECK(peer_id_ == -1); + peer_id_ = peer_id; + + if (!InitializePeerConnection()) { + RTC_LOG(LS_ERROR) << "Failed to initialize our PeerConnection instance"; + client_->SignOut(); + return; + } + } else if (peer_id != peer_id_) { + RTC_DCHECK(peer_id_ != -1); + RTC_LOG(WARNING) + << "Received a message from unknown peer while already in a " + "conversation with a different peer."; + return; + } + + Json::Reader reader; + Json::Value jmessage; + if (!reader.parse(message, jmessage)) { + RTC_LOG(WARNING) << "Received unknown message. " << message; + return; + } + std::string type_str; + std::string json_object; + + rtc::GetStringFromJsonObject(jmessage, kSessionDescriptionTypeName, + &type_str); + if (!type_str.empty()) { + if (type_str == "offer-loopback") { + // This is a loopback call. + // Recreate the peerconnection with DTLS disabled. + if (!ReinitializePeerConnectionForLoopback()) { + RTC_LOG(LS_ERROR) << "Failed to initialize our PeerConnection instance"; + DeletePeerConnection(); + client_->SignOut(); + } + return; + } + std::cout << type_str << std::endl; + absl::optional type_maybe = + webrtc::SdpTypeFromString(type_str); + if (!type_maybe) { + RTC_LOG(LS_ERROR) << "Unknown SDP type: " << type_str; + return; + } + webrtc::SdpType type = *type_maybe; + std::string sdp; + if (!rtc::GetStringFromJsonObject(jmessage, kSessionDescriptionSdpName, + &sdp)) { + RTC_LOG(WARNING) << "Can't parse received session description message."; + return; + } + webrtc::SdpParseError error; + std::unique_ptr session_description = + webrtc::CreateSessionDescription(type, sdp, &error); + if (!session_description) { + RTC_LOG(WARNING) << "Can't parse received session description message. " + << "SdpParseError was: " << error.description; + return; + } + RTC_LOG(INFO) << " Received session description :" << message; + peer_connection_->SetRemoteDescription( + DummySetSessionDescriptionObserver::Create(), + session_description.release()); + if (type == webrtc::SdpType::kOffer) { + peer_connection_->CreateAnswer( + this, webrtc::PeerConnectionInterface::RTCOfferAnswerOptions()); + } + } else { + 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)) { + RTC_LOG(WARNING) << "Can't parse received message."; + return; + } + webrtc::SdpParseError error; + std::unique_ptr candidate( + webrtc::CreateIceCandidate(sdp_mid, sdp_mlineindex, sdp, &error)); + if (!candidate.get()) { + RTC_LOG(WARNING) << "Can't parse received candidate message. " + << "SdpParseError was: " << error.description; + return; + } + if (!peer_connection_->AddIceCandidate(candidate.get())) { + RTC_LOG(WARNING) << "Failed to apply the received candidate"; + return; + } + RTC_LOG(INFO) << " Received candidate :" << message; + } +} + +void Conductor::OnMessageSent(int err) { + // Process the next pending message if any. + main_wnd_->QueueUIThreadCallback(SEND_MESSAGE_TO_PEER, NULL); +} + +void Conductor::OnServerConnectionFailure() { + main_wnd_->MessageBox("Error", ("Failed to connect to " + server_).c_str(), + true); +} + +// +// MainWndCallback implementation. +// + +void Conductor::StartLogin(const std::string& server, int port) { + if (client_->is_connected()) + return; + server_ = server; + client_->Connect(server, port, GetPeerName()); +} + +void Conductor::DisconnectFromServer() { + if (client_->is_connected()) + client_->SignOut(); +} + +void Conductor::ConnectToPeer(int peer_id) { + RTC_DCHECK(peer_id_ == -1); + RTC_DCHECK(peer_id != -1); + + if (peer_connection_.get()) { + main_wnd_->MessageBox( + "Error", "We only support connecting to one peer at a time", true); + return; + } + + if (InitializePeerConnection()) { + peer_id_ = peer_id; + peer_connection_->CreateOffer( + this, webrtc::PeerConnectionInterface::RTCOfferAnswerOptions()); + } else { + main_wnd_->MessageBox("Error", "Failed to initialize PeerConnection", true); + } +} + +void Conductor::AddTracks() { + if (!peer_connection_->GetSenders().empty()) { + return; // Already added tracks. + } + + rtc::scoped_refptr audio_track( + peer_connection_factory_->CreateAudioTrack( + kAudioLabel, peer_connection_factory_->CreateAudioSource( + cricket::AudioOptions()))); + auto result_or_error = peer_connection_->AddTrack(audio_track, {kStreamId}); + if (!result_or_error.ok()) { + RTC_LOG(LS_ERROR) << "Failed to add audio track to PeerConnection: " + << result_or_error.error().message(); + } + //rtc::scoped_refptr video_device = + // CapturerTrackSource::Create(); + rtc::scoped_refptr video_device = new rtc::RefCountedObject(); + if (video_device) { + video_device->startCapturer(); + rtc::scoped_refptr video_track_( + peer_connection_factory_->CreateVideoTrack(kVideoLabel, video_device)); + main_wnd_->StartLocalRenderer(video_track_); + + result_or_error = peer_connection_->AddTrack(video_track_, {kStreamId}); + if (!result_or_error.ok()) { + RTC_LOG(LS_ERROR) << "Failed to add video track to PeerConnection: " + << result_or_error.error().message(); + } + } else { + RTC_LOG(LS_ERROR) << "OpenVideoCaptureDevice failed"; + } + + main_wnd_->SwitchToStreamingUI(); +} + +void Conductor::DisconnectFromCurrentPeer() { + RTC_LOG(INFO) << __FUNCTION__; + if (peer_connection_.get()) { + client_->SendHangUp(peer_id_); + DeletePeerConnection(); + } + + if (main_wnd_->IsWindow()) + main_wnd_->SwitchToPeerList(client_->peers()); +} + +void Conductor::UIThreadCallback(int msg_id, void* data) { + switch (msg_id) { + case PEER_CONNECTION_CLOSED: + RTC_LOG(INFO) << "PEER_CONNECTION_CLOSED"; + DeletePeerConnection(); + + if (main_wnd_->IsWindow()) { + if (client_->is_connected()) { + main_wnd_->SwitchToPeerList(client_->peers()); + } else { + main_wnd_->SwitchToConnectUI(); + } + } else { + DisconnectFromServer(); + } + break; + + case SEND_MESSAGE_TO_PEER: { + RTC_LOG(INFO) << "SEND_MESSAGE_TO_PEER"; + std::string* msg = reinterpret_cast(data); + if (msg) { + // For convenience, we always run the message through the queue. + // This way we can be sure that messages are sent to the server + // in the same order they were signaled without much hassle. + pending_messages_.push_back(msg); + } + + if (!pending_messages_.empty() && !client_->IsSendingMessage()) { + msg = pending_messages_.front(); + pending_messages_.pop_front(); + + if (!client_->SendToPeer(peer_id_, *msg) && peer_id_ != -1) { + RTC_LOG(LS_ERROR) << "SendToPeer failed"; + DisconnectFromServer(); + } + delete msg; + } + + if (!peer_connection_.get()) + peer_id_ = -1; + + break; + } + + case NEW_TRACK_ADDED: { + auto* track = reinterpret_cast(data); + if (track->kind() == webrtc::MediaStreamTrackInterface::kVideoKind) { + auto* video_track = static_cast(track); + main_wnd_->StartRemoteRenderer(video_track); + } + track->Release(); + break; + } + + case TRACK_REMOVED: { + // Remote peer stopped sending a track. + auto* track = reinterpret_cast(data); + track->Release(); + break; + } + + default: + RTC_NOTREACHED(); + break; + } +} + +void Conductor::OnSuccess(webrtc::SessionDescriptionInterface* desc) { + peer_connection_->SetLocalDescription( + DummySetSessionDescriptionObserver::Create(), desc); + + std::string sdp; + desc->ToString(&sdp); + + // For loopback test. To save some connecting delay. + if (loopback_) { + // Replace message type from "offer" to "answer" + std::unique_ptr session_description = + webrtc::CreateSessionDescription(webrtc::SdpType::kAnswer, sdp); + peer_connection_->SetRemoteDescription( + DummySetSessionDescriptionObserver::Create(), + session_description.release()); + return; + } + + Json::StyledWriter writer; + Json::Value jmessage; + jmessage[kSessionDescriptionTypeName] = + webrtc::SdpTypeToString(desc->GetType()); + jmessage[kSessionDescriptionSdpName] = sdp; + SendMessage(writer.write(jmessage)); +} + +void Conductor::OnFailure(webrtc::RTCError error) { + RTC_LOG(LERROR) << ToString(error.type()) << ": " << error.message(); +} + +void Conductor::SendMessage(const std::string& json_object) { + std::string* msg = new std::string(json_object); + main_wnd_->QueueUIThreadCallback(SEND_MESSAGE_TO_PEER, msg); +} diff --git a/test/src/webrtcdemo/peerconnection/PeerConnectionClientDemo/conductor.h b/test/src/webrtcdemo/peerconnection/PeerConnectionClientDemo/conductor.h new file mode 100644 index 0000000..73c9533 --- /dev/null +++ b/test/src/webrtcdemo/peerconnection/PeerConnectionClientDemo/conductor.h @@ -0,0 +1,146 @@ +/* + * Copyright 2012 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 EXAMPLES_PEERCONNECTION_CLIENT_CONDUCTOR_H_ +#define EXAMPLES_PEERCONNECTION_CLIENT_CONDUCTOR_H_ + +#include +#include +#include +#include +#include + +#include "api/media_stream_interface.h" +#include "api/peer_connection_interface.h" +#include "main_wnd.h" +#include "peer_connection_client.h" + +#pragma comment(lib, "ws2_32.lib") +#pragma comment(lib, "winmm.lib") +#pragma comment(lib, "secur32.lib") +#pragma comment(lib, "iphlpapi.lib") +#pragma comment(lib, "crypt32.lib") +#pragma comment(lib, "dmoguids.lib") +#pragma comment(lib, "wmcodecdspuuid.lib") +#pragma comment(lib, "amstrmid.lib") +#pragma comment(lib, "msdmo.lib") +#pragma comment(lib, "d3d11.lib") +#pragma comment(lib, "dxgi.lib") + +namespace webrtc { +class VideoCaptureModule; +} // namespace webrtc + +namespace cricket { +class VideoRenderer; +} // namespace cricket + +class Conductor : public webrtc::PeerConnectionObserver, + public webrtc::CreateSessionDescriptionObserver, + public PeerConnectionClientObserver, + public MainWndCallback { + public: + enum CallbackID { + MEDIA_CHANNELS_INITIALIZED = 1, + PEER_CONNECTION_CLOSED, + SEND_MESSAGE_TO_PEER, + NEW_TRACK_ADDED, + TRACK_REMOVED, + }; + + Conductor(PeerConnectionClient* client, MainWindow* main_wnd); + + bool connection_active() const; + + void Close() override; + + protected: + ~Conductor(); + bool InitializePeerConnection(); + bool ReinitializePeerConnectionForLoopback(); + bool CreatePeerConnection(bool dtls); + void DeletePeerConnection(); + void EnsureStreamingUI(); + void AddTracks(); + + // + // PeerConnectionObserver implementation. + // + + void OnSignalingChange( + webrtc::PeerConnectionInterface::SignalingState new_state) override {} + void OnAddTrack( + rtc::scoped_refptr receiver, + const std::vector>& + streams) override; + void OnRemoveTrack( + rtc::scoped_refptr receiver) 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 {} + + // + // PeerConnectionClientObserver implementation. + // + + void OnSignedIn() override; + + void OnDisconnected() override; + + void OnPeerConnected(int id, const std::string& name) override; + + void OnPeerDisconnected(int id) override; + + void OnMessageFromPeer(int peer_id, const std::string& message) override; + + void OnMessageSent(int err) override; + + void OnServerConnectionFailure() override; + + // + // MainWndCallback implementation. + // + + void StartLogin(const std::string& server, int port) override; + + void DisconnectFromServer() override; + + void ConnectToPeer(int peer_id) override; + + void DisconnectFromCurrentPeer() override; + + void UIThreadCallback(int msg_id, void* data) override; + + // CreateSessionDescriptionObserver implementation. + void OnSuccess(webrtc::SessionDescriptionInterface* desc) override; + void OnFailure(webrtc::RTCError error) override; + + protected: + // Send a message to the remote peer. + void SendMessage(const std::string& json_object); + + int peer_id_; + bool loopback_; + rtc::scoped_refptr peer_connection_; + rtc::scoped_refptr + peer_connection_factory_; + PeerConnectionClient* client_; + MainWindow* main_wnd_; + std::deque pending_messages_; + std::string server_; +}; + +#endif // EXAMPLES_PEERCONNECTION_CLIENT_CONDUCTOR_H_ diff --git a/test/src/webrtcdemo/peerconnection/PeerConnectionClientDemo/defaults.cc b/test/src/webrtcdemo/peerconnection/PeerConnectionClientDemo/defaults.cc new file mode 100644 index 0000000..8d1690a --- /dev/null +++ b/test/src/webrtcdemo/peerconnection/PeerConnectionClientDemo/defaults.cc @@ -0,0 +1,59 @@ +/* + * Copyright 2012 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 "defaults.h" + +#include + +#ifdef WIN32 +#include +#else +#include +#endif + +#include "rtc_base/arraysize.h" + +const char kAudioLabel[] = "audio_label"; +const char kVideoLabel[] = "video_label"; +const char kStreamId[] = "stream_id"; +const uint16_t kDefaultServerPort = 8888; + +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"); +} + +std::string GetDefaultServerName() { + return GetEnvVarOrDefault("WEBRTC_SERVER", "localhost"); +} + +std::string GetPeerName() { + char computer_name[256]; + std::string ret(GetEnvVarOrDefault("USERNAME", "user")); + ret += '@'; + if (gethostname(computer_name, arraysize(computer_name)) == 0) { + ret += computer_name; + } else { + ret += "host"; + } + return ret; +} diff --git a/test/src/webrtcdemo/peerconnection/PeerConnectionClientDemo/defaults.h b/test/src/webrtcdemo/peerconnection/PeerConnectionClientDemo/defaults.h new file mode 100644 index 0000000..30936fd --- /dev/null +++ b/test/src/webrtcdemo/peerconnection/PeerConnectionClientDemo/defaults.h @@ -0,0 +1,29 @@ +/* + * Copyright 2011 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 EXAMPLES_PEERCONNECTION_CLIENT_DEFAULTS_H_ +#define EXAMPLES_PEERCONNECTION_CLIENT_DEFAULTS_H_ + +#include + +#include + +extern const char kAudioLabel[]; +extern const char kVideoLabel[]; +extern const char kStreamId[]; +extern const uint16_t kDefaultServerPort; + +std::string GetEnvVarOrDefault(const char* env_var_name, + const char* default_value); +std::string GetPeerConnectionString(); +std::string GetDefaultServerName(); +std::string GetPeerName(); + +#endif // EXAMPLES_PEERCONNECTION_CLIENT_DEFAULTS_H_ diff --git a/test/src/webrtcdemo/peerconnection/PeerConnectionClientDemo/flag_defs.h b/test/src/webrtcdemo/peerconnection/PeerConnectionClientDemo/flag_defs.h new file mode 100644 index 0000000..6834de6 --- /dev/null +++ b/test/src/webrtcdemo/peerconnection/PeerConnectionClientDemo/flag_defs.h @@ -0,0 +1,47 @@ +/* + * Copyright 2012 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 EXAMPLES_PEERCONNECTION_CLIENT_FLAG_DEFS_H_ +#define EXAMPLES_PEERCONNECTION_CLIENT_FLAG_DEFS_H_ + +#include "rtc_base/flags.h" + +extern const uint16_t kDefaultServerPort; // From defaults.[h|cc] + +// Define flags for the peerconnect_client testing tool, in a separate +// header file so that they can be shared across the different main.cc's +// for each platform. + +WEBRTC_DEFINE_bool(help, false, "Prints this message"); +WEBRTC_DEFINE_bool(autoconnect, + false, + "Connect to the server without user " + "intervention."); +WEBRTC_DEFINE_string(server, "localhost", "The server to connect to."); +WEBRTC_DEFINE_int(port, + kDefaultServerPort, + "The port on which the server is listening."); +WEBRTC_DEFINE_bool( + autocall, + false, + "Call the first available other client on " + "the server without user intervention. Note: this flag should only be set " + "to true on one of the two clients."); + +WEBRTC_DEFINE_string( + force_fieldtrials, + "", + "Field trials control experimental features. This flag specifies the field " + "trials in effect. E.g. running with " + "--force_fieldtrials=WebRTC-FooFeature/Enabled/ " + "will assign the group Enabled to field trial WebRTC-FooFeature. Multiple " + "trials are separated by \"/\""); + +#endif // EXAMPLES_PEERCONNECTION_CLIENT_FLAG_DEFS_H_ diff --git a/test/src/webrtcdemo/peerconnection/PeerConnectionClientDemo/main.cc b/test/src/webrtcdemo/peerconnection/PeerConnectionClientDemo/main.cc new file mode 100644 index 0000000..27c1c49 --- /dev/null +++ b/test/src/webrtcdemo/peerconnection/PeerConnectionClientDemo/main.cc @@ -0,0 +1,95 @@ +/* + * Copyright 2012 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 "conductor.h" +#include "flag_defs.h" +#include "main_wnd.h" +#include "peer_connection_client.h" +#include "rtc_base/checks.h" +#include "rtc_base/ssl_adapter.h" +#include "rtc_base/win32_socket_init.h" +#include "rtc_base/win32_socket_server.h" +#include "system_wrappers/include/field_trial.h" +#include "test/field_trial.h" + +#include + +int PASCAL wWinMain(HINSTANCE instance, + HINSTANCE prev_instance, + wchar_t* cmd_line, + int cmd_show) { + + AllocConsole(); + freopen("CONOUT$", "w", stdout); + + printf("hello world"); + std::shared_ptr p(new int[100]); + + rtc::WinsockInitializer winsock_init; + rtc::Win32SocketServer w32_ss; + rtc::Win32Thread w32_thread(&w32_ss); + rtc::ThreadManager::Instance()->SetCurrentThread(&w32_thread); + + rtc::WindowsCommandLineArguments win_args; + int argc = win_args.argc(); + char** argv = win_args.argv(); + + rtc::FlagList::SetFlagsFromCommandLine(&argc, argv, true); + if (FLAG_help) { + rtc::FlagList::Print(NULL, false); + return 0; + } + + webrtc::test::ValidateFieldTrialsStringOrDie(FLAG_force_fieldtrials); + // InitFieldTrialsFromString stores the char*, so the char array must outlive + // the application. + webrtc::field_trial::InitFieldTrialsFromString(FLAG_force_fieldtrials); + + // Abort if the user specifies a port that is outside the allowed + // range [1, 65535]. + if ((FLAG_port < 1) || (FLAG_port > 65535)) { + printf("Error: %i is not a valid port.\n", FLAG_port); + return -1; + } + + MainWnd wnd(FLAG_server, FLAG_port, FLAG_autoconnect, FLAG_autocall); + if (!wnd.Create()) { + RTC_NOTREACHED(); + return -1; + } + + rtc::InitializeSSL(); + PeerConnectionClient client; + rtc::scoped_refptr conductor( + new rtc::RefCountedObject(&client, &wnd)); + + // Main loop. + MSG msg; + BOOL gm; + while ((gm = ::GetMessage(&msg, NULL, 0, 0)) != 0 && gm != -1) { + if (!wnd.PreTranslateMessage(&msg)) { + ::TranslateMessage(&msg); + ::DispatchMessage(&msg); + } + } + + if (conductor->connection_active() || client.is_connected()) { + while ((conductor->connection_active() || client.is_connected()) && + (gm = ::GetMessage(&msg, NULL, 0, 0)) != 0 && gm != -1) { + if (!wnd.PreTranslateMessage(&msg)) { + ::TranslateMessage(&msg); + ::DispatchMessage(&msg); + } + } + } + + rtc::CleanupSSL(); + return 0; +} diff --git a/test/src/webrtcdemo/peerconnection/PeerConnectionClientDemo/main_wnd.cc b/test/src/webrtcdemo/peerconnection/PeerConnectionClientDemo/main_wnd.cc new file mode 100644 index 0000000..fe5cbf3 --- /dev/null +++ b/test/src/webrtcdemo/peerconnection/PeerConnectionClientDemo/main_wnd.cc @@ -0,0 +1,633 @@ +/* + * Copyright 2012 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 "main_wnd.h" + +#include + +#include "api/video/i420_buffer.h" +#include "defaults.h" +#include "rtc_base/arraysize.h" +#include "rtc_base/checks.h" +#include "rtc_base/logging.h" +#include "third_party/libyuv/include/libyuv/convert_argb.h" + +ATOM MainWnd::wnd_class_ = 0; +const wchar_t MainWnd::kClassName[] = L"WebRTC_MainWnd"; + +namespace { + +const char kConnecting[] = "Connecting... "; +const char kNoVideoStreams[] = "(no video streams either way)"; +const char kNoIncomingStream[] = "(no incoming video)"; + +void CalculateWindowSizeForText(HWND wnd, + const wchar_t* text, + size_t* width, + size_t* height) { + HDC dc = ::GetDC(wnd); + RECT text_rc = {0}; + ::DrawTextW(dc, text, -1, &text_rc, DT_CALCRECT | DT_SINGLELINE); + ::ReleaseDC(wnd, dc); + RECT client, window; + ::GetClientRect(wnd, &client); + ::GetWindowRect(wnd, &window); + + *width = text_rc.right - text_rc.left; + *width += (window.right - window.left) - (client.right - client.left); + *height = text_rc.bottom - text_rc.top; + *height += (window.bottom - window.top) - (client.bottom - client.top); +} + +HFONT GetDefaultFont() { + static HFONT font = reinterpret_cast(GetStockObject(DEFAULT_GUI_FONT)); + return font; +} + +std::string GetWindowText(HWND wnd) { + char text[MAX_PATH] = {0}; + ::GetWindowTextA(wnd, &text[0], ARRAYSIZE(text)); + return text; +} + +void AddListBoxItem(HWND listbox, const std::string& str, LPARAM item_data) { + LRESULT index = ::SendMessageA(listbox, LB_ADDSTRING, 0, + reinterpret_cast(str.c_str())); + ::SendMessageA(listbox, LB_SETITEMDATA, index, item_data); +} + +} // namespace + +MainWnd::MainWnd(const char* server, + int port, + bool auto_connect, + bool auto_call) + : ui_(CONNECT_TO_SERVER), + wnd_(NULL), + edit1_(NULL), + edit2_(NULL), + label1_(NULL), + label2_(NULL), + button_(NULL), + listbox_(NULL), + destroyed_(false), + nested_msg_(NULL), + callback_(NULL), + server_(server), + auto_connect_(auto_connect), + auto_call_(auto_call) { + char buffer[10]; + snprintf(buffer, sizeof(buffer), "%i", port); + port_ = buffer; +} + +MainWnd::~MainWnd() { + RTC_DCHECK(!IsWindow()); +} + +bool MainWnd::Create() { + RTC_DCHECK(wnd_ == NULL); + if (!RegisterWindowClass()) + return false; + + ui_thread_id_ = ::GetCurrentThreadId(); + wnd_ = + ::CreateWindowExW(WS_EX_OVERLAPPEDWINDOW, kClassName, L"WebRTC", + WS_OVERLAPPEDWINDOW | WS_VISIBLE | WS_CLIPCHILDREN, + CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, + CW_USEDEFAULT, NULL, NULL, GetModuleHandle(NULL), this); + + ::SendMessage(wnd_, WM_SETFONT, reinterpret_cast(GetDefaultFont()), + TRUE); + + CreateChildWindows(); + SwitchToConnectUI(); + + return wnd_ != NULL; +} + +bool MainWnd::Destroy() { + BOOL ret = FALSE; + if (IsWindow()) { + ret = ::DestroyWindow(wnd_); + } + + return ret != FALSE; +} + +void MainWnd::RegisterObserver(MainWndCallback* callback) { + callback_ = callback; +} + +bool MainWnd::IsWindow() { + return wnd_ && ::IsWindow(wnd_) != FALSE; +} + +bool MainWnd::PreTranslateMessage(MSG* msg) { + bool ret = false; + if (msg->message == WM_CHAR) { + if (msg->wParam == VK_TAB) { + HandleTabbing(); + ret = true; + } else if (msg->wParam == VK_RETURN) { + OnDefaultAction(); + ret = true; + } else if (msg->wParam == VK_ESCAPE) { + if (callback_) { + if (ui_ == STREAMING) { + callback_->DisconnectFromCurrentPeer(); + } else { + callback_->DisconnectFromServer(); + } + } + } + } else if (msg->hwnd == NULL && msg->message == UI_THREAD_CALLBACK) { + callback_->UIThreadCallback(static_cast(msg->wParam), + reinterpret_cast(msg->lParam)); + ret = true; + } + return ret; +} + +void MainWnd::SwitchToConnectUI() { + RTC_DCHECK(IsWindow()); + LayoutPeerListUI(false); + ui_ = CONNECT_TO_SERVER; + LayoutConnectUI(true); + ::SetFocus(edit1_); + + if (auto_connect_) + ::PostMessage(button_, BM_CLICK, 0, 0); +} + +void MainWnd::SwitchToPeerList(const Peers& peers) { + LayoutConnectUI(false); + + ::SendMessage(listbox_, LB_RESETCONTENT, 0, 0); + + AddListBoxItem(listbox_, "List of currently connected peers:", -1); + Peers::const_iterator i = peers.begin(); + for (; i != peers.end(); ++i) + AddListBoxItem(listbox_, i->second.c_str(), i->first); + + ui_ = LIST_PEERS; + LayoutPeerListUI(true); + ::SetFocus(listbox_); + + if (auto_call_ && peers.begin() != peers.end()) { + // Get the number of items in the list + LRESULT count = ::SendMessage(listbox_, LB_GETCOUNT, 0, 0); + if (count != LB_ERR) { + // Select the last item in the list + LRESULT selection = ::SendMessage(listbox_, LB_SETCURSEL, count - 1, 0); + if (selection != LB_ERR) + ::PostMessage(wnd_, WM_COMMAND, + MAKEWPARAM(GetDlgCtrlID(listbox_), LBN_DBLCLK), + reinterpret_cast(listbox_)); + } + } +} + +void MainWnd::SwitchToStreamingUI() { + LayoutConnectUI(false); + LayoutPeerListUI(false); + ui_ = STREAMING; +} + +void MainWnd::MessageBox(const char* caption, const char* text, bool is_error) { + DWORD flags = MB_OK; + if (is_error) + flags |= MB_ICONERROR; + + ::MessageBoxA(handle(), text, caption, flags); +} + +void MainWnd::StartLocalRenderer(webrtc::VideoTrackInterface* local_video) { + local_renderer_.reset(new VideoRenderer(handle(), 1, 1, local_video)); +} + +void MainWnd::StopLocalRenderer() { + local_renderer_.reset(); +} + +void MainWnd::StartRemoteRenderer(webrtc::VideoTrackInterface* remote_video) { + remote_renderer_.reset(new VideoRenderer(handle(), 1, 1, remote_video)); +} + +void MainWnd::StopRemoteRenderer() { + remote_renderer_.reset(); +} + +void MainWnd::QueueUIThreadCallback(int msg_id, void* data) { + ::PostThreadMessage(ui_thread_id_, UI_THREAD_CALLBACK, + static_cast(msg_id), + reinterpret_cast(data)); +} + +void MainWnd::OnPaint() { + PAINTSTRUCT ps; + ::BeginPaint(handle(), &ps); + + RECT rc; + ::GetClientRect(handle(), &rc); + + VideoRenderer* local_renderer = local_renderer_.get(); + VideoRenderer* remote_renderer = remote_renderer_.get(); + if (ui_ == STREAMING && remote_renderer && local_renderer) { + AutoLock local_lock(local_renderer); + AutoLock remote_lock(remote_renderer); + + const BITMAPINFO& bmi = remote_renderer->bmi(); + int height = abs(bmi.bmiHeader.biHeight); + int width = bmi.bmiHeader.biWidth; + + const uint8_t* image = remote_renderer->image(); + if (image != NULL) { + HDC dc_mem = ::CreateCompatibleDC(ps.hdc); + ::SetStretchBltMode(dc_mem, HALFTONE); + + // Set the map mode so that the ratio will be maintained for us. + HDC all_dc[] = {ps.hdc, dc_mem}; + for (size_t i = 0; i < arraysize(all_dc); ++i) { + SetMapMode(all_dc[i], MM_ISOTROPIC); + SetWindowExtEx(all_dc[i], width, height, NULL); + SetViewportExtEx(all_dc[i], rc.right, rc.bottom, NULL); + } + + HBITMAP bmp_mem = ::CreateCompatibleBitmap(ps.hdc, rc.right, rc.bottom); + HGDIOBJ bmp_old = ::SelectObject(dc_mem, bmp_mem); + + POINT logical_area = {rc.right, rc.bottom}; + DPtoLP(ps.hdc, &logical_area, 1); + + HBRUSH brush = ::CreateSolidBrush(RGB(0, 0, 0)); + RECT logical_rect = {0, 0, logical_area.x, logical_area.y}; + ::FillRect(dc_mem, &logical_rect, brush); + ::DeleteObject(brush); + + int x = (logical_area.x / 2) - (width / 2); + int y = (logical_area.y / 2) - (height / 2); + + StretchDIBits(dc_mem, x, y, width, height, 0, 0, width, height, image, + &bmi, DIB_RGB_COLORS, SRCCOPY); + + if ((rc.right - rc.left) > 200 && (rc.bottom - rc.top) > 200) { + const BITMAPINFO& bmi = local_renderer->bmi(); + image = local_renderer->image(); + int thumb_width = bmi.bmiHeader.biWidth / 4; + int thumb_height = abs(bmi.bmiHeader.biHeight) / 4; + StretchDIBits(dc_mem, logical_area.x - thumb_width - 10, + logical_area.y - thumb_height - 10, thumb_width, + thumb_height, 0, 0, bmi.bmiHeader.biWidth, + -bmi.bmiHeader.biHeight, image, &bmi, DIB_RGB_COLORS, + SRCCOPY); + } + + BitBlt(ps.hdc, 0, 0, logical_area.x, logical_area.y, dc_mem, 0, 0, + SRCCOPY); + + // Cleanup. + ::SelectObject(dc_mem, bmp_old); + ::DeleteObject(bmp_mem); + ::DeleteDC(dc_mem); + } else { + // We're still waiting for the video stream to be initialized. + HBRUSH brush = ::CreateSolidBrush(RGB(0, 0, 0)); + ::FillRect(ps.hdc, &rc, brush); + ::DeleteObject(brush); + + HGDIOBJ old_font = ::SelectObject(ps.hdc, GetDefaultFont()); + ::SetTextColor(ps.hdc, RGB(0xff, 0xff, 0xff)); + ::SetBkMode(ps.hdc, TRANSPARENT); + + std::string text(kConnecting); + if (!local_renderer->image()) { + text += kNoVideoStreams; + } else { + text += kNoIncomingStream; + } + ::DrawTextA(ps.hdc, text.c_str(), -1, &rc, + DT_SINGLELINE | DT_CENTER | DT_VCENTER); + ::SelectObject(ps.hdc, old_font); + } + } else { + HBRUSH brush = ::CreateSolidBrush(::GetSysColor(COLOR_WINDOW)); + ::FillRect(ps.hdc, &rc, brush); + ::DeleteObject(brush); + } + + ::EndPaint(handle(), &ps); +} + +void MainWnd::OnDestroyed() { + PostQuitMessage(0); +} + +void MainWnd::OnDefaultAction() { + if (!callback_) + return; + if (ui_ == CONNECT_TO_SERVER) { + std::string server(GetWindowText(edit1_)); + std::string port_str(GetWindowText(edit2_)); + int port = port_str.length() ? atoi(port_str.c_str()) : 0; + callback_->StartLogin(server, port); + } else if (ui_ == LIST_PEERS) { + LRESULT sel = ::SendMessage(listbox_, LB_GETCURSEL, 0, 0); + if (sel != LB_ERR) { + LRESULT peer_id = ::SendMessage(listbox_, LB_GETITEMDATA, sel, 0); + if (peer_id != -1 && callback_) { + callback_->ConnectToPeer(peer_id); + } + } + } else { + ::MessageBoxA(wnd_, "OK!", "Yeah", MB_OK); + } +} + +bool MainWnd::OnMessage(UINT msg, WPARAM wp, LPARAM lp, LRESULT* result) { + switch (msg) { + case WM_ERASEBKGND: + *result = TRUE; + return true; + + case WM_PAINT: + OnPaint(); + return true; + + case WM_SETFOCUS: + if (ui_ == CONNECT_TO_SERVER) { + SetFocus(edit1_); + } else if (ui_ == LIST_PEERS) { + SetFocus(listbox_); + } + return true; + + case WM_SIZE: + if (ui_ == CONNECT_TO_SERVER) { + LayoutConnectUI(true); + } else if (ui_ == LIST_PEERS) { + LayoutPeerListUI(true); + } + break; + + case WM_CTLCOLORSTATIC: + *result = reinterpret_cast(GetSysColorBrush(COLOR_WINDOW)); + return true; + + case WM_COMMAND: + if (button_ == reinterpret_cast(lp)) { + if (BN_CLICKED == HIWORD(wp)) + OnDefaultAction(); + } else if (listbox_ == reinterpret_cast(lp)) { + if (LBN_DBLCLK == HIWORD(wp)) { + OnDefaultAction(); + } + } + return true; + + case WM_CLOSE: + if (callback_) + callback_->Close(); + break; + } + return false; +} + +// static +LRESULT CALLBACK MainWnd::WndProc(HWND hwnd, UINT msg, WPARAM wp, LPARAM lp) { + MainWnd* me = + reinterpret_cast(::GetWindowLongPtr(hwnd, GWLP_USERDATA)); + if (!me && WM_CREATE == msg) { + CREATESTRUCT* cs = reinterpret_cast(lp); + me = reinterpret_cast(cs->lpCreateParams); + me->wnd_ = hwnd; + ::SetWindowLongPtr(hwnd, GWLP_USERDATA, reinterpret_cast(me)); + } + + LRESULT result = 0; + if (me) { + void* prev_nested_msg = me->nested_msg_; + me->nested_msg_ = &msg; + + bool handled = me->OnMessage(msg, wp, lp, &result); + if (WM_NCDESTROY == msg) { + me->destroyed_ = true; + } else if (!handled) { + result = ::DefWindowProc(hwnd, msg, wp, lp); + } + + if (me->destroyed_ && prev_nested_msg == NULL) { + me->OnDestroyed(); + me->wnd_ = NULL; + me->destroyed_ = false; + } + + me->nested_msg_ = prev_nested_msg; + } else { + result = ::DefWindowProc(hwnd, msg, wp, lp); + } + + return result; +} + +// static +bool MainWnd::RegisterWindowClass() { + if (wnd_class_) + return true; + + WNDCLASSEXW wcex = {sizeof(WNDCLASSEX)}; + wcex.style = CS_DBLCLKS; + wcex.hInstance = GetModuleHandle(NULL); + wcex.hbrBackground = reinterpret_cast(COLOR_WINDOW + 1); + wcex.hCursor = ::LoadCursor(NULL, IDC_ARROW); + wcex.lpfnWndProc = &WndProc; + wcex.lpszClassName = kClassName; + wnd_class_ = ::RegisterClassExW(&wcex); + RTC_DCHECK(wnd_class_ != 0); + return wnd_class_ != 0; +} + +void MainWnd::CreateChildWindow(HWND* wnd, + MainWnd::ChildWindowID id, + const wchar_t* class_name, + DWORD control_style, + DWORD ex_style) { + if (::IsWindow(*wnd)) + return; + + // Child windows are invisible at first, and shown after being resized. + DWORD style = WS_CHILD | control_style; + *wnd = ::CreateWindowExW(ex_style, class_name, L"", style, 100, 100, 100, 100, + wnd_, reinterpret_cast(id), + GetModuleHandle(NULL), NULL); + RTC_DCHECK(::IsWindow(*wnd) != FALSE); + ::SendMessage(*wnd, WM_SETFONT, reinterpret_cast(GetDefaultFont()), + TRUE); +} + +void MainWnd::CreateChildWindows() { + // Create the child windows in tab order. + CreateChildWindow(&label1_, LABEL1_ID, L"Static", ES_CENTER | ES_READONLY, 0); + CreateChildWindow(&edit1_, EDIT_ID, L"Edit", + ES_LEFT | ES_NOHIDESEL | WS_TABSTOP, WS_EX_CLIENTEDGE); + CreateChildWindow(&label2_, LABEL2_ID, L"Static", ES_CENTER | ES_READONLY, 0); + CreateChildWindow(&edit2_, EDIT_ID, L"Edit", + ES_LEFT | ES_NOHIDESEL | WS_TABSTOP, WS_EX_CLIENTEDGE); + CreateChildWindow(&button_, BUTTON_ID, L"Button", BS_CENTER | WS_TABSTOP, 0); + + CreateChildWindow(&listbox_, LISTBOX_ID, L"ListBox", + LBS_HASSTRINGS | LBS_NOTIFY, WS_EX_CLIENTEDGE); + + ::SetWindowTextA(edit1_, server_.c_str()); + ::SetWindowTextA(edit2_, port_.c_str()); +} + +void MainWnd::LayoutConnectUI(bool show) { + struct Windows { + HWND wnd; + const wchar_t* text; + size_t width; + size_t height; + } windows[] = { + {label1_, L"Server"}, {edit1_, L"XXXyyyYYYgggXXXyyyYYYggg"}, + {label2_, L":"}, {edit2_, L"XyXyX"}, + {button_, L"Connect"}, + }; + + if (show) { + const size_t kSeparator = 5; + size_t total_width = (ARRAYSIZE(windows) - 1) * kSeparator; + + for (size_t i = 0; i < ARRAYSIZE(windows); ++i) { + CalculateWindowSizeForText(windows[i].wnd, windows[i].text, + &windows[i].width, &windows[i].height); + total_width += windows[i].width; + } + + RECT rc; + ::GetClientRect(wnd_, &rc); + size_t x = (rc.right / 2) - (total_width / 2); + size_t y = rc.bottom / 2; + for (size_t i = 0; i < ARRAYSIZE(windows); ++i) { + size_t top = y - (windows[i].height / 2); + ::MoveWindow(windows[i].wnd, static_cast(x), static_cast(top), + static_cast(windows[i].width), + static_cast(windows[i].height), TRUE); + x += kSeparator + windows[i].width; + if (windows[i].text[0] != 'X') + ::SetWindowTextW(windows[i].wnd, windows[i].text); + ::ShowWindow(windows[i].wnd, SW_SHOWNA); + } + } else { + for (size_t i = 0; i < ARRAYSIZE(windows); ++i) { + ::ShowWindow(windows[i].wnd, SW_HIDE); + } + } +} + +void MainWnd::LayoutPeerListUI(bool show) { + if (show) { + RECT rc; + ::GetClientRect(wnd_, &rc); + ::MoveWindow(listbox_, 0, 0, rc.right, rc.bottom, TRUE); + ::ShowWindow(listbox_, SW_SHOWNA); + } else { + ::ShowWindow(listbox_, SW_HIDE); + InvalidateRect(wnd_, NULL, TRUE); + } +} + +void MainWnd::HandleTabbing() { + bool shift = ((::GetAsyncKeyState(VK_SHIFT) & 0x8000) != 0); + UINT next_cmd = shift ? GW_HWNDPREV : GW_HWNDNEXT; + UINT loop_around_cmd = shift ? GW_HWNDLAST : GW_HWNDFIRST; + HWND focus = GetFocus(), next; + do { + next = ::GetWindow(focus, next_cmd); + if (IsWindowVisible(next) && + (GetWindowLong(next, GWL_STYLE) & WS_TABSTOP)) { + break; + } + + if (!next) { + next = ::GetWindow(focus, loop_around_cmd); + if (IsWindowVisible(next) && + (GetWindowLong(next, GWL_STYLE) & WS_TABSTOP)) { + break; + } + } + focus = next; + } while (true); + ::SetFocus(next); +} + +// +// MainWnd::VideoRenderer +// + +MainWnd::VideoRenderer::VideoRenderer( + HWND wnd, + int width, + int height, + webrtc::VideoTrackInterface* track_to_render) + : wnd_(wnd), rendered_track_(track_to_render) { + ::InitializeCriticalSection(&buffer_lock_); + ZeroMemory(&bmi_, sizeof(bmi_)); + bmi_.bmiHeader.biSize = sizeof(BITMAPINFOHEADER); + bmi_.bmiHeader.biPlanes = 1; + bmi_.bmiHeader.biBitCount = 32; + bmi_.bmiHeader.biCompression = BI_RGB; + bmi_.bmiHeader.biWidth = width; + bmi_.bmiHeader.biHeight = -height; + bmi_.bmiHeader.biSizeImage = + width * height * (bmi_.bmiHeader.biBitCount >> 3); + rendered_track_->AddOrUpdateSink(this, rtc::VideoSinkWants()); +} + +MainWnd::VideoRenderer::~VideoRenderer() { + rendered_track_->RemoveSink(this); + ::DeleteCriticalSection(&buffer_lock_); +} + +void MainWnd::VideoRenderer::SetSize(int width, int height) { + AutoLock lock(this); + + if (width == bmi_.bmiHeader.biWidth && height == bmi_.bmiHeader.biHeight) { + return; + } + + bmi_.bmiHeader.biWidth = width; + bmi_.bmiHeader.biHeight = -height; + bmi_.bmiHeader.biSizeImage = + width * height * (bmi_.bmiHeader.biBitCount >> 3); + image_.reset(new uint8_t[bmi_.bmiHeader.biSizeImage]); +} + +void MainWnd::VideoRenderer::OnFrame(const webrtc::VideoFrame& video_frame) { + { + AutoLock lock(this); + + rtc::scoped_refptr buffer( + video_frame.video_frame_buffer()->ToI420()); + if (video_frame.rotation() != webrtc::kVideoRotation_0) { + buffer = webrtc::I420Buffer::Rotate(*buffer, video_frame.rotation()); + } + + SetSize(buffer->width(), buffer->height()); + + RTC_DCHECK(image_.get() != NULL); + libyuv::I420ToARGB(buffer->DataY(), buffer->StrideY(), buffer->DataU(), + buffer->StrideU(), buffer->DataV(), buffer->StrideV(), + image_.get(), + bmi_.bmiHeader.biWidth * bmi_.bmiHeader.biBitCount / 8, + buffer->width(), buffer->height()); + } + InvalidateRect(wnd_, NULL, TRUE); +} diff --git a/test/src/webrtcdemo/peerconnection/PeerConnectionClientDemo/main_wnd.h b/test/src/webrtcdemo/peerconnection/PeerConnectionClientDemo/main_wnd.h new file mode 100644 index 0000000..6182416 --- /dev/null +++ b/test/src/webrtcdemo/peerconnection/PeerConnectionClientDemo/main_wnd.h @@ -0,0 +1,206 @@ +/* + * Copyright 2012 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 EXAMPLES_PEERCONNECTION_CLIENT_MAIN_WND_H_ +#define EXAMPLES_PEERCONNECTION_CLIENT_MAIN_WND_H_ + +#include +#include +#include + +#include "api/media_stream_interface.h" +#include "api/video/video_frame.h" +#include "peer_connection_client.h" +#include "media/base/media_channel.h" +#include "media/base/video_common.h" +#if defined(WEBRTC_WIN) +#include "rtc_base/win32.h" +#endif // WEBRTC_WIN + +class MainWndCallback { + public: + virtual void StartLogin(const std::string& server, int port) = 0; + virtual void DisconnectFromServer() = 0; + virtual void ConnectToPeer(int peer_id) = 0; + virtual void DisconnectFromCurrentPeer() = 0; + virtual void UIThreadCallback(int msg_id, void* data) = 0; + virtual void Close() = 0; + + protected: + virtual ~MainWndCallback() {} +}; + +// Pure virtual interface for the main window. +class MainWindow { + public: + virtual ~MainWindow() {} + + enum UI { + CONNECT_TO_SERVER, + LIST_PEERS, + STREAMING, + }; + + virtual void RegisterObserver(MainWndCallback* callback) = 0; + + virtual bool IsWindow() = 0; + virtual void MessageBox(const char* caption, + const char* text, + bool is_error) = 0; + + virtual UI current_ui() = 0; + + virtual void SwitchToConnectUI() = 0; + virtual void SwitchToPeerList(const Peers& peers) = 0; + virtual void SwitchToStreamingUI() = 0; + + virtual void StartLocalRenderer(webrtc::VideoTrackInterface* local_video) = 0; + virtual void StopLocalRenderer() = 0; + virtual void StartRemoteRenderer( + webrtc::VideoTrackInterface* remote_video) = 0; + virtual void StopRemoteRenderer() = 0; + + virtual void QueueUIThreadCallback(int msg_id, void* data) = 0; +}; + +#ifdef WIN32 + +class MainWnd : public MainWindow { + public: + static const wchar_t kClassName[]; + + enum WindowMessages { + UI_THREAD_CALLBACK = WM_APP + 1, + }; + + MainWnd(const char* server, int port, bool auto_connect, bool auto_call); + ~MainWnd(); + + bool Create(); + bool Destroy(); + bool PreTranslateMessage(MSG* msg); + + virtual void RegisterObserver(MainWndCallback* callback); + virtual bool IsWindow(); + virtual void SwitchToConnectUI(); + virtual void SwitchToPeerList(const Peers& peers); + virtual void SwitchToStreamingUI(); + virtual void MessageBox(const char* caption, const char* text, bool is_error); + virtual UI current_ui() { return ui_; } + + virtual void StartLocalRenderer(webrtc::VideoTrackInterface* local_video); + virtual void StopLocalRenderer(); + virtual void StartRemoteRenderer(webrtc::VideoTrackInterface* remote_video); + virtual void StopRemoteRenderer(); + + virtual void QueueUIThreadCallback(int msg_id, void* data); + + HWND handle() const { return wnd_; } + + class VideoRenderer : public rtc::VideoSinkInterface { + public: + VideoRenderer(HWND wnd, + int width, + int height, + webrtc::VideoTrackInterface* track_to_render); + virtual ~VideoRenderer(); + + void Lock() { ::EnterCriticalSection(&buffer_lock_); } + + void Unlock() { ::LeaveCriticalSection(&buffer_lock_); } + + // VideoSinkInterface implementation + void OnFrame(const webrtc::VideoFrame& frame) override; + + const BITMAPINFO& bmi() const { return bmi_; } + const uint8_t* image() const { return image_.get(); } + + protected: + void SetSize(int width, int height); + + enum { + SET_SIZE, + RENDER_FRAME, + }; + + HWND wnd_; + BITMAPINFO bmi_; + std::unique_ptr image_; + CRITICAL_SECTION buffer_lock_; + rtc::scoped_refptr rendered_track_; + }; + + // A little helper class to make sure we always to proper locking and + // unlocking when working with VideoRenderer buffers. + template + class AutoLock { + public: + explicit AutoLock(T* obj) : obj_(obj) { obj_->Lock(); } + ~AutoLock() { obj_->Unlock(); } + + protected: + T* obj_; + }; + + protected: + enum ChildWindowID { + EDIT_ID = 1, + BUTTON_ID, + LABEL1_ID, + LABEL2_ID, + LISTBOX_ID, + }; + + void OnPaint(); + void OnDestroyed(); + + void OnDefaultAction(); + + bool OnMessage(UINT msg, WPARAM wp, LPARAM lp, LRESULT* result); + + static LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wp, LPARAM lp); + static bool RegisterWindowClass(); + + void CreateChildWindow(HWND* wnd, + ChildWindowID id, + const wchar_t* class_name, + DWORD control_style, + DWORD ex_style); + void CreateChildWindows(); + + void LayoutConnectUI(bool show); + void LayoutPeerListUI(bool show); + + void HandleTabbing(); + + private: + std::unique_ptr local_renderer_; + std::unique_ptr remote_renderer_; + UI ui_; + HWND wnd_; + DWORD ui_thread_id_; + HWND edit1_; + HWND edit2_; + HWND label1_; + HWND label2_; + HWND button_; + HWND listbox_; + bool destroyed_; + void* nested_msg_; + MainWndCallback* callback_; + static ATOM wnd_class_; + std::string server_; + std::string port_; + bool auto_connect_; + bool auto_call_; +}; +#endif // WIN32 + +#endif // EXAMPLES_PEERCONNECTION_CLIENT_MAIN_WND_H_ diff --git a/test/src/webrtcdemo/peerconnection/PeerConnectionClientDemo/peer_connection_client.cc b/test/src/webrtcdemo/peerconnection/PeerConnectionClientDemo/peer_connection_client.cc new file mode 100644 index 0000000..ebaddb6 --- /dev/null +++ b/test/src/webrtcdemo/peerconnection/PeerConnectionClientDemo/peer_connection_client.cc @@ -0,0 +1,511 @@ +/* + * Copyright 2012 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. + */ + + + /* + ĿͻˣhttpЭ + */ + +#include "peer_connection_client.h" + +#include "defaults.h" +#include "rtc_base/checks.h" +#include "rtc_base/logging.h" +#include "rtc_base/net_helpers.h" + +#ifdef WIN32 +#include "rtc_base/win32_socket_server.h" +#endif + +namespace { + +// This is our magical hangup signal. +const char kByeMessage[] = "BYE"; +// Delay between server connection retries, in milliseconds +const int kReconnectDelay = 2000; + +rtc::AsyncSocket* CreateClientSocket(int family) { +#ifdef WIN32 + rtc::Win32Socket* sock = new rtc::Win32Socket(); + sock->CreateT(family, SOCK_STREAM); + return sock; +#elif defined(WEBRTC_POSIX) + rtc::Thread* thread = rtc::Thread::Current(); + RTC_DCHECK(thread != NULL); + return thread->socketserver()->CreateAsyncSocket(family, SOCK_STREAM); +#else +#error Platform not supported. +#endif +} + +} // namespace + +PeerConnectionClient::PeerConnectionClient() + : callback_(NULL), resolver_(NULL), state_(NOT_CONNECTED), my_id_(-1) {} + +PeerConnectionClient::~PeerConnectionClient() {} + +// ʹsigslot tcpӵĸ¼ +void PeerConnectionClient::InitSocketSignals() { + RTC_DCHECK(control_socket_.get() != NULL); + RTC_DCHECK(hanging_get_.get() != NULL); + control_socket_->SignalCloseEvent.connect(this, + &PeerConnectionClient::OnClose); + hanging_get_->SignalCloseEvent.connect(this, &PeerConnectionClient::OnClose); + control_socket_->SignalConnectEvent.connect(this, + &PeerConnectionClient::OnConnect); + hanging_get_->SignalConnectEvent.connect( + this, &PeerConnectionClient::OnHangingGetConnect); + control_socket_->SignalReadEvent.connect(this, &PeerConnectionClient::OnRead); + hanging_get_->SignalReadEvent.connect(this, &PeerConnectionClient::OnHangingGetRead); +} + +int PeerConnectionClient::id() const { + return my_id_; +} + +bool PeerConnectionClient::is_connected() const { + return my_id_ != -1; +} + +const Peers& PeerConnectionClient::peers() const { + return peers_; +} + +void PeerConnectionClient::RegisterObserver( + PeerConnectionClientObserver* callback) { + RTC_DCHECK(!callback_); + callback_ = callback; +} +// +void PeerConnectionClient::Connect(const std::string& server, + int port, + const std::string& client_name) { + RTC_DCHECK(!server.empty()); + RTC_DCHECK(!client_name.empty()); + + if (state_ != NOT_CONNECTED) { + RTC_LOG(WARNING) + << "The client must not be connected before you can call Connect()"; + callback_->OnServerConnectionFailure(); + return; + } + + if (server.empty() || client_name.empty()) { + callback_->OnServerConnectionFailure(); + return; + } + + if (port <= 0) + port = kDefaultServerPort; + + server_address_.SetIP(server); + server_address_.SetPort(port); + client_name_ = client_name; + + if (server_address_.IsUnresolvedIP()) { + state_ = RESOLVING; + resolver_ = new rtc::AsyncResolver(); + resolver_->SignalDone.connect(this, &PeerConnectionClient::OnResolveResult); + resolver_->Start(server_address_); + } else { + DoConnect(); + } +} +// dns +void PeerConnectionClient::OnResolveResult( + rtc::AsyncResolverInterface* resolver) { + if (resolver_->GetError() != 0) { + callback_->OnServerConnectionFailure(); + resolver_->Destroy(false); + resolver_ = NULL; + state_ = NOT_CONNECTED; + } else { + server_address_ = resolver_->address(); + DoConnect(); + } +} +// +void PeerConnectionClient::DoConnect() { + control_socket_.reset(CreateClientSocket(server_address_.ipaddr().family())); + hanging_get_.reset(CreateClientSocket(server_address_.ipaddr().family())); + InitSocketSignals(); + char buffer[1024]; + snprintf(buffer, sizeof(buffer), "GET /sign_in?%s HTTP/1.0\r\n\r\n", + client_name_.c_str()); + onconnect_data_ = buffer; + + bool ret = ConnectControlSocket(); + if (ret) + state_ = SIGNING_IN; + if (!ret) { + callback_->OnServerConnectionFailure(); + } +} +// ͨתһpeerڵ +bool PeerConnectionClient::SendToPeer(int peer_id, const std::string& message) { + if (state_ != CONNECTED) + return false; + + RTC_DCHECK(is_connected()); + RTC_DCHECK(control_socket_->GetState() == rtc::Socket::CS_CLOSED); + if (!is_connected() || peer_id == -1) + return false; + + char headers[1024]; + snprintf(headers, sizeof(headers), + "POST /message?peer_id=%i&to=%i HTTP/1.0\r\n" + "Content-Length: %zu\r\n" + "Content-Type: text/plain\r\n" + "\r\n", + my_id_, peer_id, message.length()); + onconnect_data_ = headers; + onconnect_data_ += message; + return ConnectControlSocket(); +} + +bool PeerConnectionClient::SendHangUp(int peer_id) { + return SendToPeer(peer_id, kByeMessage); +} + +bool PeerConnectionClient::IsSendingMessage() { + return state_ == CONNECTED && + control_socket_->GetState() != rtc::Socket::CS_CLOSED; +} + +bool PeerConnectionClient::SignOut() { + if (state_ == NOT_CONNECTED || state_ == SIGNING_OUT) + return true; + + if (hanging_get_->GetState() != rtc::Socket::CS_CLOSED) + hanging_get_->Close(); + + if (control_socket_->GetState() == rtc::Socket::CS_CLOSED) { + state_ = SIGNING_OUT; + + if (my_id_ != -1) { + char buffer[1024]; + snprintf(buffer, sizeof(buffer), + "GET /sign_out?peer_id=%i HTTP/1.0\r\n\r\n", my_id_); + onconnect_data_ = buffer; + return ConnectControlSocket(); + } else { + // Can occur if the app is closed before we finish connecting. + return true; + } + } else { + state_ = SIGNING_OUT_WAITING; + } + + return true; +} + +void PeerConnectionClient::Close() { + control_socket_->Close(); + hanging_get_->Close(); + onconnect_data_.clear(); + peers_.clear(); + if (resolver_ != NULL) { + resolver_->Destroy(false); + resolver_ = NULL; + } + my_id_ = -1; + state_ = NOT_CONNECTED; +} + +bool PeerConnectionClient::ConnectControlSocket() { + RTC_DCHECK(control_socket_->GetState() == rtc::Socket::CS_CLOSED); + int err = control_socket_->Connect(server_address_); + if (err == SOCKET_ERROR) { + Close(); + return false; + } + return true; +} + +void PeerConnectionClient::OnConnect(rtc::AsyncSocket* socket) { + RTC_DCHECK(!onconnect_data_.empty()); + size_t sent = socket->Send(onconnect_data_.c_str(), onconnect_data_.length()); + RTC_DCHECK(sent == onconnect_data_.length()); + onconnect_data_.clear(); +} + +void PeerConnectionClient::OnHangingGetConnect(rtc::AsyncSocket* socket) { + char buffer[1024]; + snprintf(buffer, sizeof(buffer), "GET /wait?peer_id=%i HTTP/1.0\r\n\r\n", + my_id_); + int len = static_cast(strlen(buffer)); + int sent = socket->Send(buffer, len); + RTC_DCHECK(sent == len); +} + +void PeerConnectionClient::OnMessageFromPeer(int peer_id, + const std::string& message) { + if (message.length() == (sizeof(kByeMessage) - 1) && + message.compare(kByeMessage) == 0) { + callback_->OnPeerDisconnected(peer_id); + } else { + callback_->OnMessageFromPeer(peer_id, message); + } +} + +bool PeerConnectionClient::GetHeaderValue(const std::string& data, + size_t eoh, + const char* header_pattern, + size_t* value) { + RTC_DCHECK(value != NULL); + size_t found = data.find(header_pattern); + if (found != std::string::npos && found < eoh) { + *value = atoi(&data[found + strlen(header_pattern)]); + return true; + } + return false; +} + +bool PeerConnectionClient::GetHeaderValue(const std::string& data, + size_t eoh, + const char* header_pattern, + std::string* value) { + RTC_DCHECK(value != NULL); + size_t found = data.find(header_pattern); + if (found != std::string::npos && found < eoh) { + size_t begin = found + strlen(header_pattern); + size_t end = data.find("\r\n", begin); + if (end == std::string::npos) + end = eoh; + value->assign(data.substr(begin, end - begin)); + return true; + } + return false; +} + +bool PeerConnectionClient::ReadIntoBuffer(rtc::AsyncSocket* socket, + std::string* data, + size_t* content_length) { + char buffer[0xffff]; + do { + int bytes = socket->Recv(buffer, sizeof(buffer), nullptr); + if (bytes <= 0) + break; + data->append(buffer, bytes); + } while (true); + + bool ret = false; + size_t i = data->find("\r\n\r\n"); + if (i != std::string::npos) { + RTC_LOG(INFO) << "Headers received"; + if (GetHeaderValue(*data, i, "\r\nContent-Length: ", content_length)) { + size_t total_response_size = (i + 4) + *content_length; + if (data->length() >= total_response_size) { + ret = true; + std::string should_close; + const char kConnection[] = "\r\nConnection: "; + if (GetHeaderValue(*data, i, kConnection, &should_close) && + should_close.compare("close") == 0) { + socket->Close(); + // Since we closed the socket, there was no notification delivered + // to us. Compensate by letting ourselves know. + OnClose(socket, 0); + } + } else { + // We haven't received everything. Just continue to accept data. + } + } else { + RTC_LOG(LS_ERROR) << "No content length field specified by the server."; + } + } + return ret; +} + +void PeerConnectionClient::OnRead(rtc::AsyncSocket* socket) { + size_t content_length = 0; + if (ReadIntoBuffer(socket, &control_data_, &content_length)) { + size_t peer_id = 0, eoh = 0; + bool ok = + ParseServerResponse(control_data_, content_length, &peer_id, &eoh); + if (ok) { + if (my_id_ == -1) { + // First response. Let's store our server assigned ID. + RTC_DCHECK(state_ == SIGNING_IN); + my_id_ = static_cast(peer_id); + RTC_DCHECK(my_id_ != -1); + + // The body of the response will be a list of already connected peers. + if (content_length) { + size_t pos = eoh + 4; + while (pos < control_data_.size()) { + size_t eol = control_data_.find('\n', pos); + if (eol == std::string::npos) + break; + int id = 0; + std::string name; + bool connected; + if (ParseEntry(control_data_.substr(pos, eol - pos), &name, &id, + &connected) && + id != my_id_) { + peers_[id] = name; + callback_->OnPeerConnected(id, name); + } + pos = eol + 1; + } + } + RTC_DCHECK(is_connected()); + callback_->OnSignedIn(); + } else if (state_ == SIGNING_OUT) { + Close(); + callback_->OnDisconnected(); + } else if (state_ == SIGNING_OUT_WAITING) { + SignOut(); + } + } + + control_data_.clear(); + + if (state_ == SIGNING_IN) { + RTC_DCHECK(hanging_get_->GetState() == rtc::Socket::CS_CLOSED); + state_ = CONNECTED; + hanging_get_->Connect(server_address_); + } + } +} + +void PeerConnectionClient::OnHangingGetRead(rtc::AsyncSocket* socket) { + RTC_LOG(INFO) << __FUNCTION__; + size_t content_length = 0; + if (ReadIntoBuffer(socket, ¬ification_data_, &content_length)) { + size_t peer_id = 0, eoh = 0; + bool ok = + ParseServerResponse(notification_data_, content_length, &peer_id, &eoh); + + if (ok) { + // Store the position where the body begins. + size_t pos = eoh + 4; + + if (my_id_ == static_cast(peer_id)) { + // A notification about a new member or a member that just + // disconnected. + int id = 0; + std::string name; + bool connected = false; + if (ParseEntry(notification_data_.substr(pos), &name, &id, + &connected)) { + if (connected) { + peers_[id] = name; + callback_->OnPeerConnected(id, name); + } else { + peers_.erase(id); + callback_->OnPeerDisconnected(id); + } + } + } else { + OnMessageFromPeer(static_cast(peer_id), + notification_data_.substr(pos)); + } + } + + notification_data_.clear(); + } + + if (hanging_get_->GetState() == rtc::Socket::CS_CLOSED && + state_ == CONNECTED) { + hanging_get_->Connect(server_address_); + } +} + +bool PeerConnectionClient::ParseEntry(const std::string& entry, + std::string* name, + int* id, + bool* connected) { + RTC_DCHECK(name != NULL); + RTC_DCHECK(id != NULL); + RTC_DCHECK(connected != NULL); + RTC_DCHECK(!entry.empty()); + + *connected = false; + size_t separator = entry.find(','); + if (separator != std::string::npos) { + *id = atoi(&entry[separator + 1]); + name->assign(entry.substr(0, separator)); + separator = entry.find(',', separator + 1); + if (separator != std::string::npos) { + *connected = atoi(&entry[separator + 1]) ? true : false; + } + } + return !name->empty(); +} + +int PeerConnectionClient::GetResponseStatus(const std::string& response) { + int status = -1; + size_t pos = response.find(' '); + if (pos != std::string::npos) + status = atoi(&response[pos + 1]); + return status; +} + +bool PeerConnectionClient::ParseServerResponse(const std::string& response, + size_t content_length, + size_t* peer_id, + size_t* eoh) { + int status = GetResponseStatus(response.c_str()); + if (status != 200) { + RTC_LOG(LS_ERROR) << "Received error from server"; + Close(); + callback_->OnDisconnected(); + return false; + } + + *eoh = response.find("\r\n\r\n"); + RTC_DCHECK(*eoh != std::string::npos); + if (*eoh == std::string::npos) + return false; + + *peer_id = -1; + + // See comment in peer_channel.cc for why we use the Pragma header and + // not e.g. "X-Peer-Id". + GetHeaderValue(response, *eoh, "\r\nPragma: ", peer_id); + + return true; +} + +void PeerConnectionClient::OnClose(rtc::AsyncSocket* socket, int err) { + RTC_LOG(INFO) << __FUNCTION__; + + socket->Close(); + +#ifdef WIN32 + if (err != WSAECONNREFUSED) { +#else + if (err != ECONNREFUSED) { +#endif + if (socket == hanging_get_.get()) { + if (state_ == CONNECTED) { + hanging_get_->Close(); + hanging_get_->Connect(server_address_); + } + } else { + callback_->OnMessageSent(err); + } + } else { + if (socket == control_socket_.get()) { + RTC_LOG(WARNING) << "Connection refused; retrying in 2 seconds"; + rtc::Thread::Current()->PostDelayed(RTC_FROM_HERE, kReconnectDelay, this, + 0); + } else { + Close(); + callback_->OnDisconnected(); + } + } +} + +void PeerConnectionClient::OnMessage(rtc::Message* msg) { + // ignore msg; there is currently only one supported message ("retry") + DoConnect(); +} diff --git a/test/src/webrtcdemo/peerconnection/PeerConnectionClientDemo/peer_connection_client.h b/test/src/webrtcdemo/peerconnection/PeerConnectionClientDemo/peer_connection_client.h new file mode 100644 index 0000000..55fa566 --- /dev/null +++ b/test/src/webrtcdemo/peerconnection/PeerConnectionClientDemo/peer_connection_client.h @@ -0,0 +1,134 @@ +/* + * Copyright 2011 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 EXAMPLES_PEERCONNECTION_CLIENT_PEER_CONNECTION_CLIENT_H_ +#define EXAMPLES_PEERCONNECTION_CLIENT_PEER_CONNECTION_CLIENT_H_ + +#include +#include +#include + +#include "rtc_base/net_helpers.h" +#include "rtc_base/physical_socket_server.h" +#include "rtc_base/signal_thread.h" +#include "rtc_base/third_party/sigslot/sigslot.h" + +typedef std::map Peers; + +struct PeerConnectionClientObserver { + virtual void OnSignedIn() = 0; // Called when we're logged on. + virtual void OnDisconnected() = 0; + virtual void OnPeerConnected(int id, const std::string& name) = 0; + virtual void OnPeerDisconnected(int peer_id) = 0; + virtual void OnMessageFromPeer(int peer_id, const std::string& message) = 0; + virtual void OnMessageSent(int err) = 0; + virtual void OnServerConnectionFailure() = 0; + + protected: + virtual ~PeerConnectionClientObserver() {} +}; + +class PeerConnectionClient : public sigslot::has_slots<>, + public rtc::MessageHandler { + public: + enum State { + NOT_CONNECTED, + RESOLVING, + SIGNING_IN, + CONNECTED, + SIGNING_OUT_WAITING, + SIGNING_OUT, + }; + + PeerConnectionClient(); + ~PeerConnectionClient(); + + int id() const; + bool is_connected() const; + const Peers& peers() const; + + void RegisterObserver(PeerConnectionClientObserver* callback); + + void Connect(const std::string& server, + int port, + const std::string& client_name); + + bool SendToPeer(int peer_id, const std::string& message); + bool SendHangUp(int peer_id); + bool IsSendingMessage(); + + bool SignOut(); + + // implements the MessageHandler interface + void OnMessage(rtc::Message* msg); + + protected: + void DoConnect(); + void Close(); + void InitSocketSignals(); + bool ConnectControlSocket(); + void OnConnect(rtc::AsyncSocket* socket); + void OnHangingGetConnect(rtc::AsyncSocket* socket); + void OnMessageFromPeer(int peer_id, const std::string& message); + + // Quick and dirty support for parsing HTTP header values. + bool GetHeaderValue(const std::string& data, + size_t eoh, + const char* header_pattern, + size_t* value); + + bool GetHeaderValue(const std::string& data, + size_t eoh, + const char* header_pattern, + std::string* value); + + // Returns true if the whole response has been read. + bool ReadIntoBuffer(rtc::AsyncSocket* socket, + std::string* data, + size_t* content_length); + + void OnRead(rtc::AsyncSocket* socket); + + void OnHangingGetRead(rtc::AsyncSocket* socket); + + // Parses a single line entry in the form ",," + bool ParseEntry(const std::string& entry, + std::string* name, + int* id, + bool* connected); + + int GetResponseStatus(const std::string& response); + + bool ParseServerResponse(const std::string& response, + size_t content_length, + size_t* peer_id, + size_t* eoh); + + void OnClose(rtc::AsyncSocket* socket, int err); + + void OnResolveResult(rtc::AsyncResolverInterface* resolver); + + PeerConnectionClientObserver* callback_; + rtc::SocketAddress server_address_; + rtc::AsyncResolver* resolver_; + std::unique_ptr control_socket_; + std::unique_ptr hanging_get_; + std::string onconnect_data_; + std::string control_data_; + std::string notification_data_; + std::string client_name_; + Peers peers_; + State state_; + int my_id_; +}; + +#endif // EXAMPLES_PEERCONNECTION_CLIENT_PEER_CONNECTION_CLIENT_H_ diff --git a/test/src/webrtcdemo/peerconnection/server/data_socket.cc b/test/src/webrtcdemo/peerconnection/server/data_socket.cc new file mode 100644 index 0000000..b1e9f40 --- /dev/null +++ b/test/src/webrtcdemo/peerconnection/server/data_socket.cc @@ -0,0 +1,333 @@ +/* + * Copyright 2011 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 "data_socket.h" + +#include +#include +#include +#include +#include +#if defined(WEBRTC_POSIX) +#include +#endif + +#include "utils.h" + +static const char kHeaderTerminator[] = "\r\n\r\n"; +static const int kHeaderTerminatorLength = sizeof(kHeaderTerminator) - 1; + +// static +const char DataSocket::kCrossOriginAllowHeaders[] = + "Access-Control-Allow-Origin: *\r\n" + "Access-Control-Allow-Credentials: true\r\n" + "Access-Control-Allow-Methods: POST, GET, OPTIONS\r\n" + "Access-Control-Allow-Headers: Content-Type, " + "Content-Length, Connection, Cache-Control\r\n" + "Access-Control-Expose-Headers: Content-Length\r\n"; + +#if defined(WIN32) +class WinsockInitializer +{ + static WinsockInitializer singleton; + + WinsockInitializer() + { + WSADATA data; + WSAStartup(MAKEWORD(1, 0), &data); + } + +public: + ~WinsockInitializer() { WSACleanup(); } +}; +WinsockInitializer WinsockInitializer::singleton; +#endif + +// +// SocketBase +// + +bool SocketBase::Create() +{ + assert(!valid()); + socket_ = ::socket(AF_INET, SOCK_STREAM, 0); + return valid(); +} + +void SocketBase::Close() +{ + if (socket_ != INVALID_SOCKET) + { + closesocket(socket_); + socket_ = INVALID_SOCKET; + } +} + +// +// DataSocket +// + +std::string DataSocket::request_arguments() const +{ + size_t args = request_path_.find('?'); + if (args != std::string::npos) + return request_path_.substr(args + 1); + return ""; +} + +bool DataSocket::PathEquals(const char *path) const +{ + assert(path); + size_t args = request_path_.find('?'); + if (args != std::string::npos) + return request_path_.substr(0, args).compare(path) == 0; + return request_path_.compare(path) == 0; +} + +bool DataSocket::OnDataAvailable(bool *close_socket) +{ + assert(valid()); + char buffer[0xfff] = {0}; + int bytes = recv(socket_, buffer, sizeof(buffer), 0); + if (bytes == SOCKET_ERROR || bytes == 0) + { + *close_socket = true; + return false; + } + + *close_socket = false; + + bool ret = true; + if (headers_received()) + { + if (method_ != POST) + { + // unexpectedly received data. + ret = false; + } + else + { + data_.append(buffer, bytes); + } + } + else + { + request_headers_.append(buffer, bytes); + size_t found = request_headers_.find(kHeaderTerminator); + if (found != std::string::npos) + { + data_ = request_headers_.substr(found + kHeaderTerminatorLength); + request_headers_.resize(found + kHeaderTerminatorLength); + ret = ParseHeaders(); + } + } + return ret; +} + +bool DataSocket::Send(const std::string &data) const +{ + return send(socket_, data.data(), static_cast(data.length()), 0) != + SOCKET_ERROR; +} + +bool DataSocket::Send(const std::string &status, + bool connection_close, + const std::string &content_type, + const std::string &extra_headers, + const std::string &data) const +{ + assert(valid()); + assert(!status.empty()); + std::string buffer("HTTP/1.1 " + status + "\r\n"); + + buffer += + "Server: PeerConnectionTestServer/0.1\r\n" + "Cache-Control: no-cache\r\n"; + + if (connection_close) + buffer += "Connection: close\r\n"; + + if (!content_type.empty()) + buffer += "Content-Type: " + content_type + "\r\n"; + + buffer += + "Content-Length: " + int2str(static_cast(data.size())) + "\r\n"; + + if (!extra_headers.empty()) + { + buffer += extra_headers; + // Extra headers are assumed to have a separator per header. + } + + buffer += kCrossOriginAllowHeaders; + + buffer += "\r\n"; + buffer += data; + + return Send(buffer); +} + +void DataSocket::Clear() +{ + method_ = INVALID; + content_length_ = 0; + content_type_.clear(); + request_path_.clear(); + request_headers_.clear(); + data_.clear(); +} + +bool DataSocket::ParseHeaders() +{ + assert(!request_headers_.empty()); + assert(method_ == INVALID); + size_t i = request_headers_.find("\r\n"); + if (i == std::string::npos) + return false; + + if (!ParseMethodAndPath(request_headers_.data(), i)) + return false; + + assert(method_ != INVALID); + assert(!request_path_.empty()); + + if (method_ == POST) + { + const char *headers = request_headers_.data() + i + 2; + size_t len = request_headers_.length() - i - 2; + if (!ParseContentLengthAndType(headers, len)) + return false; + } + + return true; +} + +bool DataSocket::ParseMethodAndPath(const char *begin, size_t len) +{ + struct + { + const char *method_name; + size_t method_name_len; + RequestMethod id; + } supported_methods[] = { + {"GET", 3, GET}, + {"POST", 4, POST}, + {"OPTIONS", 7, OPTIONS}, + }; + + const char *path = NULL; + for (size_t i = 0; i < ARRAYSIZE(supported_methods); ++i) + { + if (len > supported_methods[i].method_name_len && + isspace(begin[supported_methods[i].method_name_len]) && + strncmp(begin, supported_methods[i].method_name, + supported_methods[i].method_name_len) == 0) + { + method_ = supported_methods[i].id; + path = begin + supported_methods[i].method_name_len; + break; + } + } + + const char *end = begin + len; + if (!path || path >= end) + return false; + + ++path; + begin = path; + while (!isspace(*path) && path < end) + ++path; + + request_path_.assign(begin, path - begin); + + return true; +} + +bool DataSocket::ParseContentLengthAndType(const char *headers, size_t length) +{ + assert(content_length_ == 0); + assert(content_type_.empty()); + + const char *end = headers + length; + while (headers && headers < end) + { + if (!isspace(headers[0])) + { + static const char kContentLength[] = "Content-Length:"; + static const char kContentType[] = "Content-Type:"; + if ((headers + ARRAYSIZE(kContentLength)) < end && + strncmp(headers, kContentLength, ARRAYSIZE(kContentLength) - 1) == + 0) + { + headers += ARRAYSIZE(kContentLength) - 1; + while (headers[0] == ' ') + ++headers; + content_length_ = atoi(headers); + } + else if ((headers + ARRAYSIZE(kContentType)) < end && + strncmp(headers, kContentType, ARRAYSIZE(kContentType) - 1) == + 0) + { + headers += ARRAYSIZE(kContentType) - 1; + while (headers[0] == ' ') + ++headers; + const char *type_end = strstr(headers, "\r\n"); + if (type_end == NULL) + type_end = end; + content_type_.assign(headers, type_end); + } + } + else + { + ++headers; + } + headers = strstr(headers, "\r\n"); + if (headers) + headers += 2; + } + + return !content_type_.empty() && content_length_ != 0; +} + +// +// ListeningSocket +// + +bool ListeningSocket::Listen(unsigned short port) +{ + assert(valid()); + int enabled = 1; + setsockopt(socket_, SOL_SOCKET, SO_REUSEADDR, + reinterpret_cast(&enabled), sizeof(enabled)); + struct sockaddr_in addr = {0}; + addr.sin_family = AF_INET; + addr.sin_addr.s_addr = inet_addr("0.0.0.0"); + addr.sin_port = htons(port); + if (bind(socket_, reinterpret_cast(&addr), sizeof(addr)) == + SOCKET_ERROR) + { + printf("bind failed\n"); + return false; + } + return listen(socket_, 5) != SOCKET_ERROR; +} + +DataSocket *ListeningSocket::Accept() const +{ + assert(valid()); + struct sockaddr_in addr = {0}; + socklen_t size = sizeof(addr); + NativeSocket client = + accept(socket_, reinterpret_cast(&addr), &size); + if (client == INVALID_SOCKET) + return NULL; + + return new DataSocket(client); +} diff --git a/test/src/webrtcdemo/peerconnection/server/data_socket.h b/test/src/webrtcdemo/peerconnection/server/data_socket.h new file mode 100644 index 0000000..4429bc5 --- /dev/null +++ b/test/src/webrtcdemo/peerconnection/server/data_socket.h @@ -0,0 +1,150 @@ +/* + * Copyright 2011 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 EXAMPLES_PEERCONNECTION_SERVER_DATA_SOCKET_H_ +#define EXAMPLES_PEERCONNECTION_SERVER_DATA_SOCKET_H_ + +#ifdef WIN32 +#include +typedef int socklen_t; +typedef SOCKET NativeSocket; +#else +#include +#include +#include +#define closesocket close +typedef int NativeSocket; + +#ifndef SOCKET_ERROR +#define SOCKET_ERROR (-1) +#endif + +#ifndef INVALID_SOCKET +#define INVALID_SOCKET static_cast(-1) +#endif +#endif + +#include + +class SocketBase { + public: + SocketBase() : socket_(INVALID_SOCKET) {} + explicit SocketBase(NativeSocket socket) : socket_(socket) {} + ~SocketBase() { Close(); } + + NativeSocket socket() const { return socket_; } + bool valid() const { return socket_ != INVALID_SOCKET; } + + bool Create(); + void Close(); + + protected: + NativeSocket socket_; +}; + +// Represents an HTTP server socket. +class DataSocket : public SocketBase { + public: + enum RequestMethod { + INVALID, + GET, + POST, + OPTIONS, + }; + + explicit DataSocket(NativeSocket socket) + : SocketBase(socket), method_(INVALID), content_length_(0) {} + + ~DataSocket() {} + + static const char kCrossOriginAllowHeaders[]; + + bool headers_received() const { return method_ != INVALID; } + + RequestMethod method() const { return method_; } + + const std::string& request_path() const { return request_path_; } + std::string request_arguments() const; + + const std::string& data() const { return data_; } + + const std::string& content_type() const { return content_type_; } + + size_t content_length() const { return content_length_; } + + bool request_received() const { + return headers_received() && (method_ != POST || data_received()); + } + + bool data_received() const { + return method_ != POST || data_.length() >= content_length_; + } + + // Checks if the request path (minus arguments) matches a given path. + bool PathEquals(const char* path) const; + + // Called when we have received some data from clients. + // Returns false if an error occurred. + bool OnDataAvailable(bool* close_socket); + + // Send a raw buffer of bytes. + bool Send(const std::string& data) const; + + // Send an HTTP response. The |status| should start with a valid HTTP + // response code, followed by a string. E.g. "200 OK". + // If |connection_close| is set to true, an extra "Connection: close" HTTP + // header will be included. |content_type| is the mime content type, not + // including the "Content-Type: " string. + // |extra_headers| should be either empty or a list of headers where each + // header terminates with "\r\n". + // |data| is the body of the message. It's length will be specified via + // a "Content-Length" header. + bool Send(const std::string& status, + bool connection_close, + const std::string& content_type, + const std::string& extra_headers, + const std::string& data) const; + + // Clears all held state and prepares the socket for receiving a new request. + void Clear(); + + protected: + // A fairly relaxed HTTP header parser. Parses the method, path and + // content length (POST only) of a request. + // Returns true if a valid request was received and no errors occurred. + bool ParseHeaders(); + + // Figures out whether the request is a GET or POST and what path is + // being requested. + bool ParseMethodAndPath(const char* begin, size_t len); + + // Determines the length of the body and it's mime type. + bool ParseContentLengthAndType(const char* headers, size_t length); + + protected: + RequestMethod method_; + size_t content_length_; + std::string content_type_; + std::string request_path_; + std::string request_headers_; + std::string data_; +}; + +// The server socket. Accepts connections and generates DataSocket instances +// for each new connection. +class ListeningSocket : public SocketBase { + public: + ListeningSocket() {} + + bool Listen(unsigned short port); + DataSocket* Accept() const; +}; + +#endif // EXAMPLES_PEERCONNECTION_SERVER_DATA_SOCKET_H_ diff --git a/test/src/webrtcdemo/peerconnection/server/main.cc b/test/src/webrtcdemo/peerconnection/server/main.cc new file mode 100644 index 0000000..487e24c --- /dev/null +++ b/test/src/webrtcdemo/peerconnection/server/main.cc @@ -0,0 +1,213 @@ +/* + * Copyright 2011 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 +#include +#include +#if defined(WEBRTC_POSIX) +#include +#endif +#include + +#include +#include + +#include "absl/flags/flag.h" +#include "absl/flags/parse.h" +#include "absl/flags/usage.h" +#include "data_socket.h" +#include "peer_channel.h" +#include "system_wrappers/include/field_trial.h" +#include + +static const size_t kMaxConnections = (FD_SETSIZE - 2); + +void HandleBrowserRequest(DataSocket *ds, bool *quit) +{ + assert(ds && ds->valid()); + assert(quit); + + const std::string &path = ds->request_path(); + + *quit = (path.compare("/quit") == 0); + + if (*quit) + { + ds->Send("200 OK", true, "text/html", "", + "Quitting..."); + } + else if (ds->method() == DataSocket::OPTIONS) + { + // We'll get this when a browsers do cross-resource-sharing requests. + // The headers to allow cross-origin script support will be set inside + // Send. + ds->Send("200 OK", true, "", "", ""); + } + else + { + // Here we could write some useful output back to the browser depending on + // the path. + printf("Received an invalid request: %s\n", ds->request_path().c_str()); + ds->Send("500 Sorry", true, "text/html", "", + "Sorry, not yet implemented"); + } +} + +int main(int argc, char *argv[]) +{ + + int port = 8888; + // Abort if the user specifies a port that is outside the allowed + // range [1, 65535]. + if ((port < 1) || (port > 65535)) + { + printf("Error: %i is not a valid port.\n", port); + return -1; + } + + ListeningSocket listener; + if (!listener.Create()) + { + printf("Failed to create server socket\n"); + return -1; + } + else if (!listener.Listen(port)) + { + printf("Failed to listen on server socket\n"); + return -1; + } + + printf("Server listening on port %i\n", port); + + PeerChannel clients; + typedef std::vector SocketArray; + SocketArray sockets; + bool quit = false; + while (!quit) + { + fd_set socket_set; + FD_ZERO(&socket_set); + if (listener.valid()) + FD_SET(listener.socket(), &socket_set); + + for (SocketArray::iterator i = sockets.begin(); i != sockets.end(); ++i) + FD_SET((*i)->socket(), &socket_set); + + struct timeval timeout = {10, 0}; + if (select(FD_SETSIZE, &socket_set, NULL, NULL, &timeout) == SOCKET_ERROR) + { + printf("select failed\n"); + break; + } + + for (SocketArray::iterator i = sockets.begin(); i != sockets.end(); ++i) + { + DataSocket *s = *i; + bool socket_done = true; + if (FD_ISSET(s->socket(), &socket_set)) + { + if (s->OnDataAvailable(&socket_done) && s->request_received()) + { + ChannelMember *member = clients.Lookup(s); + if (member || PeerChannel::IsPeerConnection(s)) + { + if (!member) + { + if (s->PathEquals("/sign_in")) + { + clients.AddMember(s); + } + else + { + printf("No member found for: %s\n", s->request_path().c_str()); + s->Send("500 Error", true, "text/plain", "", + "Peer most likely gone."); + } + } + else if (member->is_wait_request(s)) + { + // no need to do anything. + socket_done = false; + } + else + { + ChannelMember *target = clients.IsTargetedRequest(s); + if (target) + { + member->ForwardRequestToPeer(s, target); + } + else if (s->PathEquals("/sign_out")) + { + s->Send("200 OK", true, "text/plain", "", ""); + } + else + { + printf("Couldn't find target for request: %s\n", + s->request_path().c_str()); + s->Send("500 Error", true, "text/plain", "", + "Peer most likely gone."); + } + } + } + else + { + HandleBrowserRequest(s, &quit); + if (quit) + { + printf("Quitting...\n"); + FD_CLR(listener.socket(), &socket_set); + listener.Close(); + clients.CloseAll(); + } + } + } + } + else + { + socket_done = false; + } + + if (socket_done) + { + printf("Disconnecting socket\n"); + clients.OnClosing(s); + assert(s->valid()); // Close must not have been called yet. + FD_CLR(s->socket(), &socket_set); + delete (*i); + i = sockets.erase(i); + if (i == sockets.end()) + break; + } + } + + clients.CheckForTimeout(); + + if (FD_ISSET(listener.socket(), &socket_set)) + { + DataSocket *s = listener.Accept(); + if (sockets.size() >= kMaxConnections) + { + delete s; // sorry, that's all we can take. + printf("Connection limit reached\n"); + } + else + { + sockets.push_back(s); + printf("New connection...\n"); + } + } + } + + for (SocketArray::iterator i = sockets.begin(); i != sockets.end(); ++i) + delete (*i); + sockets.clear(); + + return 0; +} diff --git a/test/src/webrtcdemo/peerconnection/server/peer_channel.cc b/test/src/webrtcdemo/peerconnection/server/peer_channel.cc new file mode 100644 index 0000000..af75a72 --- /dev/null +++ b/test/src/webrtcdemo/peerconnection/server/peer_channel.cc @@ -0,0 +1,412 @@ +/* + * Copyright 2011 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 "peer_channel.h" + +#include +#include +#include + +#include + +#include "data_socket.h" +#include "utils.h" + +// Set to the peer id of the originator when messages are being +// exchanged between peers, but set to the id of the receiving peer +// itself when notifications are sent from the server about the state +// of other peers. +// +// WORKAROUND: Since support for CORS varies greatly from one browser to the +// next, we don't use a custom name for our peer-id header (originally it was +// "X-Peer-Id: "). Instead, we use a "simple header", "Pragma" which should +// always be exposed to CORS requests. There is a special CORS header devoted +// to exposing proprietary headers (Access-Control-Expose-Headers), however +// at this point it is not working correctly in some popular browsers. +static const char kPeerIdHeader[] = "Pragma: "; + +static const char *kRequestPaths[] = { + "/wait", + "/sign_out", + "/message", +}; + +enum RequestPathIndex +{ + kWait, + kSignOut, + kMessage, +}; + +const size_t kMaxNameLength = 512; + +// +// ChannelMember +// + +int ChannelMember::s_member_id_ = 0; + +ChannelMember::ChannelMember(DataSocket *socket) + : waiting_socket_(NULL), + id_(++s_member_id_), + connected_(true), + timestamp_(time(NULL)) +{ + assert(socket); + assert(socket->method() == DataSocket::GET); + assert(socket->PathEquals("/sign_in")); + name_ = socket->request_arguments(); + if (name_.empty()) + name_ = "peer_" + int2str(id_); + else if (name_.length() > kMaxNameLength) + name_.resize(kMaxNameLength); + + std::replace(name_.begin(), name_.end(), ',', '_'); +} + +ChannelMember::~ChannelMember() {} + +bool ChannelMember::is_wait_request(DataSocket *ds) const +{ + return ds && ds->PathEquals(kRequestPaths[kWait]); +} + +bool ChannelMember::TimedOut() +{ + return waiting_socket_ == NULL && (time(NULL) - timestamp_) > 30; +} + +std::string ChannelMember::GetPeerIdHeader() const +{ + std::string ret(kPeerIdHeader + int2str(id_) + "\r\n"); + return ret; +} + +bool ChannelMember::NotifyOfOtherMember(const ChannelMember &other) +{ + assert(&other != this); + QueueResponse("200 OK", "text/plain", GetPeerIdHeader(), other.GetEntry()); + return true; +} + +// Returns a string in the form "name,id,connected\n". +std::string ChannelMember::GetEntry() const +{ + assert(name_.length() <= kMaxNameLength); + + // name, 11-digit int, 1-digit bool, newline, null + char entry[kMaxNameLength + 15]; + snprintf(entry, sizeof(entry), "%s,%d,%d\n", + name_.substr(0, kMaxNameLength).c_str(), id_, connected_); + return entry; +} + +void ChannelMember::ForwardRequestToPeer(DataSocket *ds, ChannelMember *peer) +{ + assert(peer); + assert(ds); + + std::string extra_headers(GetPeerIdHeader()); + + if (peer == this) + { + ds->Send("200 OK", true, ds->content_type(), extra_headers, ds->data()); + } + else + { + printf("Client %s sending to %s\n", name_.c_str(), peer->name().c_str()); + peer->QueueResponse("200 OK", ds->content_type(), extra_headers, + ds->data()); + ds->Send("200 OK", true, "text/plain", "", ""); + } +} + +void ChannelMember::OnClosing(DataSocket *ds) +{ + if (ds == waiting_socket_) + { + waiting_socket_ = NULL; + timestamp_ = time(NULL); + } +} + +void ChannelMember::QueueResponse(const std::string &status, + const std::string &content_type, + const std::string &extra_headers, + const std::string &data) +{ + if (waiting_socket_) + { + assert(queue_.empty()); + assert(waiting_socket_->method() == DataSocket::GET); + bool ok = + waiting_socket_->Send(status, true, content_type, extra_headers, data); + if (!ok) + { + printf("Failed to deliver data to waiting socket\n"); + } + waiting_socket_ = NULL; + timestamp_ = time(NULL); + } + else + { + QueuedResponse qr; + qr.status = status; + qr.content_type = content_type; + qr.extra_headers = extra_headers; + qr.data = data; + queue_.push(qr); + } +} + +void ChannelMember::SetWaitingSocket(DataSocket *ds) +{ + assert(ds->method() == DataSocket::GET); + if (ds && !queue_.empty()) + { + assert(waiting_socket_ == NULL); + const QueuedResponse &response = queue_.front(); + ds->Send(response.status, true, response.content_type, + response.extra_headers, response.data); + queue_.pop(); + } + else + { + waiting_socket_ = ds; + } +} + +// +// PeerChannel +// + +// static +bool PeerChannel::IsPeerConnection(const DataSocket *ds) +{ + assert(ds); + return (ds->method() == DataSocket::POST && ds->content_length() > 0) || + (ds->method() == DataSocket::GET && ds->PathEquals("/sign_in")); +} + +ChannelMember *PeerChannel::Lookup(DataSocket *ds) const +{ + assert(ds); + + if (ds->method() != DataSocket::GET && ds->method() != DataSocket::POST) + return NULL; + + size_t i = 0; + for (; i < ARRAYSIZE(kRequestPaths); ++i) + { + if (ds->PathEquals(kRequestPaths[i])) + break; + } + + if (i == ARRAYSIZE(kRequestPaths)) + return NULL; + + std::string args(ds->request_arguments()); + static const char kPeerId[] = "peer_id="; + size_t found = args.find(kPeerId); + if (found == std::string::npos) + return NULL; + + int id = atoi(&args[found + ARRAYSIZE(kPeerId) - 1]); + Members::const_iterator iter = members_.begin(); + for (; iter != members_.end(); ++iter) + { + if (id == (*iter)->id()) + { + if (i == kWait) + (*iter)->SetWaitingSocket(ds); + if (i == kSignOut) + (*iter)->set_disconnected(); + return *iter; + } + } + + return NULL; +} + +ChannelMember *PeerChannel::IsTargetedRequest(const DataSocket *ds) const +{ + assert(ds); + // Regardless of GET or POST, we look for the peer_id parameter + // only in the request_path. + const std::string &path = ds->request_path(); + size_t args = path.find('?'); + if (args == std::string::npos) + return NULL; + size_t found; + const char kTargetPeerIdParam[] = "to="; + do + { + found = path.find(kTargetPeerIdParam, args); + if (found == std::string::npos) + return NULL; + if (found == (args + 1) || path[found - 1] == '&') + { + found += ARRAYSIZE(kTargetPeerIdParam) - 1; + break; + } + args = found + ARRAYSIZE(kTargetPeerIdParam) - 1; + } while (true); + int id = atoi(&path[found]); + Members::const_iterator i = members_.begin(); + for (; i != members_.end(); ++i) + { + if ((*i)->id() == id) + { + return *i; + } + } + return NULL; +} + +bool PeerChannel::AddMember(DataSocket *ds) +{ + assert(IsPeerConnection(ds)); + ChannelMember *new_guy = new ChannelMember(ds); + Members failures; + BroadcastChangedState(*new_guy, &failures); + HandleDeliveryFailures(&failures); + members_.push_back(new_guy); + + printf("New member added (total=%s): %s\n", + size_t2str(members_.size()).c_str(), new_guy->name().c_str()); + + // Let the newly connected peer know about other members of the channel. + std::string content_type; + std::string response = BuildResponseForNewMember(*new_guy, &content_type); + ds->Send("200 Added", true, content_type, new_guy->GetPeerIdHeader(), + response); + return true; +} + +void PeerChannel::CloseAll() +{ + Members::const_iterator i = members_.begin(); + for (; i != members_.end(); ++i) + { + (*i)->QueueResponse("200 OK", "text/plain", "", "Server shutting down"); + } + DeleteAll(); +} + +void PeerChannel::OnClosing(DataSocket *ds) +{ + for (Members::iterator i = members_.begin(); i != members_.end(); ++i) + { + ChannelMember *m = (*i); + m->OnClosing(ds); + if (!m->connected()) + { + i = members_.erase(i); + Members failures; + BroadcastChangedState(*m, &failures); + HandleDeliveryFailures(&failures); + delete m; + if (i == members_.end()) + break; + } + } + printf("Total connected: %s\n", size_t2str(members_.size()).c_str()); +} + +void PeerChannel::CheckForTimeout() +{ + for (Members::iterator i = members_.begin(); i != members_.end(); ++i) + { + ChannelMember *m = (*i); + if (m->TimedOut()) + { + printf("Timeout: %s\n", m->name().c_str()); + m->set_disconnected(); + i = members_.erase(i); + Members failures; + BroadcastChangedState(*m, &failures); + HandleDeliveryFailures(&failures); + delete m; + if (i == members_.end()) + break; + } + } +} + +void PeerChannel::DeleteAll() +{ + for (Members::iterator i = members_.begin(); i != members_.end(); ++i) + delete (*i); + members_.clear(); +} + +void PeerChannel::BroadcastChangedState(const ChannelMember &member, + Members *delivery_failures) +{ + // This function should be called prior to DataSocket::Close(). + assert(delivery_failures); + + if (!member.connected()) + { + printf("Member disconnected: %s\n", member.name().c_str()); + } + + Members::iterator i = members_.begin(); + for (; i != members_.end(); ++i) + { + if (&member != (*i)) + { + if (!(*i)->NotifyOfOtherMember(member)) + { + (*i)->set_disconnected(); + delivery_failures->push_back(*i); + i = members_.erase(i); + if (i == members_.end()) + break; + } + } + } +} + +void PeerChannel::HandleDeliveryFailures(Members *failures) +{ + assert(failures); + + while (!failures->empty()) + { + Members::iterator i = failures->begin(); + ChannelMember *member = *i; + assert(!member->connected()); + failures->erase(i); + BroadcastChangedState(*member, failures); + delete member; + } +} + +// Builds a simple list of "name,id\n" entries for each member. +std::string PeerChannel::BuildResponseForNewMember(const ChannelMember &member, + std::string *content_type) +{ + assert(content_type); + + *content_type = "text/plain"; + // The peer itself will always be the first entry. + std::string response(member.GetEntry()); + for (Members::iterator i = members_.begin(); i != members_.end(); ++i) + { + if (member.id() != (*i)->id()) + { + assert((*i)->connected()); + response += (*i)->GetEntry(); + } + } + + return response; +} diff --git a/test/src/webrtcdemo/peerconnection/server/peer_channel.h b/test/src/webrtcdemo/peerconnection/server/peer_channel.h new file mode 100644 index 0000000..ea6dd62 --- /dev/null +++ b/test/src/webrtcdemo/peerconnection/server/peer_channel.h @@ -0,0 +1,129 @@ +/* + * @Author: your name + * @Date: 2021-10-17 00:47:32 + * @LastEditTime: 2021-10-17 11:35:02 + * @LastEditors: Please set LastEditors + * @Description: In User Settings Edit + * @FilePath: \webrtcdemo\peerconnection\server\peer_channel.h + */ +/* + * Copyright 2011 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 EXAMPLES_PEERCONNECTION_SERVER_PEER_CHANNEL_H_ +#define EXAMPLES_PEERCONNECTION_SERVER_PEER_CHANNEL_H_ + +#include + +#include +#include +#include + +class DataSocket; + +// Represents a single peer connected to the server. +class ChannelMember +{ +public: + explicit ChannelMember(DataSocket *socket); + ~ChannelMember(); + + bool connected() const { return connected_; } + int id() const { return id_; } + void set_disconnected() { connected_ = false; } + bool is_wait_request(DataSocket *ds) const; + const std::string &name() const { return name_; } + + bool TimedOut(); + + std::string GetPeerIdHeader() const; + + bool NotifyOfOtherMember(const ChannelMember &other); + + // Returns a string in the form "name,id\n". + std::string GetEntry() const; + + void ForwardRequestToPeer(DataSocket *ds, ChannelMember *peer); + + void OnClosing(DataSocket *ds); + + void QueueResponse(const std::string &status, + const std::string &content_type, + const std::string &extra_headers, + const std::string &data); + + void SetWaitingSocket(DataSocket *ds); + +protected: + struct QueuedResponse + { + std::string status, content_type, extra_headers, data; + }; + + DataSocket *waiting_socket_; + int id_; + bool connected_; + time_t timestamp_; + std::string name_; + std::queue queue_; + static int s_member_id_; +}; + +// Manages all currently connected peers. +class PeerChannel +{ +public: + typedef std::vector Members; + + PeerChannel() {} + + ~PeerChannel() { DeleteAll(); } + + const Members &members() const { return members_; } + + // Returns true if the request should be treated as a new ChannelMember + // request. Otherwise the request is not peerconnection related. + static bool IsPeerConnection(const DataSocket *ds); + + // Finds a connected peer that's associated with the |ds| socket. + ChannelMember *Lookup(DataSocket *ds) const; + + // Checks if the request has a "peer_id" parameter and if so, looks up the + // peer for which the request is targeted at. + ChannelMember *IsTargetedRequest(const DataSocket *ds) const; + + // Adds a new ChannelMember instance to the list of connected peers and + // associates it with the socket. + bool AddMember(DataSocket *ds); + + // Closes all connections and sends a "shutting down" message to all + // connected peers. + void CloseAll(); + + // Called when a socket was determined to be closing by the peer (or if the + // connection went dead). + void OnClosing(DataSocket *ds); + + void CheckForTimeout(); + +protected: + void DeleteAll(); + void BroadcastChangedState(const ChannelMember &member, + Members *delivery_failures); + void HandleDeliveryFailures(Members *failures); + + // Builds a simple list of "name,id\n" entries for each member. + std::string BuildResponseForNewMember(const ChannelMember &member, + std::string *content_type); + +protected: + Members members_; +}; + +#endif // EXAMPLES_PEERCONNECTION_SERVER_PEER_CHANNEL_H_ diff --git a/test/src/webrtcdemo/peerconnection/server/server_test.html b/test/src/webrtcdemo/peerconnection/server/server_test.html new file mode 100644 index 0000000..0a165f1 --- /dev/null +++ b/test/src/webrtcdemo/peerconnection/server/server_test.html @@ -0,0 +1,237 @@ + + +PeerConnection server test page + + + + + +Server:
+ Loopback (just send +received messages right back)
+Your name: + + +
+
+Target peer id: +Message: + +
+ + +
+
+

+ + diff --git a/test/src/webrtcdemo/peerconnection/server/utils.cc b/test/src/webrtcdemo/peerconnection/server/utils.cc new file mode 100644 index 0000000..77d0fad --- /dev/null +++ b/test/src/webrtcdemo/peerconnection/server/utils.cc @@ -0,0 +1,27 @@ +/* + * Copyright 2011 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 "utils.h" + +#include + +#include "rtc_base/string_encode.h" + +using rtc::ToString; + +std::string int2str(int i) +{ + return ToString(i); +} + +std::string size_t2str(size_t i) +{ + return ToString(i); +} diff --git a/test/src/webrtcdemo/peerconnection/server/utils.h b/test/src/webrtcdemo/peerconnection/server/utils.h new file mode 100644 index 0000000..11a469a --- /dev/null +++ b/test/src/webrtcdemo/peerconnection/server/utils.h @@ -0,0 +1,25 @@ +/* + * Copyright 2011 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 EXAMPLES_PEERCONNECTION_SERVER_UTILS_H_ +#define EXAMPLES_PEERCONNECTION_SERVER_UTILS_H_ + +#include + +#include + +#ifndef ARRAYSIZE +#define ARRAYSIZE(x) (sizeof(x) / sizeof(x[0])) +#endif + +std::string int2str(int i); +std::string size_t2str(size_t i); + +#endif // EXAMPLES_PEERCONNECTION_SERVER_UTILS_H_ diff --git a/test/src/webrtcdemo/webrtcdemo.cpp b/test/src/webrtcdemo/webrtcdemo.cpp index d8b1745..ff90e74 100644 --- a/test/src/webrtcdemo/webrtcdemo.cpp +++ b/test/src/webrtcdemo/webrtcdemo.cpp @@ -1,7 +1,7 @@ /* * @Author: your name * @Date: 2021-10-11 10:01:51 - * @LastEditTime: 2021-10-13 00:29:44 + * @LastEditTime: 2021-10-17 11:23:29 * @LastEditors: Please set LastEditors * @Description: In User Settings Edit * @FilePath: \test_algorithm\webrtcdemo.cpp @@ -28,9 +28,10 @@ class CaptureCallBack :public webrtc::DesktopCapturer::Callback{ virtual void OnCaptureResult(webrtc::DesktopCapturer::Result ret, std::unique_ptr frame){ if(ret == webrtc::DesktopCapturer::Result::SUCCESS){ - std::cout << "capture frame " - << frame.get()->size().width() << " " - << frame.get()->size().height() << "\r\n"; + std::cout + << "capture frame " + << frame.get()->size().width() << " " + << frame.get()->size().height() << "\r\n"; } } }; @@ -44,4 +45,4 @@ int main(int argc,char *argv[]){ Sleep(100); } return 0; -} \ No newline at end of file +}