diff --git a/.gitignore b/.gitignore index 9ac76d8fac..65af915cde 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,4 @@ *.DS_Store -*.Makefile *.host.mk *.ncb *.ninja @@ -77,3 +76,8 @@ target/ src/ .gclient_entries +.gclient_previous_sync_commits +outvideo* +outaudio* +*.log + diff --git a/DEPS b/DEPS index 2b6b87ca75..b0fba03866 100644 --- a/DEPS +++ b/DEPS @@ -3110,6 +3110,26 @@ hooks = [ '--bucket', 'chromium-webrtc-resources', 'src/resources'], }, + { + 'name': 'noise_tracks', + 'pattern': '.', + 'action': [ "download_from_google_storage", + "--no_resume", + "--no_auth", + "--bucket", "chromium-webrtc-resources", + "-s", "resources/audio_processing/test/py_quality_assessment/noise_tracks/city.wav.sha1", + ], + }, + { + 'name': 'probing_signals', + 'pattern': '.', + 'action': [ "download_from_google_storage", + "--no_resume", + "--no_auth", + "--bucket", "chromium-webrtc-resources", + "-s", "resources/audio_processing/test/py_quality_assessment/probing_signals/tone-880.wav.sha1", + ], + }, ] recursedeps = [] diff --git a/Makefile b/Makefile index 70e88127ad..7af9304889 100644 --- a/Makefile +++ b/Makefile @@ -7,8 +7,8 @@ target_lib_dir := $(target_dir)/lib target_bin_dir := $(target_dir)/bin target_pylib_dir := $(target_dir)/pylib -compile_docker := alphartc-compile -release_docker := alphartc +compile_docker := gcc-compile +release_docker := gcc host_workdir := `pwd` docker_homedir := /app/AlphaRTC/ @@ -22,18 +22,13 @@ all: init sync app release init: docker build dockers --build-arg UID=$(shell id -u) --build-arg GUID=$(shell id -g) -f $(build_dockerfile) -t $(compile_docker) -release: - docker build $(target_dir) -f $(release_dockerfile) -t $(release_docker) - sync: docker run $(docker_flags) $(compile_docker) \ make docker-$@ \ output_dir=$(output_dir) \ gn_flags=$(gn_flags) -app: peerconnection_serverless - -peerconnection_serverless: +app: docker run $(docker_flags) $(compile_docker) \ make docker-$@ \ output_dir=$(output_dir) \ @@ -41,26 +36,20 @@ peerconnection_serverless: target_bin_dir=$(target_bin_dir) \ target_pylib_dir=$(target_pylib_dir) -# Docker internal command +release: + docker build $(target_dir) -f $(release_dockerfile) -t $(release_docker) + +# Docker internal command docker-sync: gclient sync mv -fvn src/* . rm -rf src gn gen $(output_dir) $(gn_flags) -docker-app: docker-peerconnection_serverless - -docker-peerconnection_serverless: - ninja -C $(output_dir) peerconnection_serverless - - mkdir -p $(target_lib_dir) - cp modules/third_party/onnxinfer/lib/*.so $(target_lib_dir) - cp modules/third_party/onnxinfer/lib/*.so.* $(target_lib_dir) +docker-app: + ninja -C $(output_dir) peerconnection_challenge_client mkdir -p $(target_bin_dir) - cp $(output_dir)/peerconnection_serverless $(target_bin_dir)/peerconnection_serverless.origin - cp examples/peerconnection/serverless/peerconnection_serverless $(target_bin_dir) - - mkdir -p $(target_pylib_dir) - cp modules/third_party/cmdinfer/*.py $(target_pylib_dir)/ + cp $(output_dir)/peerconnection_challenge_client $(target_bin_dir)/peerconnection_serverless_gcc + cp $(output_dir)/peerconnection_challenge_client peerconnection_serverless_gcc diff --git a/api/transport/BUILD.gn b/api/transport/BUILD.gn index 8d78039b08..0f07301fe4 100644 --- a/api/transport/BUILD.gn +++ b/api/transport/BUILD.gn @@ -86,20 +86,17 @@ rtc_source_set("datagram_transport_interface") { ] } -# Revision for enabling AlphaCC and disabling GCC -rtc_static_library("alpha_cc") { +rtc_library("goog_cc") { visibility = [ "*" ] sources = [ - "alpha_cc_factory.cc", - "alpha_cc_factory.h", + "goog_cc_factory.cc", + "goog_cc_factory.h", ] deps = [ ":network_control", ":webrtc_key_value_config", "..:network_state_predictor_api", - "../../modules/congestion_controller/alpha_cc", - "//third_party/abseil-cpp/absl/memory", - # "../../modules/congestion_controller/goog_cc", + "../../modules/congestion_controller/goog_cc", "../../rtc_base:deprecation", ] } diff --git a/api/transport/alpha_cc_factory.cc b/api/transport/alpha_cc_factory.cc index 68e4e8687f..8a4cf03c0e 100644 --- a/api/transport/alpha_cc_factory.cc +++ b/api/transport/alpha_cc_factory.cc @@ -17,25 +17,25 @@ #include "rtc_base/logging.h" namespace webrtc { -GoogCcNetworkControllerFactory::GoogCcNetworkControllerFactory( +AlphaCcNetworkControllerFactory::AlphaCcNetworkControllerFactory( RtcEventLog* event_log) : event_log_(event_log) {} -GoogCcNetworkControllerFactory::GoogCcNetworkControllerFactory( +AlphaCcNetworkControllerFactory::AlphaCcNetworkControllerFactory( NetworkStatePredictorFactoryInterface* network_state_predictor_factory) { factory_config_.network_state_predictor_factory = network_state_predictor_factory; } -GoogCcNetworkControllerFactory::GoogCcNetworkControllerFactory( - GoogCcFactoryConfig config) +AlphaCcNetworkControllerFactory::AlphaCcNetworkControllerFactory( + AlphaCcFactoryConfig config) : factory_config_(std::move(config)) {} std::unique_ptr -GoogCcNetworkControllerFactory::Create(NetworkControllerConfig config) { +AlphaCcNetworkControllerFactory::Create(NetworkControllerConfig config) { if (event_log_) config.event_log = event_log_; - GoogCcConfig goog_cc_config; + AlphaCcConfig goog_cc_config; goog_cc_config.feedback_only = factory_config_.feedback_only; if (factory_config_.network_state_estimator_factory) { RTC_DCHECK(config.key_value_config); @@ -48,18 +48,18 @@ GoogCcNetworkControllerFactory::Create(NetworkControllerConfig config) { factory_config_.network_state_predictor_factory ->CreateNetworkStatePredictor(); } - return std::make_unique(config, + return std::make_unique(config, std::move(goog_cc_config)); } -TimeDelta GoogCcNetworkControllerFactory::GetProcessInterval() const { +TimeDelta AlphaCcNetworkControllerFactory::GetProcessInterval() const { const int64_t kUpdateIntervalMs = 25; return TimeDelta::Millis(kUpdateIntervalMs); } -GoogCcFeedbackNetworkControllerFactory::GoogCcFeedbackNetworkControllerFactory( +AlphaCcFeedbackNetworkControllerFactory::AlphaCcFeedbackNetworkControllerFactory( RtcEventLog* event_log) - : GoogCcNetworkControllerFactory(event_log) { + : AlphaCcNetworkControllerFactory(event_log) { factory_config_.feedback_only = true; } diff --git a/api/transport/alpha_cc_factory.h b/api/transport/alpha_cc_factory.h index b14d6dcd78..c94d7ccf70 100644 --- a/api/transport/alpha_cc_factory.h +++ b/api/transport/alpha_cc_factory.h @@ -19,7 +19,7 @@ namespace webrtc { class RtcEventLog; -struct GoogCcFactoryConfig { +struct AlphaCcFactoryConfig { std::unique_ptr network_state_estimator_factory = nullptr; NetworkStatePredictorFactoryInterface* network_state_predictor_factory = @@ -27,32 +27,32 @@ struct GoogCcFactoryConfig { bool feedback_only = false; }; -class GoogCcNetworkControllerFactory +class AlphaCcNetworkControllerFactory : public NetworkControllerFactoryInterface { public: - GoogCcNetworkControllerFactory() = default; - explicit RTC_DEPRECATED GoogCcNetworkControllerFactory( + AlphaCcNetworkControllerFactory() = default; + explicit RTC_DEPRECATED AlphaCcNetworkControllerFactory( RtcEventLog* event_log); - explicit GoogCcNetworkControllerFactory( + explicit AlphaCcNetworkControllerFactory( NetworkStatePredictorFactoryInterface* network_state_predictor_factory); - explicit GoogCcNetworkControllerFactory(GoogCcFactoryConfig config); + explicit AlphaCcNetworkControllerFactory(AlphaCcFactoryConfig config); std::unique_ptr Create( NetworkControllerConfig config) override; TimeDelta GetProcessInterval() const override; protected: RtcEventLog* const event_log_ = nullptr; - GoogCcFactoryConfig factory_config_; + AlphaCcFactoryConfig factory_config_; }; -// Deprecated, use GoogCcFactoryConfig to enable feedback only mode instead. +// Deprecated, use AlphaCcFactoryConfig to enable feedback only mode instead. // Factory to create packet feedback only GoogCC, this can be used for // connections providing packet receive time feedback but no other reports. -class RTC_DEPRECATED GoogCcFeedbackNetworkControllerFactory - : public GoogCcNetworkControllerFactory { +class RTC_DEPRECATED AlphaCcFeedbackNetworkControllerFactory + : public AlphaCcNetworkControllerFactory { public: - explicit GoogCcFeedbackNetworkControllerFactory(RtcEventLog* event_log); + explicit AlphaCcFeedbackNetworkControllerFactory(RtcEventLog* event_log); }; } // namespace webrtc diff --git a/api/transport/goog_cc_factory.cc b/api/transport/goog_cc_factory.cc new file mode 100644 index 0000000000..3117639c13 --- /dev/null +++ b/api/transport/goog_cc_factory.cc @@ -0,0 +1,65 @@ +/* + * Copyright (c) 2018 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 "api/transport/goog_cc_factory.h" + +#include +#include + +#include "modules/congestion_controller/goog_cc/goog_cc_network_control.h" + +namespace webrtc { +GoogCcNetworkControllerFactory::GoogCcNetworkControllerFactory( + RtcEventLog* event_log) + : event_log_(event_log) {} + +GoogCcNetworkControllerFactory::GoogCcNetworkControllerFactory( + NetworkStatePredictorFactoryInterface* network_state_predictor_factory) { + factory_config_.network_state_predictor_factory = + network_state_predictor_factory; +} + +GoogCcNetworkControllerFactory::GoogCcNetworkControllerFactory( + GoogCcFactoryConfig config) + : factory_config_(std::move(config)) {} + +std::unique_ptr +GoogCcNetworkControllerFactory::Create(NetworkControllerConfig config) { + if (event_log_) + config.event_log = event_log_; + GoogCcConfig goog_cc_config; + goog_cc_config.feedback_only = factory_config_.feedback_only; + if (factory_config_.network_state_estimator_factory) { + RTC_DCHECK(config.key_value_config); + goog_cc_config.network_state_estimator = + factory_config_.network_state_estimator_factory->Create( + config.key_value_config); + } + if (factory_config_.network_state_predictor_factory) { + goog_cc_config.network_state_predictor = + factory_config_.network_state_predictor_factory + ->CreateNetworkStatePredictor(); + } + return std::make_unique(config, + std::move(goog_cc_config)); +} + +TimeDelta GoogCcNetworkControllerFactory::GetProcessInterval() const { + const int64_t kUpdateIntervalMs = 25; + return TimeDelta::Millis(kUpdateIntervalMs); +} + +GoogCcFeedbackNetworkControllerFactory::GoogCcFeedbackNetworkControllerFactory( + RtcEventLog* event_log) + : GoogCcNetworkControllerFactory(event_log) { + factory_config_.feedback_only = true; +} + +} // namespace webrtc \ No newline at end of file diff --git a/api/transport/goog_cc_factory.h b/api/transport/goog_cc_factory.h new file mode 100644 index 0000000000..330cd74402 --- /dev/null +++ b/api/transport/goog_cc_factory.h @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2018 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 API_TRANSPORT_GOOG_CC_FACTORY_H_ +#define API_TRANSPORT_GOOG_CC_FACTORY_H_ +#include + +#include "api/network_state_predictor.h" +#include "api/transport/network_control.h" +#include "rtc_base/deprecation.h" + +namespace webrtc { +class RtcEventLog; + +struct GoogCcFactoryConfig { + std::unique_ptr + network_state_estimator_factory = nullptr; + NetworkStatePredictorFactoryInterface* network_state_predictor_factory = + nullptr; + bool feedback_only = false; +}; + +class GoogCcNetworkControllerFactory + : public NetworkControllerFactoryInterface { + public: + GoogCcNetworkControllerFactory() = default; + explicit RTC_DEPRECATED GoogCcNetworkControllerFactory( + RtcEventLog* event_log); + explicit GoogCcNetworkControllerFactory( + NetworkStatePredictorFactoryInterface* network_state_predictor_factory); + + explicit GoogCcNetworkControllerFactory(GoogCcFactoryConfig config); + std::unique_ptr Create( + NetworkControllerConfig config) override; + TimeDelta GetProcessInterval() const override; + + protected: + RtcEventLog* const event_log_ = nullptr; + GoogCcFactoryConfig factory_config_; +}; + +// Deprecated, use GoogCcFactoryConfig to enable feedback only mode instead. +// Factory to create packet feedback only GoogCC, this can be used for +// connections providing packet receive time feedback but no other reports. +class RTC_DEPRECATED GoogCcFeedbackNetworkControllerFactory + : public GoogCcNetworkControllerFactory { + public: + explicit GoogCcFeedbackNetworkControllerFactory(RtcEventLog* event_log); +}; + +} // namespace webrtc + +#endif // API_TRANSPORT_GOOG_CC_FACTORY_H_ \ No newline at end of file diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 72aea6d1b4..ce764ba768 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -18,40 +18,36 @@ steps: - checkout: self - script: make init - displayName: 'build compile environment' + displayName: 'Build compile environment' - script: make sync - displayName: 'sync dependencies' + displayName: 'Sync dependencies' - script: make app - displayName: 'build application' + displayName: 'Build application' - script: make release - displayName: 'build release image' + displayName: 'Build release image' -- script: docker run -d --rm -v `pwd`/examples/peerconnection/serverless/corpus:/app -w /app --name alphartc alphartc peerconnection_serverless receiver.json - && docker exec alphartc peerconnection_serverless sender.json - displayName: 'run example' +- script: docker run -d --rm -v `pwd`/examples/peerconnection/challenge_client/corpus:/app -w /app --name gcc gcc peerconnection_serverless_gcc receiver.json + && docker exec gcc peerconnection_serverless_gcc sender.json + displayName: 'Run GCC peerconnection example' -- script: docker run -d --rm -v `pwd`/examples/peerconnection/serverless/corpus:/app -w /app --name alphartc_pyinfer alphartc peerconnection_serverless receiver_pyinfer.json - && docker exec alphartc_pyinfer peerconnection_serverless sender_pyinfer.json - displayName: 'run pyinfer example' +- script: docker save gcc | gzip > gcc.tar.gz + displayName: "Export the GCC docker image" -- script: docker save alphartc | gzip > alphartc.tar.gz - displayName: "Export alphartc docker image" - -- publish: $(System.DefaultWorkingDirectory)/alphartc.tar.gz +- publish: $(System.DefaultWorkingDirectory)/gcc.tar.gz continueOnError: true - artifact: alphartc.tar.gz - displayName: "Archive AlphaRTC Peerconnection" + artifact: gcc.tar.gz + displayName: "Archive the GCC docker image" -- script: docker image tag alphartc:latest $(dockerRegistry)/alphartc:latest - displayName: 'Tag alphartc image' +- script: docker image tag gcc:latest $(dockerRegistry)/gcc:latest + displayName: 'Tag the GCC docker image' - task: Docker@2 inputs: - containerRegistry: 'opennetlab Azure registry' - repository: 'alphartc' + containerRegistry: 'OpenNetLab Azure registry' + repository: 'gcc' command: 'push' tags: 'latest' - displayName: "Push alphartc image" + displayName: "Push the GCC docker image to Azure registry" diff --git a/call/BUILD.gn b/call/BUILD.gn index 5c6bb69911..a9037c3819 100644 --- a/call/BUILD.gn +++ b/call/BUILD.gn @@ -153,8 +153,7 @@ rtc_library("rtp_sender") { "../api:transport_api", "../api/rtc_event_log", "../api/transport:field_trial_based_config", - # Revision for enabling AlphaCC and disabling GCC - "../api/transport:alpha_cc", + "../api/transport:goog_cc", "../api/transport:network_control", "../api/transport:webrtc_key_value_config", "../api/units:data_rate", diff --git a/call/bitrate_allocator.cc b/call/bitrate_allocator.cc index 8e2006defa..9557a84f84 100644 --- a/call/bitrate_allocator.cc +++ b/call/bitrate_allocator.cc @@ -40,7 +40,7 @@ const int kDefaultBitrateBps = 300000; const double kToggleFactor = 0.1; const uint32_t kMinToggleBitrateBps = 20000; -const int64_t kBweLogIntervalMs = 5000; +const int64_t kBweLogIntervalMs = 200; double MediaRatio(uint32_t allocated_bitrate, uint32_t protection_bitrate) { RTC_DCHECK_GT(allocated_bitrate, 0); @@ -391,7 +391,7 @@ void BitrateAllocator::OnNetworkEstimateChanged(TargetTransferRate msg) { // Periodically log the incoming BWE. int64_t now = msg.at_time.ms(); if (now > last_bwe_log_time_ + kBweLogIntervalMs) { - RTC_LOG(LS_INFO) << "Current BWE " << last_target_bps_; + RTC_LOG(LS_INFO) << "GCC: Current BWE " << last_target_bps_; last_bwe_log_time_ = now; } diff --git a/call/rtp_transport_controller_send.cc b/call/rtp_transport_controller_send.cc index ad9f0bfb0a..56c5e55ca1 100644 --- a/call/rtp_transport_controller_send.cc +++ b/call/rtp_transport_controller_send.cc @@ -15,8 +15,7 @@ #include "absl/strings/match.h" #include "absl/types/optional.h" -// Revision for enabling AlphaCC and disabling GCC -#include "api/transport/alpha_cc_factory.h" +#include "api/transport/goog_cc_factory.h" #include "api/transport/network_types.h" #include "api/units/data_rate.h" #include "api/units/time_delta.h" @@ -565,19 +564,6 @@ void RtpTransportControllerSend::OnRemoteNetworkEstimate( }); } -void RtpTransportControllerSend::OnApplicationPacket(const rtcp::App& app) { - if (app.sub_type() != kAppPacketSubType || app.name() != kAppPacketName) { - return; - } - const BweMessage bwe = *reinterpret_cast(app.data()); - task_queue_.PostTask([this, bwe]() { - RTC_DCHECK_RUN_ON(&task_queue_); - if (controller_) { - PostUpdates(controller_->OnReceiveBwe(bwe)); - } - }); -} - void RtpTransportControllerSend::MaybeCreateControllers() { RTC_DCHECK(!controller_); RTC_DCHECK(!control_handler_); diff --git a/call/rtp_transport_controller_send.h b/call/rtp_transport_controller_send.h index a7aa557ec0..e7310334cf 100644 --- a/call/rtp_transport_controller_send.h +++ b/call/rtp_transport_controller_send.h @@ -120,7 +120,6 @@ class RtpTransportControllerSend final // Implements TransportFeedbackObserver interface void OnAddPacket(const RtpPacketSendInfo& packet_info) override; void OnTransportFeedback(const rtcp::TransportFeedback& feedback) override; - void OnApplicationPacket(const rtcp::App& app) override; // Implements NetworkStateEstimateObserver interface void OnRemoteNetworkEstimate(NetworkStateEstimate estimate) override; diff --git a/dockers/Dockerfile.release b/dockers/Dockerfile.release index f33228ed3a..3b9dd17b03 100644 --- a/dockers/Dockerfile.release +++ b/dockers/Dockerfile.release @@ -3,8 +3,4 @@ FROM ubuntu:18.04 RUN apt-get update && apt-get install -y \ libx11-6 libgomp1 python3 -COPY lib /usr/lib/ - COPY bin /usr/bin/ - -COPY pylib /usr/lib/python3/dist-packages/ diff --git a/examples/BUILD.gn b/examples/BUILD.gn index bd002b77d4..c92e2698f9 100644 --- a/examples/BUILD.gn +++ b/examples/BUILD.gn @@ -52,7 +52,7 @@ group("examples") { ] if (current_os != "winuwp") { deps += [ ":peerconnection_client" ] - deps += [ ":peerconnection_serverless" ] + deps += [ ":peerconnection_challenge_client" ] } } @@ -720,98 +720,74 @@ if (is_linux || is_win) { ] } - rtc_executable("peerconnection_serverless") { + rtc_executable("peerconnection_challenge_client") { testonly = true sources = [ - "peerconnection/serverless/main.cc", - "peerconnection/serverless/logger.cc", - "peerconnection/serverless/logger.h", - "peerconnection/serverless/conductor.cc", - "peerconnection/serverless/conductor.h", - "peerconnection/serverless/defaults.cc", - "peerconnection/serverless/defaults.h", - "peerconnection/serverless/peer_connection_client.cc", - "peerconnection/serverless/peer_connection_client.h", + "peerconnection/challenge_client/main.cc", + "peerconnection/challenge_client/logger.cc", + "peerconnection/challenge_client/logger.h", + "peerconnection/challenge_client/conductor.cc", + "peerconnection/challenge_client/conductor.h", + "peerconnection/challenge_client/defaults.cc", + "peerconnection/challenge_client/defaults.h", + "peerconnection/challenge_client/peer_connection_client.cc", + "peerconnection/challenge_client/peer_connection_client.h", ] deps = [ + "../api/audio_codecs:builtin_audio_decoder_factory", + "../api/audio_codecs:builtin_audio_encoder_factory", "../api:audio_options_api", "../api:create_peerconnection_factory", "../api:libjingle_peerconnection_api", + "../api:media_stream_interface", "../api:scoped_refptr", "../api/audio:audio_mixer_api", "../api/audio_codecs:audio_codecs_api", - "../api/video:video_frame_i420", - "../api/video:video_rtp_headers", - "../api/video_codecs:video_codecs_api", - "../media:rtc_media_base", - "../p2p:rtc_p2p", - "../rtc_base:checks", - "../rtc_base/third_party/sigslot", - "../system_wrappers:field_trial", - "../test:field_trial", - "../test:platform_video_capturer", - "../test:test_support", - "//third_party/abseil-cpp/absl/memory", - "//third_party/abseil-cpp/absl/types:optional", - "../api:libjingle_peerconnection_api", - "../api/audio_codecs:builtin_audio_decoder_factory", - "../api/audio_codecs:builtin_audio_encoder_factory", "../api/video:video_frame", + "../api/video:video_frame_i420", "../api/video:video_rtp_headers", "../api/video_codecs:builtin_video_decoder_factory", "../api/video_codecs:builtin_video_encoder_factory", + "../api/video_codecs:video_codecs_api", "../media:rtc_audio_video", + "../media:rtc_media_base", "../modules/audio_device", "../modules/audio_processing", "../modules/audio_processing:api", "../modules/video_capture:video_capture_module", + "../p2p:rtc_p2p", "../pc:libjingle_peerconnection", "../pc:peerconnection", + "../rtc_base:checks", + "../rtc_base/third_party/sigslot", + # TODO: check if this is needed "../rtc_base", "../rtc_base:rtc_base_approved", "../rtc_base:rtc_json", - "../test:video_test_common", - "../test:test_support", - "../test:video_test_support", - "//third_party/libyuv", - ] - } - - if (is_win) { - rtc_executable("peerconnection_serverless_win_gui") { - testonly = true - sources = [ - "peerconnection/serverless/win/main.cc", - "peerconnection/serverless/win/main_wnd.cc", - "peerconnection/serverless/conductor.cc", - "peerconnection/serverless/conductor.h", - "peerconnection/serverless/defaults.cc", - "peerconnection/serverless/defaults.h", - "peerconnection/serverless/peer_connection_client.cc", - "peerconnection/serverless/peer_connection_client.h", - ] - - deps = [ - "../api:audio_options_api", - "../api:create_peerconnection_factory", - "../api:libjingle_peerconnection_api", - "../api:scoped_refptr", - "../api/audio:audio_mixer_api", - "../api/audio_codecs:audio_codecs_api", - "../api/video:video_frame_i420", - "../api/video:video_rtp_headers", - "../api/video_codecs:video_codecs_api", - "../media:rtc_media_base", - "../p2p:rtc_p2p", - "../rtc_base:checks", - "../rtc_base/third_party/sigslot", "../system_wrappers:field_trial", "../test:field_trial", "../test:platform_video_capturer", "../test:test_support", + "../test:video_test_common", + "../test:video_test_support", "//third_party/abseil-cpp/absl/memory", "//third_party/abseil-cpp/absl/types:optional", + "//third_party/libyuv", + ] + + if (is_win) { + sources += [ + "peerconnection/challenge_client/flag_defs.h", + "peerconnection/challenge_client/main.cc", + "peerconnection/challenge_client/main_wnd.cc", + "peerconnection/challenge_client/main_wnd.h", + ] + configs += [ "//build/config/win:windowed" ] + deps += [ "../media:rtc_media_base" ] + } + + deps += [ "../api:libjingle_peerconnection_api", "../api/audio_codecs:builtin_audio_decoder_factory", "../api/audio_codecs:builtin_audio_encoder_factory", @@ -830,13 +806,10 @@ if (is_linux || is_win) { "../rtc_base:rtc_base_approved", "../rtc_base:rtc_json", "../test:video_test_common", - "../test:test_support", - "../test:video_test_support", + "//third_party/abseil-cpp/absl/flags:flag", + "//third_party/abseil-cpp/absl/flags:parse", "//third_party/libyuv", ] - - configs += [ "//build/config/win:windowed" ] - } } rtc_executable("peerconnection_server") { diff --git a/examples/peerconnection/challenge_client/conductor.cc b/examples/peerconnection/challenge_client/conductor.cc new file mode 100644 index 0000000000..4db886e5a9 --- /dev/null +++ b/examples/peerconnection/challenge_client/conductor.cc @@ -0,0 +1,643 @@ +/* + * 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 "examples/peerconnection/challenge_client/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/task_queue/default_task_queue_factory.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 "examples/peerconnection/serverless/defaults.h" +#include "modules/audio_device/include/audio_device.h" +#include "modules/audio_device/include/test_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/frame_generator_capturer.h" +#include "api/test/create_frame_generator.h" +#include "test/vcm_capturer.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 FrameGeneratorTrackSource : public webrtc::VideoTrackSource { + public: + static rtc::scoped_refptr Create( + std::shared_ptr audio_started_) { + auto alphaCCConfig = webrtc::GetAlphaCCConfig(); + // Creat an FrameGenerator, responsible for reading yuv files + std::unique_ptr yuv_frame_generator( + webrtc::test::CreateFromYuvFileFrameGenerator( + std::vector{ + alphaCCConfig->video_file_path}, /* file_path */ + alphaCCConfig->video_width, /*video_width */ + alphaCCConfig->video_height, /*video_height*/ + 1 /*frame_repeat_count*/)); + + // Use FrameGenerator to periodically capture frames + std::unique_ptr capturer( + new webrtc::test::FrameGeneratorCapturer( + webrtc::Clock::GetRealTimeClock(), /* clock */ + std::move(yuv_frame_generator), /* frame_generator */ + alphaCCConfig->video_fps, /* target_fps*/ + *webrtc::CreateDefaultTaskQueueFactory())); /* task_queue_factory */ + + return new rtc::RefCountedObject( + std::move(capturer), audio_started_); + } + + protected: + explicit FrameGeneratorTrackSource( + std::unique_ptr capturer, + std::shared_ptr audio_started_) + : VideoTrackSource(/*remote=*/false), capturer_(std::move(capturer)) { + // Creat a thread that waits for the audio capturer thread + // to start + std::thread waiting_for_audio_started_([this, audio_started_]() { + auto alphaCCConfig = webrtc::GetAlphaCCConfig(); + // Only wait for audio to start when use audio file + if (alphaCCConfig->audio_source_option == + webrtc::AlphaCCConfig::AudioSourceOption::kAudioFile) { + audio_started_->Wait(rtc::Event::kForever); + } + if (capturer_ && capturer_->Init()) { + capturer_->Start(); + } + }); + // Detach() instead of Join(), for non-blocking + waiting_for_audio_started_.detach(); + } + + private: + rtc::VideoSourceInterface* source() override { + return capturer_.get(); + } + + std::unique_ptr capturer_; +}; + +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) + : loopback_(false), + client_(client), + main_wnd_(main_wnd), + alphacc_config_(webrtc::GetAlphaCCConfig()), + audio_started_(std::make_shared()) { + if (alphacc_config_->save_to_file) { + frame_writer_ = absl::make_unique( + alphacc_config_->video_output_path, alphacc_config_->video_output_width, + alphacc_config_->video_output_height, + alphacc_config_->video_output_fps); + } else { + frame_writer_ = nullptr; + } + client_->RegisterObserver(this); + main_wnd->RegisterObserver(this); +} + +Conductor::~Conductor() { + RTC_DCHECK(!peer_connection_); +} + + +void Conductor::Close() { + client_->SignOut(); + DeletePeerConnection(); +} + +bool Conductor::InitializePeerConnection() { + RTC_DCHECK(!peer_connection_factory_); + RTC_DCHECK(!peer_connection_); + + auto task_queue_factory = webrtc::CreateDefaultTaskQueueFactory(); + rtc::scoped_refptr audio_device_module = nullptr; + + using AudioSourceOption = webrtc::AlphaCCConfig::AudioSourceOption; + // Use audio file for audio input + if (alphacc_config_->audio_source_option == AudioSourceOption::kAudioFile) { + auto capturer = webrtc::TestAudioDeviceModule::CreateWavFileReader( + alphacc_config_->audio_file_path, true); + + std::unique_ptr renderer; + if (alphacc_config_->save_to_file) { + renderer = webrtc::TestAudioDeviceModule::CreateWavFileWriter( + alphacc_config_->audio_output_path, + capturer.get()->SamplingFrequency(), capturer.get()->NumChannels()); + } else { + renderer = webrtc::TestAudioDeviceModule::CreateDiscardRenderer( + 8000 /*sampling frequecy, unused*/, 2 /*num_channels, ununsed*/); + } + + audio_device_module = webrtc::TestAudioDeviceModule::Create( + task_queue_factory.get(), std::move(capturer), std::move(renderer), + audio_started_); + } else if (alphacc_config_->audio_source_option == + AudioSourceOption::kMicrophone) { + audio_device_module = nullptr; + } + + peer_connection_factory_ = webrtc::CreatePeerConnectionFactory( + nullptr /* network_thread */, nullptr /* worker_thread */, + nullptr /* signaling_thread */, audio_device_module /* 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(); + + // Start the timer for auto close. + if (alphacc_config_->conn_autoclose != kAutoCloseDisableValue) { + main_wnd_->StartAutoCloseTimer(alphacc_config_->conn_autoclose * 1000); + } + + 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; + loopback_ = false; +} + +// +// 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; + + const std::string msg = writer.write(jmessage); + client_->SendClientMessage(msg); +} + +// +// PeerConnectionClientObserver implementation. +// + +// void Conductor::OnSignedIn() { +// RTC_LOG(INFO) << __FUNCTION__; +// main_wnd_->SwitchToPeerList(client_->peers()); +// } + +// 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() { + RTC_LOG(INFO) << __FUNCTION__; + RTC_LOG(INFO) << "Our peer disconnected"; + main_wnd_->QueueUIThreadCallback(PEER_CONNECTION_CLOSED, NULL); +} + +// +// MainWndCallback implementation. +// + + +void Conductor::ConnectToPeer() { + if (peer_connection_.get()) { + main_wnd_->MessageBox( + "Error", "We only support connecting to one peer at a time", true); + return; + } + if (InitializePeerConnection()) { + 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; + using VideoSourceOption = webrtc::AlphaCCConfig::VideoSourceOption; + + switch (alphacc_config_->video_source_option) { + case VideoSourceOption::kVideoDisabled: + video_device = webrtc::FakeVideoTrackSource::Create(); + break; + case VideoSourceOption::kWebcam: + video_device = CapturerTrackSource::Create(); + break; + case VideoSourceOption::kVideoFile: + video_device = FrameGeneratorTrackSource::Create(audio_started_); + break; + default: + RTC_NOTREACHED(); + } + + if (video_device) { + 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(); + 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::OnFrameCallback(const webrtc::VideoFrame& video_frame) { + if (alphacc_config_->save_to_file) { + frame_writer_->WriteFrame(video_frame); + } +} + +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; + + const std::string msg = writer.write(jmessage); + client_->SendClientMessage(msg); +} + +void Conductor::OnFailure(webrtc::RTCError error) { + RTC_LOG(LERROR) << ToString(error.type()) << ": " << error.message(); +} + + +// +// PeerConnectionClientObserver implementation. +// +void Conductor::ParseMessage(const std::string& message) { + 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; + } + 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::OnGetMessage(const std::string& new_message) { + RTC_DCHECK(!new_message.empty()); + + if (!peer_connection_.get()) { + if (!InitializePeerConnection()) { + RTC_LOG(LS_ERROR) << "Failed to initialize our PeerConnection instance"; + client_->SignOut(); + return; + } + } + + // append new message to accumulate_message_ + accumulate_message_ += new_message; + // create newbuffer to store the value of accumulate_message_ + std::unique_ptr uniq_char(new char[0xffff]); + char* pbuffer = uniq_char.get(); + strcpy(pbuffer, accumulate_message_.data()); + // check if terminal symbol of message exists in msg and locate it + char* locate = strstr(pbuffer, messageTerminate); + if (locate != NULL) { + // empty the accumulate_message_ to prepare for the ParseMessage + accumulate_message_ = ""; + // split the message using terminal symbol + while (locate != NULL) { + // send the splited message to conductor + accumulate_message_.append(pbuffer, locate - pbuffer); + ParseMessage(accumulate_message_); + // finish to parse one messge, empty the accumulate_message_ + accumulate_message_ = ""; + // move pbuffer point to jump over terminal symbol + pbuffer = locate + strlen(messageTerminate); + // check if terminal symbol of message exists + locate = strstr(pbuffer, messageTerminate); + } + // if the end of message leaves some without terminal, append to + // accumulate_message_ + if (strlen(pbuffer) != 0) { + accumulate_message_.append(pbuffer); + } + } + // if no terminal in the accumulate_message_,just return and it will deal with + // in the next call +} \ No newline at end of file diff --git a/examples/peerconnection/challenge_client/conductor.h b/examples/peerconnection/challenge_client/conductor.h new file mode 100644 index 0000000000..573df7fd60 --- /dev/null +++ b/examples/peerconnection/challenge_client/conductor.h @@ -0,0 +1,121 @@ +/* + * 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/alphacc_config.h" +#include "api/media_stream_interface.h" +#include "api/peer_connection_interface.h" +#include "examples/peerconnection/challenge_client/main_wnd.h" +#include "examples/peerconnection/challenge_client/peer_connection_client.h" +#include "pc/test/fake_video_track_source.h" +#include "test/testsupport/frame_writer.h" +#include "test/testsupport/video_frame_writer.h" + +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 { + PEER_CONNECTION_CLOSED = 1, + NEW_TRACK_ADDED, + TRACK_REMOVED, + }; + + Conductor(PeerConnectionClient* client, MainWindow* main_wnd); + + protected: + ~Conductor(); + bool InitializePeerConnection(); + bool ReinitializePeerConnectionForLoopback(); + bool CreatePeerConnection(bool dtls); + void DeletePeerConnection(); + 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 OnGetMessage(const std::string& message) override; + + void OnPeerDisconnected() override; + + void ConnectToPeer() override; + + // + // MainWndCallback implementation. + // + + void UIThreadCallback(int msg_id, void* data) override; + + void Close() override; + + void OnFrameCallback(const webrtc::VideoFrame& video_frame) override; + + // CreateSessionDescriptionObserver implementation. + void OnSuccess(webrtc::SessionDescriptionInterface* desc) override; + void OnFailure(webrtc::RTCError error) override; + + protected: + void ParseMessage(const std::string& msg); + + bool loopback_; + rtc::scoped_refptr peer_connection_; + rtc::scoped_refptr + peer_connection_factory_; + PeerConnectionClient* client_; + MainWindow* main_wnd_; + std::deque pending_messages_; + const webrtc::AlphaCCConfig* alphacc_config_; + std::shared_ptr audio_started_; + std::string accumulate_message_; + std::string part_message_; + std::unique_ptr frame_writer_; +}; + +#endif // EXAMPLES_PEERCONNECTION_CLIENT_CONDUCTOR_H_ diff --git a/examples/peerconnection/challenge_client/corpus/BandwidthEstimator.py b/examples/peerconnection/challenge_client/corpus/BandwidthEstimator.py new file mode 100644 index 0000000000..9b30c979c6 --- /dev/null +++ b/examples/peerconnection/challenge_client/corpus/BandwidthEstimator.py @@ -0,0 +1,20 @@ + +class Estimator(object): + def report_states(self, stats: dict): + ''' + stats is a dict with the following items + { + "send_time_ms": uint, + "arrival_time_ms": uint, + "payload_type": int, + "sequence_number": uint, + "ssrc": int, + "padding_length": uint, + "header_length": uint, + "payload_size": uint + } + ''' + pass + + def get_estimated_bandwidth(self)->int: + return int(1e6) # 1Mbps diff --git a/examples/peerconnection/challenge_client/corpus/onnx-model.onnx b/examples/peerconnection/challenge_client/corpus/onnx-model.onnx new file mode 100644 index 0000000000..49f5101c27 Binary files /dev/null and b/examples/peerconnection/challenge_client/corpus/onnx-model.onnx differ diff --git a/examples/peerconnection/challenge_client/corpus/receiver.json b/examples/peerconnection/challenge_client/corpus/receiver.json new file mode 100644 index 0000000000..04e445a885 --- /dev/null +++ b/examples/peerconnection/challenge_client/corpus/receiver.json @@ -0,0 +1,57 @@ +{ + "serverless_connection": { + "autoclose": 20, + "sender": { + "enabled": false + }, + "receiver": { + "enabled": true, + "listening_ip": "0.0.0.0", + "listening_port": 8000 + } + }, + "bwe_feedback_duration": 200, + "onnx": { + "onnx_model_path": "onnx-model.onnx" + }, + "video_source": { + "video_disabled": { + "enabled": true + }, + "webcam": { + "enabled": false + }, + "video_file": { + "enabled": false, + "height": 240, + "width": 320, + "fps": 10, + "file_path": "testmedia/test.yuv" + } + }, + "audio_source": { + "microphone": { + "enabled": false + }, + "audio_file": { + "enabled": true, + "file_path": "testmedia/test.wav" + } + }, + "save_to_file": { + "enabled": true, + "audio": { + "file_path": "outaudio.wav" + }, + "video": { + "width": 320, + "height": 240, + "fps": 10, + "file_path": "outvideo.yuv" + } + }, + "logging": { + "enabled": true, + "log_output_path": "gcc-receiver.log" + } +} \ No newline at end of file diff --git a/examples/peerconnection/challenge_client/corpus/receiver_pyinfer.json b/examples/peerconnection/challenge_client/corpus/receiver_pyinfer.json new file mode 100644 index 0000000000..75e19cd8d9 --- /dev/null +++ b/examples/peerconnection/challenge_client/corpus/receiver_pyinfer.json @@ -0,0 +1,54 @@ +{ + "serverless_connection": { + "autoclose": 20, + "sender": { + "enabled": false + }, + "receiver": { + "enabled": true, + "listening_ip": "0.0.0.0", + "listening_port": 8000 + } + }, + "bwe_feedback_duration": 200, + "video_source": { + "video_disabled": { + "enabled": true + }, + "webcam": { + "enabled": false + }, + "video_file": { + "enabled": false, + "height": 240, + "width": 320, + "fps": 10, + "file_path": "testmedia/test.yuv" + } + }, + "audio_source": { + "microphone": { + "enabled": false + }, + "audio_file": { + "enabled": true, + "file_path": "testmedia/test.wav" + } + }, + "save_to_file": { + "enabled": true, + "audio": { + "file_path": "outaudio.wav" + }, + "video": { + "width": 320, + "height": 240, + "fps": 10, + "file_path": "outvideo.yuv" + } + }, + "logging": { + "enabled": true, + "log_output_path": "webrtc.log" + } +} \ No newline at end of file diff --git a/examples/peerconnection/challenge_client/corpus/sender.json b/examples/peerconnection/challenge_client/corpus/sender.json new file mode 100644 index 0000000000..514e42963e --- /dev/null +++ b/examples/peerconnection/challenge_client/corpus/sender.json @@ -0,0 +1,48 @@ +{ + "serverless_connection": { + "autoclose": 20, + "sender": { + "enabled": true, + "dest_ip": "0.0.0.0", + "dest_port": 8000 + }, + "receiver": { + "enabled": false + } + }, + "bwe_feedback_duration": 200, + "onnx": { + "onnx_model_path": "onnx-model.onnx" + }, + "video_source": { + "video_disabled": { + "enabled": false + }, + "webcam": { + "enabled": false + }, + "video_file": { + "enabled": true, + "height": 240, + "width": 320, + "fps": 10, + "file_path": "testmedia/test.yuv" + } + }, + "audio_source": { + "microphone": { + "enabled": false + }, + "audio_file": { + "enabled": true, + "file_path": "testmedia/test.wav" + } + }, + "save_to_file": { + "enabled": false + }, + "logging": { + "enabled": true, + "log_output_path": "gcc-sender.log" + } +} \ No newline at end of file diff --git a/examples/peerconnection/challenge_client/corpus/sender_pyinfer.json b/examples/peerconnection/challenge_client/corpus/sender_pyinfer.json new file mode 100644 index 0000000000..3a1ccc7436 --- /dev/null +++ b/examples/peerconnection/challenge_client/corpus/sender_pyinfer.json @@ -0,0 +1,44 @@ +{ + "serverless_connection": { + "autoclose": 20, + "sender": { + "enabled": true, + "dest_ip": "0.0.0.0", + "dest_port": 8000 + }, + "receiver": { + "enabled": false + } + }, + "bwe_feedback_duration": 200, + "video_source": { + "video_disabled": { + "enabled": false + }, + "webcam": { + "enabled": false + }, + "video_file": { + "enabled": true, + "height": 240, + "width": 320, + "fps": 10, + "file_path": "testmedia/test.yuv" + } + }, + "audio_source": { + "microphone": { + "enabled": false + }, + "audio_file": { + "enabled": true, + "file_path": "testmedia/test.wav" + } + }, + "save_to_file": { + "enabled": false + }, + "logging": { + "enabled": false + } +} \ No newline at end of file diff --git a/examples/peerconnection/challenge_client/corpus/testmedia/test.wav b/examples/peerconnection/challenge_client/corpus/testmedia/test.wav new file mode 100644 index 0000000000..04ff11d0d8 Binary files /dev/null and b/examples/peerconnection/challenge_client/corpus/testmedia/test.wav differ diff --git a/examples/peerconnection/challenge_client/corpus/testmedia/test.yuv b/examples/peerconnection/challenge_client/corpus/testmedia/test.yuv new file mode 100644 index 0000000000..0821ea9ddc --- /dev/null +++ b/examples/peerconnection/challenge_client/corpus/testmedia/test.yuv @@ -0,0 +1 @@ +ʾÿƿúĽƿqýƿ½ôúwþĽĽƿýļþþļƿýýýƿɿþþļžļļſûzýõÿt½ôļtƿþĸøƾþxžøøyǾȿþƼ÷wը˾Ⱦƾ¾tJ=ôǽýýt̓[JJžžú÷vF;֦V2ƿǾľþƽt]pɑD#yýĽžþƽ·rc%%qܳt6^Ŀȹþú¾þr۰c-+eD&LƿȷĿžúǺsY%'>M02Ⱦ¾ʿýýùúnܺfH:2" %#9Źž¼ƿǽýh˛_<&"!,& (CpȾþþ÷ûb֓J*# (("!,=O\nƿûøúɿ~]\&+%!%%%"$ ##2@D^ǿſvaP&+#6:2,&!"$![ž½z|jܱU130'/MPC>>>/)# #9PbĿž÷wΖUFF5'&?VVJCGLG;-%# ##%%,&'Wƿýõ÷hGF>*#&#8IPNC>9>B>>/.+)#&%#%#8Zxžúúþȼȿy҉=05, #&%8<:@D<3023:@;85,%%#%!#),7g½½úƼķqݪO (!,,&!-85+)&+8<7498-%((!#*+)#0z¾û½tj% # %%##298,,761325,%"*+("!# #6^ɽƿƿzxГ3 ( ##%!"$ #/74.05232@C>9%#%,&'+% #2^ſúzsܮP% ##"$&+# #& "*,2:;8-/7DHHH:+%(('+%%$Zÿú·kr),?6 ( !#'+%&%%%#0FF<36GLB>BMD1%%&%%#OʾúƼmՋ1D^J-%%,&'%%(('!# & " (J[K;BMPC58IUF2!$(('!"4tʾnܺSCVOFF<*#,2-%(&&+# (('!*#:Ze^_^PJC=OVOF1#%),(",pƽnЃ-!7DHO\\J2"+0/)%#%,%%(&&,2''FgzzhTPPPUQIJD5'&%%%(&-nſ½ſ·qJ+8OdcJ=82,&'%%%##)#%*+(%,MrtaWW\_O=DPN<.%# "$&*#/n¾ž¾úýt(,<:2,(8Pbbd]B/)%"*+(%%,+% 8gvf_^\D05BMPI<."!"*%+eúkΖL$-DH4$(CUdtvW=52%%)*&%)+,'-`|yjbUC725BTTLC7%! ! '%!*c¿ſ¸}gӡT((>M?&*AQPZhgRDH;%%%&%!'+*& ,ThtzmVPZK=HY][V_\D&'%'+,-+*=Pf|zsw}|raN=5/8RgbU8%!((25+*K¿vڰT"-Qwxfcjb^[_\SDLVP:$")%./.25;JYdhnpr}|sg[K=666@S\ZJ3%&%&%*&*%&V¿¼·۾\%!"Bc|zrw}|m_\^SD@DPN6"%%,-2=<>MRPNQZbbbkeVKLV[YMGJILOB2%")%&%%Oº´ȿq&$+=^nprrpddca^a^UKB=GJH;DPOJYtnWVhtys\J>8>QZJ3"#'%+%"aľɽÓǂ5*@bxqfjnaF8>N\`\RJIB28D>8-#'16666@MG88883%,-216:=<>/8=DPOQKL\`dpW3%"2Q`M7+%%!""g¿ſ¸ʄD>ZbF.*5GTTTU[YOJMMP>:=*".29<72,-29<4+*2=>=<>HH>=>2+0%!""*5:=72,,583+**1652728DH=:LV>*,BNC52?NSTL?33,'+%&% !%,5;3.*/852,'+,-2122:=33JnnO4!*Qw}b8 OJ8272,,(#+16:882,5?;8832,-8<9-+88>HJ^zxZJSTj¿ɽֹxSD883(3952,29CD6662,33,+%%! '*,)*22:06>=7283++,8>22AJD2.:VmvytnZCFOP]tv];%?þԮrYM82,*5:1'%.:C>,'&%%+,.*& #'&%%+,5?<4883/.3++.2988ISL9-3AJaswnfcO42BcyscH((`Ƽ¼ſΚc\V=5/'+,+%./395,'&&%%+%&% "#%)+)*=PH;216161116:8>CFJIB?DHQZWVaszV66GaszxfD&=ƾÿź۸tQZ]@0%! '$+1611165.*/+%%!$%!""*+,.*:OG827165.666:=@D=58>Uacje][nhU[_b[SQPI91D^þ؛QDPO82%'125+(+332,-,'&&")%& !%+1,->=7883(0,2,3666:616Mjz}ysww}|kejnthP5+1666%):]ƽ¿y?NM7+*"-51'%!""*+.*!','& !"+72'+3372,,(+,.*0,2258Dg~w^QDPfv~pU1"%+,.*0-5Z¿ü¿Øд`Aca>*(##BTD-#""%+)$! '$%0/.+,)*211,'+,25811,8>VzmJ;.*ClnO,++1,,'&3hþºķ“̟TLna;%!'Hjb?&"%! "$-5-+,53%%./.2.*/+16:88DPasxV6%):]zxfS>*")%&+,.*+,)Dxú½¿¿ſŃD>]S4!!AjhG*$%!"" !;U*,66,'+,2666.*(3>C@S\c\lkN61PxrY?3+**1.*(2+%./38Jrñ¼h06>D2./ThG"%!%&%%";طZ*.2.)*218>=5/',:;;J]SF\}raYdwK4!!&,11,%)+)*5DH:2?h̾ú¸ٱP"8>=7Jfc9%&&,(##$!*O^%&%04++*29<706:=7AJ>8Lnn[1"%+/.+-5:=@ILM?*&2Pxƿƺա?*DURVkw\4!!&'12)"%Aze#"..25,'+,2622:8>=70,DhrB'%%!'+,+:DFOWVMG<-#'=h½Ŀњ>*Ghz}tF%"%'12)@V',-21,'+,)**-+8=585Gr|P5'%%)&,(0,9CDJYYMD>2&%%+8RqþȽϚFDl[*$"%%,'! +ߝ>*,),-2((*+,.$+1332?NttH5,-+%%+,.*08>D=@9<7=<>/,-21,)->Vt¿ſñ];*"(##$ -pߪF",-+)*21-+%)+0,(->QtW3%./.(-88>16:8>?D?DIA=<72,527*&2<4*,;^Ƽº¼ѻhP>*"%)+)/xv'12).*/+1*,),,-2Bs];",'+,26=<>MVRJHHD>D>?;82,364448>8,'&&8DgȳaZQ2!2C:+%P?#+121++02,%(,,3DdW8*%-5/2:>?<8CT\RCAG>89=>?80,36899=>?GD;,%0Jkͺk7+OgF%#^p*&021+/20,%'+1A`}rZ=,,1=DCADCC:38CF>4687257268738;899FINPKD;7+# !&0C\tļ©ƹw21YmH"\ڜ8*21253+(,,,)2WyxT>8578CFMNF>38621<>8:>:3122129=>9888P^[TJ@:>:72(%'# -5>Pmļ½÷°àʛJ(Wg;$dٽS&0212/%(:>.+?kz\:+.=DJHJMI=2,/7;8:FIJ@:1225/46=>212A`l^QF>329=>9578,,)$!,B`}ƹʽ˫h^ȡR%\e3+cՁ+/20/+(5DC8>^tkK4+0ISKDHJ;3869DJ@:KUM>4/207;8212/,3B`_OGD@:18CJ@:981+/1+/'+6C\½̾ГLD;Lx/%8ee=[S0212+(5AGMfy`D+(DUMAGMC-)28>V]D8CF>851+/146,,)7;8AGM>>>@:57ALD9892,4=>2'+(%'8JYm÷ʾá֟JDyyI -VmyePy !.+/+*%:Sfvzxs\>4LneK4726)&+6CY[?.+21<>>5//20/+18>@:5=DJA=>>?8KD989212A@:18;3'%'#(8RtǾ½ƾş̵c3SfY.3lzRC|ז2&,3612Ozy]]M>VjD1+&+/20/FRJ6*%,3=DJA9815//021:32:QTD8@DCCJH;8:46=DC88>J@72/+*'%/?Vv»ûýť|%-PO&%JbV/%hݲS ,:>BIp}f\>4M_7+8>@DA=;HJ;62+18BIC:822531+&,36,:TVH>?GCADD;7:8CFH>>?GPK989=>2',,)7HVmͿüºşХJ68!&,%"Aw+->DL_rcVA,:q^=8Rrzn`TJA=;;31229=D>4464+-.+),3.=LQF>?G>?8;8BCALQFDA@DLD9;>?>?8;21++-5IfǾžſǿ½ÝÇ4 *TҔ=(6C_zv`TSK4=l~geg@:DA@57/2>KD3869+121++127;I=:>B<>:>BPPHJKD@:29DP^T>>?>=89=>951225/25398155/%6K[g}ÿýù÷ȷz9>O'K~_OP[wʽnnaP>851:FH>>>2+183++68A=;;JH>>>;31IZZG464622:A=;/207212/,40,,3.)&%'029Qsþ½ºýû¼ʾļ½ϽS,+qݱK7pnR[glʺhLD[wdC:=>>DA@?B622@:1.=BICDAPPMID;78FIJ5//72/,3BC:-.021/+*-.0%(.1+/'(% !.8JbͿɽùýýÿȿû½ʾɬ}TJPtӯKFxjDA`ϲql^F8Cg}hQg\J@:9>?GH>121=KUWP>,,8CF>9=>DUQKG^tgMI`]D212/4646/+/2*%$*20,,&+/212/,*,3610,,&!&2912DV^akzfT\tVH?JYY[J5/49=DJHHBCR[PP\h]MZncD989=?802,/-.)&%/20/+%(..122,,),3.31/(%'+(%'#()>\½ƹ½ýڷ\7GDMmtejhVHJYb]MLQbww^QOSTJP<29BCJOSTVPHR[`d[LD[nt\B;>DA@?212/43++,3.),,),-.)122,++-/+*-,/+*-,+(%)2@M^zûº¿ýåָf;Hhsrge^a[TT\ZQ@5=[qpWPINJ@>85?KUWSTVcaUUbrzbGDa~xT;8:B;:8721<>;3'++-/021/21++021/221+%'031/(+021++038Og¾ýýƥоVcrc\POVNPRRRYQF>4=OgmyznSDLNL>9=DJUbZQMTkzfa_gj`TS\e^J;8:42,/-29=>>7+++-883157/021/2-5>82*%,021/+(5,%%'#+0ALTdžŽýƾ¹²¾»åŽl}jYQBCRIINRRJHDA@?DOZhvn\P]eV>>KUW^a[RR_z~gYQUUZZOB;:<8/+*-,&0289972/,29B72672/,22129>?8;2,,12,/-,%%%'# /?DAJvĻÿûǾƹºɽąhyjrzxj\PHJKPOVVH>DA@KD9>Smytpt}w^INY[a_\P]lsvh]a_a_VPA,-54+%'82*029<8/.+2>?8;>?8.%(.5//068AC:=46,0,,&%" !$!#+/?LD9Kļ½ǿûûֺteymhgehcVOGKNPRYQBJHDHJFR`sj_a_\Y[aaflstqlwxj\S62:>BPGV^K47?KC:-1=K<>HJFB;,,)73++,,3BC=846,0++'%%'#()+02NW8"Aļ²ý»þͿװwnvh\]eaUOLJHDHUUPOF>>DLT\jy~sne\ZZjysehcak~mTJT\yxnznDCCJF>32>?>DLNLLJ?772/,-)%,3==80,,&,,),-+(%)1229VP,Bù½ƽǾ¼ƾ¹Ъxvb]^[UMHJFB=DJUUPA729=>DPav~xtptptetnleelʷvz~nTVP<8/8992HJKPSTOSOB3++62,446=3++%(..1)&++'(29*,@M@#4ʽļþ½ļýƾ¹ùϝhgztea_\YNFCKQKCKQWUH82246,08CS]eahnnwtfvnep|xjqlYQUJ5/2>@:5KUQZZTJJHHB9=>212/432,/-,/+*-,/212/,*)2MYG+:¾ƾ»ýýȽý”ʂISdUUZOGKCKQWRJP^a_J6,3.3-.)8CJNP_gp|ahtpyj[T[TMNJ@:68ACNPRYW_g^QBCJHJVH/+*-989=46=3.18CF>9;3'++0I^fT6*?ļȿǾƾǿƾ¿ƾȡںlB[hTVP<@?BUbh]ST_gdU:+.++'(.=LV]\jyszveVv~~wnnng`]VPVPVOLKIG>:>BFIVcg`SKT\jVHCKQWcJ5&#+BIPA38DA@KR[PJH;<2'+2:A\e^8\þǾ½ýƝֱkYsyncZQQF>Kepn`Y[\e^P>)$*0=MY[_m~tlkgtpdcmhZG@?@DLM@:>K[_]]D;7,3IZhQ8FWUV]\RC80822.11+/HVdU,3¾û½ľӽ۵q]rwrtpj`\RLJZcca_a^a[RB,"(2:FP[crne}wjrhgh]S<>:>DJH;<8J[_]\ckVDHRUI83666D`ee]JDJ=:J`lYCPdhllYI8,*.10482)+8H^^@+G¿ǼȿкÁV\tzpknkd`]\WVV\gllg^WVH3%&04<>CPbqzzkds}zk\jl`VK?8?JIHIHAetZCKS\]J>:::DZ^d`NH^nkP5172,1972,///CUeeL.)W¿ÿÿֵxZgtjTBG[ggt~vxsmcUIGC2'!"'2=CFADIU_lzn`wnWgyb[]WVTMIAHRUOMLC@H^nwzswznSF8+@n~dMLJOQ@6604<[]_cUAH\W>10410483)%*.28H`r\<)+gƿƿÿϕkntpcUUenknxxne]YPJI@6)% "'28?FGNYblskcr|reteQKMLCFA9=LFGNNHCK^^]jvkntdUC7+'ChnWKSUUIG@D2/>QZV`]POWK66D41=C:,*%*,148J[VNH%*yʾĻǽ¾ԫv]dha^dhanknkswtjTSLNQD;4* "'6=CAHRjl`]ckrtrtz}jULFB97517A<>JOQGCRUOgsmk^QOD2$)Mvx]JL\cVUUK83?JS\l`SWgL.>NH66D@6),*,14.1D\c[08ƿǼýÉL<8:Jena^b[[]kn_UUKOMA<9-"'-;DJ;4NhbWVfkh^^lz}tzv]NC@83)566D@<>HID@KSZg~x_UTB1%,PreUIG_c\^^PDHOMKSZYS\lV>NYSK?>>>41104?2/>bleL$\ÿľ¾ŷ¿ľ»իa,/aybYZ\WV\]TMIJHI=:;4.12=JH72@SWP\hj_UThxsmmcPJ=4141=>CHD@::=CAJZgltt]]V>1-2PdODJO^^]YZ\Z^^QB=LMLYbVV\eeaSF@D;4.04B=9NYZ^^D(}ÿŷǾѻ˟M(`jUV`ZPSRLFBA<>>:;=CA::JH72>C?J]d_\]fpvxqeWKG>88:=:;AC@@=:;AGNZ^dl^QOJ;4.9WgIHVb[[a^bckk]J>BMLJKMDRr|yhTM\W>2.'*36:JH=C`]7>¿¿Ƚſȿ̃4[nQKRLD@D@:;==:;;4>JN=:HD10488?QTVenhg`en`TVJD>>@D;=:;;>C?=CHIS\]jVIHA<2/>\hJO^na_lj_Z^^OD=LMNQKQKRjzzphnqZ>.)$).12A<4=^nWaÿÿ¿ſşܿz2Q|R<86//666>CH;==;=JI@=CA.)2>B=?DRmk^VUck[VTMD@D>?8>?83?DHACCFJO^nlSF@9752H^ZPNYnkntkVD?DBGHISNQ^dcZ^kntlP;4%'288872@NY_\A +¿ſž¾̿zA\Z51* &048JNHC=CHK?>=CHA-"5JI@=>QleQWgqeVNPDHA@=@DIHA<<>H?>>@@DRmz_N=2/.1;PPJOQVeqzs\IA>CHRUOKMR[gcagtnahneQF8,149=;4.9PODR`:K¿ÿ¾ÿýǼş̤W83)%'.12AC@HL\aSA56BB=--@LJD>D\mcYZnp\IHIDFNQKQTMIJIHA<<>>472@\kPB2.///@SMDRV`ZqzxbNHM[]_IAJOQVagcUUMDDR`eWG6/.1;FA98NH6=^DsľľǾ¿¿ú˦ӟmYLF8,*%)%*37=L`rvcB27=A<49NLF8BM[mkZ^ktdULFB9H^aSSLNHISF88:=36//Nza<2+/729BGCBG[hnqx}mckrhQ@=LTMN^dcM<236Kck[F8,*.:JVKBGH97@=#;þǾĿþúľΩz;4>>1-$)!"#+/>QlwzcB,1>JD@D>G>;=CPbeWSlzsk^QB<>blVKBLFGPD72972,51Kv]/$).7=<><><>ZtpllznSA<>PODGS\YS?2;832=FYZMDDA<4?8gžǾȿȥ۵R(--%"'-?Q`lpW8&%7NQKB=-562.7N_c\glhlhg\RU\h^JDDBBA<4//66042>aS1%%*,+'2=<8@c~vjerycP>19BLMLJKNHM<2:JVOD=?DIA72@NWVTLF=4@D';ÿÿÿýȟܝ9#+* " ""'-:J`rmQ27VbP>,*,:,%*9N_\`]Z^^fkh\RW[J;;==4*,*,04224=RD2)()% &::DQksl`S]tzfG666>@=@LTVJIA>51KQKRPOJHH3,1>DJPOD>:;A-"¾Ǿžþ¿ûž։/&,*,01%%!"'*>_seD+"'@YZM:,*372/.3DRRU\WVT]jgVFGJ=8:=3/*.+'+,104BH=+/+'%$#%,;JZ^d\WUU`eZC66D@<28GSQKRFA98=LM`eZFAD:,%,149FA98=4*Zſþ¿ûĥy,%*,+>JD/$#% "2KSF+"-;IUTVJ>:;3+*37?8>CPUNJOcfV>8824=:;,*,01,10'*36/%*&%&,--5JPOJU_d`WVTFAD:A<>>C@@;456837=NQVNDHOB+!'*+8FA/*'*+>ƿŽžļÿĩl,'236PUC,*%$#$)36'!%9BG>:;AC@51" ,997.;JG>;=LTR<3+/6=83)%193+/0%$)++*,*!"',*,0AHKSZhlh`]PLF8B9=ACC9---%,149=CABG[eeL,#%%,;D8+!"#107ǾþƿſǼĬڷV'!%.1;LF8%'.'!%$#"2>=:;,'288/* ""-224=D@:;,1>D>1-.1210+&0410+&&%,14.%''*+)+8HHR^mk^gl_NJ;;729=CA:2.'+/+66>@9BLjzlS4*%$)482'!"#78'2ÿ¿ž̲Ҕ8"'*383)% +(# "28A<99=3%%*,+%''+/+6::DD@:* ,96/.+*,+*,+&+,)%*% "'0/*'%$)&04DRHR^mb[d`WDB>:;A8:=>:2.'+8A?>>>:D\mmcYC7&%)+++' +<8,9ƿȿĿǾÿþѸt#!"#&+()&"$#"8HRUUK=CHA/$#% %,10+,6=8=4'!%$)+++&%),*%)%''+$#$&+(,*,+*%*,9NUNJYbZP[VH=>C?=>:;A897.,9FNOD=::O\]\]\P;-"&+(%',14.<ÿþþֺ̿]"$*.%''$#(-H^aneTBFTM5 ""-)+++&(.12,*%$" "'-%''$()&$)!(#"$*)%*-22--%,6HROMR[SLNHD@:88<><>=:66524DRVC78HSWPGUZK82)(* &&(-* 1ǾſžǾзD (.0%#%%"#28(#363+=TVJ51*+'" "&+(,'!%$)()&$*)% &%!"#&&%))"'*%$)&51*+/6=CHZ^TMIJ=:641410+0%+2.7>C?=>D;:DMLC=CA:2&+(%)% &,-%2ÿμK%*,+% !"':OUN:% ""+6652+'"%$)&)++%$)&)&%))"'%$)&$#$&%%&%))"'%%''2.'+8=:@ScaPJMD::246=8=2/.)++/6IPODG@=@G>;=?8>0/&%))"" &04`ſþž¿ӽn'+24*%%$$ "ChvV51*+/$ '*3104% '*%" ,)%*%$()&$*+! ((#%%"*.%$#$&%%&%''+--.;:2=WgTHIJB=997528A?411,*,0ANQKIHA?>7,1>4*,-(#$" "#29-!>þÿռڤD +n~dP;- (.)$ ((#%$)&$"'-%%*&!"#&&)%*%$(%''$#%%"*+*2/.=TYLDHOBJD>8:41=6652++*,+9=AJOQ=:6+,)* &&%$$ !"#&!"1DB(0¾ÿϸl'%*,+%$#$:\hR3%3bԿG&%! "'% ' " ((#+'"%)+&%))"'%%)% %*,+%+*,C[VNPKSUPD8:=38104/*'%*.++,6DRB2751*+/$$#$&%%& "2)+GN3aſǿþ̴ۚ=&+(%!"#&,G\F++HˍG% "'%%$)&$"&" "&% ' '!%$)($#$&%%!"',+,)#2Pd^^VQ`_N?22883635+!#+2./'*>D;)+84*,-2'%$)&$"%$)4%*9IA%>̿ſŽ˱_#+2$#$&%%&=:-"7eܬe5+.)$%$)&$"%%''$(%$$ !!"#!"',+&%))""%*&!&:`la^bYmkI8,)++/4=7,(),1>=4'19A<.)+/+)+0%+'*%"'*+)()&.;B2,¿αӅ0+,)##!'8?69nǟe5 "'0.)+()&$*&%))"'%%)#%%"$!'+10+03% "'&+VqeVYZ\hbC7+--.6652.1;=:;;>10;==4()&.%$)&)&+,)#)%*%$(19A4 þ¿þαܩF&10+#%+87'&UzG5333,&&&%+'%%+'%%',&'%('%(%.===PN>10,&9n}gROSZ[O=76228=70+28DD<;=762:;=6,&+)%%%%%++)('%%%!&8B<,dŽº˷\!453,&$+8.$)nаfJ2)%&&%('%#('%#%%'+),,,/2;K\m|xkbR;!%L~rbRVeVD<22++8=-*-8BD<:;A@=7653,+)(*&&%('%(%$ %+'% %,62(JɽŽľǾĻμЂ+2?7'&#((0+_ѾmC+ &&% %,+)(*),,/0226>NfsN)3^|}xq]btcOD9-**22++4@F>45:@=;4.,,/0('%#%%%'+&&% ',&&% ),+#5üÿÿÿüݷV;KG5$ &,,/0(GԺcI=765$ %+'++-*02:;2;JWgt~jD*?gkbjqgkypbR>,&+,,/7AII=2269:2)%%%*&)%&"#(($++#%%%*%% %+0+$,ľüſüŽǽÿͻҕD*5PVB-&8===8#-ʫ|nV<' %&&,,/7=HD<:Zpmdkz|xk>HaWOS_gkpmfU8#),+-8HNNIC=7010,%% $%%%!"#.,%+'+%%"#%%"BU8, +¾ƿľ¿½ǽǽp9:JJ=0%+BJ872!JοtS:*&)#((,=HB4.,/2.,/JOSUV^dkvxkb\I7K\W`h`UI=&&19:=HLKB<5,,/0%%%!"!&&%  %,++)(%%'+&/h&nÿÿƾÿȲ۹R#33!"%&,+ %TϾ~cO=0%&,%%'/HVYG5$*&)++"#%#!&(' #%+'+%%%%!"!Gz)`ýüÿŮ_&&%C=#(0%%?ƴcO=0+$$ '2;>15PfU4@``hg\J2&,=BCIPP=0%-*0*&&+)#!#((,2+ &&n2WƼǽľºɰh&-vM*&-Ͻ}xpmf`ZTI=AgmCL\^d]M9%%%;=@=;A@1(0%,&+""#.('%#%%',.,%"#0:R޽ǽϴv' +ř8$+%S˻y[Y\YYTC2%+0;453226+ $ '!&&1,&%%%*%'% W@Mʾ¼ƴԕ8%% tt/*?3-ոdUPNVL6,'287++-210,*&"#"#%##(($$ '&&,)%&" "#2JHƾ¾ƽɰۼ["#%##\F'>H3Y澄eODNNA&+43.,/,,/*&)#&!$&&% '%%'+&%+'% !#!#_RBľ¿ľ¾ͻ׉&+8.Hk,6PN*+wdULKF1,60+2++-'%"#%##$ '&$ '&$%%'$&*-("#%##$1\Bÿÿ¾ÿܰP)3.5I,P[?'Jۜ^dfUD3'&,+,,/**-10,*+)(*)'%(%$%%%!"'&1,2+%%%!"_d=ľ¿ľӐ/$&&~;KYG9$xӖ`h`K8*!#++-2-*0;=@70+$$('%'%(#"#"")387+'%" 3r!BǼľƾľ\$%%m[OPG5$9ӞpbO70*&"+4376>AIJD9,&%#(($$%%%!"%4>A9-)%&" %_ۃ)Gü½ؓ7-*0Z·ODN0+FѠncI/*('%++-2-CUPJJC2%%%!"'&%%%!"'&%&8HVUC2+)#Cޏ.BſĻ\-8HMYGJ,&'N۪hO=22+&&,10,*I]UCF>.$$+22++&%%"&,MdshO7'&!pޖ2BŽĻø؇*%:@:k؅VL,&Vn8*45/*(/2..=K\L685,&+5@=;0+ %$ +B`rtcD9);-=ĻĻG28+KฃK/#!#&gч0),,585,+8BUVB.,/,16>HD;,&%#%4>[hf]M5,)%&]c1ľǽÿr!,60>ИU8,('%+$&\<#(4@>857ANN8*&&3=HBJJ:*%%  2HVbh`VB3.+#%&ݛ9d¾ºùܟ7)3==cۤbJ=8-8B<"c\$+=HB;=@D<2&&,1=BB44nʋ_LKF<,%CV 2HD;62(-**+.3345/5,)%$ +BJTdka\J=7,&'<߰`' +ǾøF8C2%;ߞ?'7A0%*&?w%FTI821+)(%+.45/53.+%%  ?PNN]b\YH:2++" gʓL!xľľ\)CI5\H%+0;03mkߢD 2NTI810,%#(($,,/**)%!$:RVLNY\VL@6,'"Fɂ2Pľ¿¾z8CN> 0ݔ;'>@6!&GL3@ȃHDLD982,&+"$ '&$%*&)#!#6R_QJOSUJ82)%' sݴY%7ƿPBKB)DכI5@/#RݱjSF82,2+%%%%!"'&%&&%!4K\RA9A@?7-** %w֢M!,&!nDPN6,%zڱmC.,%21(%*ɂP6,'/<2)%+)#%%%!"' %G_Y=87221+('%#%Vx3$P͊GWgJ8#>ΚN!!,;45P֝F!$5@70*1(%"#%##$%%%*VeVD;4,&+,)%&"%%7՟G!x֝FWs]@-^ݡI&7A85%+F->F8283.%%  $ '&$0JOOD>4.+2110,%#%gg5@F!.,BܩJ=Y\:*9֘41>A33,\l&&3D>1570*%%%!"'&%&>WTC>A5,).37633,(' <Ъc]ve2?S:",,-ݼ[%.=0%!0z%!85,45Jۯ[1;B6222++&('%#%%#gO/*(&&3211021(%*B~R_22V62ѹy'&,%4y+#.=4IڡRA=7654.%%'+#!#ez, ('*334.,/3.%$ zbRʇ$.rbWժ\#%%8~;#30RЎQ:8555+'((("PP%/.2672,,2,%! PƇѥ>#hͫ^%)"3|ȏO+!)"]c4855+,,+)'(#$)"=8#*.,,67.,.,.#$%thYجQ/3:.,\ڱl/ gn,,6;481,,+'(#%!*|֋1*'(285+'+)%!#9n)bҕVDI@-DÊD)~ێ8%55032+'+,," R[ &'(232%)''(#%LԝP$׾zPJG8=bfwהB(O['(2'(2'%)/.$#$5B!'(#2+$),,+'  ra4ȇP?9=DD26mϩӈ2&')'"%)(("&'"%bԁ) &'.,! )''%!#,JDΔV95BOD2(JܠB( &&'"-+'%!'(#4c%!'&'"-*'%!#=ЃC/yڠV28NVA2%5[$%*'+1,)''%%! cެG! )),,+')" P٫T#@ګT,?dd='kv%%)()''%%,,"#$6֖5 $),/./.$"$)b巄ܷc4_J*Pܜ4#*&'"%)/+#$]y*$),+,,+' #$$sÄZZ-=Z%'(#%+)(("&%):s'$),+'(#%$yб|Q*5ב0%)(%!'&'(#%+%#`l(!)+)%)''  2Ұq\S63h#$"$&')*'%"0c$&')*%!##=vJ9%! 8ÿP%#$((+),VV$)'(#$W֟Y0%) Aÿ<!$&')*.$-B $#$ $# &)n)&'!YĿľĿЇ0$)'! ))%Qؗ8!#%!#Mܓ8"$&?Ŀųn&%) $.,*|[%!4rCAaʳÿ`! )#%)32%@/$4ßĿûN!$)'!!)481/.%rf9Ŀؗ1%.2-#-2+$%/#==WÿýV095*'+%! +85%!pn )ÿފ4*18=@-! 2DIF/92Mÿ`",?VD&*;HJG1ZL!.Ƴڕ9%6PU:,9585+Fm  [ýİb:BOB0%,?:.'(#M1+Ⱦ˳ýʊPB>128=D>,#$f;NõݟN,,"18=><2%5A)~ĿûةB!)+,,03.$dCPԽ˻ܬG!.8- &&%!47#w߰K%/:."$&+~t%/ɾJ%.,!! )"qVV־ĽķI((+%!Sܸ?+¼¼J!$%!4т*JAsH!yב.F܏%! Px*[ÿm+%#2>#6ҽҋ='  #qԃ$aƳK%""LN!:ʽ޽a&! ) \v6! SJ!&ý="$=r'@\$" ,ܘ4 #hþ׾v2  &&VK7Վ@ -.$8kVG$ 4++tȂ1$Y%%%%&,-&LΥ`,!(Dq5,'%*0:3!<|[:!#"n¹ȁOKN;(*8@0#f՟P !&ŵ¹ʹñǾ}ý¿ؿP[v_D1,85'=9=ʹ°ƽʼ¹˹y}zt}z¾ý¾¾ýƹ~dWVb`I8/0:*$bZ9&*sĿýʽîŨ̽ŽĿƼ¿[TTJ:3GQG72,--.%yֺU47(WĹſýɼ¹±ɷ¿ƽ»ĿĿÞm8-8;5,&,,-%-r;02%?¬~w}pdhqpjdberqececfec\]cedhszzzsnnnwwwòŽʽŠnB'-..02+++AhqjdkpjV;("%8727cvd\S>2=FHD>3+++-88;===1,(%*)*$++/+,887/+,-31,(6BHDDHSUScvntry;òĿʰϿnTJ8-/+,8;2+&%HqvdRTlnF5,++&,880:MG1,2,0#'--&!!#"%++/"%,--+,.8;===CDB<8=FA=Rymt®дſŹְ˱å`*$85%!(1,22,0%$Px_PUksWVZdbD&%+,-%! (*1,225,+-..47/+,++/==:8=JT\^UHJZdbeemy|ryv¹ɼȾýǾʡıtaptnF22,$1rw^OKSULLLQZzm8!#,8?>2 %:842=U_VICDIN[j~ztzt|~pt~®ɰS,ʼýȽŽýɸ±wc\^[jZ2,P^U]chgc\]cqpZZZagWPUWdrkYUkwVKYp¾ĵñztszrzõY.ƹõqpnt}zǴG+,"%3DHC<12=U`_emwljqDZþľdbyɥq`_tʱ~_/,ɷ÷Ǽõտõy|mjjyøʮýǾٿȸ¸tWPft~v~ɜqmyØ~tztF$8FHķ¾ʿƿȸƠʾǾ¹Ͻϱɼȸ¸ʾrzvnla]jy}zzƞdby\D1 #DQG-ʾýʼ§ƽý¹}õǽǾõ|~}zjjnptsnkjjx|JPnpG1)*+:F=)±kjszǽ˽ѿȱ˺еqwxhŸdz˾̽fe¹¸ŽmtkYMGA2,0%'=QǷĿ®оýVɾǁTvоĻfQM>+#'PƸŻʴǿýɰכ]ķLJaЬź¾^>3& %>ɾʷӱɼõپǾ~wvxrc\UHD\f_J+/>NhqVϽƸʿǿĿ̼tykYpwjdkt~tpjzrUHJ@HSJP[_VPPNHDOKJOKJFHJOOKB<==,--8DP^^J+) %*)1APUN;ƷʼɹĿʾwv~rys^OPgbPD8@]szxw~y}~xw|r][dhgkjs`_hlxtnx}pjdk\VRNRTINUSPJ=5;Pgk^J.$3;547/8=JKD81,7=5(*1.01,7?>6-..%*==1.*031/047/.*)*2FQM@72/%%)9=)*8GQMG~dM>M^jjxj^bemy~xwtmjnpsztyqpzs^Zdttabe][VZdljnpsvwcszxytz}e]sxwxkYavn[T_^\fjd_^gkngknsnftwwkTTd\]jh\SZZaptcOKJM^]SC>DJ@>DQ[O?>?>?CDI==CINURNJINURNRM>3;?F=72/47622622+%"(02++3;?;5;:3%"'%#)3;??>6HSJ@72226KN?72;A=4769=>==CQZP9&)9=5227=931/>DNH}pvnlS>=R^\VYUMOU_`_hnh`_agc\]\SZZZaf_[TS][VK6'-@HAHJF=78@NRMSU`hmj[dlaVZdrsnf]cqnTJd~~hFH[jfW^jkYah\MO]c\VVZWVZ\LLQ^OJerpddbc\dbcZdbemwnhjd_pjZD23;4& *03=FPNC<@G=.8DJLL=528;20:<8@HSULB7'%*03=93,--+2FF=BHMG¾¾»ýýúƴʾĿ¾Ŀý¾Ȫÿƾü˫¾ýýĿ¿ϺĿÿľþ½ú¾¾¿ýýƿƾÿüź½ſĿ»¾üúÿÿĿ½½¾ĿĿĽƽÿŽſľĿķ¼ſÿƾĿýĿþ¾¼źŽ¿ſĿſĿ¾ûþ¾ǽĿÿÿƾ¾ƿ¿¼ſźſ¾ſ÷Ŀ¿¾Ŀſÿÿú½ľĽúÿ¾ý˾ÿĽ¹ýÿú¿ÿ¾¿¹Ļþ¾ÿÿ¾¾ÿȿƾſ¿Ľ¹¾¹ſĽþý¿ʿĽɺĽſÿ¾ýĿƾϹ»¿¼ſĿɿƾǿľþźȾ¾ſýƾýÿȿýÿþ¾ÿʽ¼ûÿ¹úÿþſ½¿ÿ¾¼þĽſ¼ýýþ¾¾·ſ¾Ľþ»þ¾ýýüȾſķĽÿĿĽ½þ¾ɾ¾þ½Ŀɿƾ½üžýýÿ¼¿¼þϥ¼ȿŽ¿þñĿĿ¿ĿĽÿ¾ɾ÷ĿĿýýĿʰnB}Ŀƾþɺýýºýý½ƽƾý޼¿¾¾¹ž¼¾ĽĿý¼Ŀv?P׬H+Ľ¿ſ»»ü¾þŸ¼ǿýĿþû½ĽĿºýȟC!xЎ52ĻĽ»Ŀźþ¾Ľú¹ǻſ¹˾½ÿ½Ž˻t(dW'Jý¼¼ſüĿƾĽ¾ſƾýǾļĽ¹ĿĿ¾ǝJet(#hº¿¼ľ½Ľžý½üľþ¿¿¾úľĽþŽûľʭh+!Ym@,¾˾¿Ŀƺýž¾¾ûÿ®ü¼ƺþŽн|8"=>%Jǽ¿ſý¿¼ÿÿý¿ĿýĻľľĿĽÿÿ̚K"!+%)tſûſ¾¼ýſüſ¾½ĿŽû¿ÿ¾ƾͤ]*%$".'!;¾ǿĿķÿ¼½þú¾ýĽ¼ɟ\+!%%+860=nƿƾɿĽľ¼¾¼Žƿ¾¾ü¿¼þ¾ŽþШc,""!%/8>=7BOIDHIJA4.RǾúþĽ¾þüʽ¾¿¼þõͲn2""!%,2:<2/11*%%&%$8lzgpýûû¼¾þ¾¼ú¿ƾûÿ¼þдw:"!%%,/130++0+%%&++'2HI7+0FK>=Ry¾½ýÿýý½ľǻÿû»·˼<#! %%!%17521*'2.''-1*,24.'!(/13*,;RyÿýüûýƾĽƾŽſƾž½¿Ǿ·¾νG*%&%$" %3AFB>;1*,*%&+0114.+*,./1/1/,2Dwǿƺ¾ĿĿü¿ǾĿ½Ľ¾ýý¿ſ»þ¾¿̫Y11,)*,'!#1ALLLJI>52+*++./30,)./320152+,;Qn;ºÿÿ¾ǿľÿƾüȘN691*,*)%+=Rdÿ¾ľüĿ»ÿý¿ij\s½úÿֽN=7.'',)%/52112452+,,217375245211(/11,4<=7.>Pazÿź¼üĿÿýÿºúm¾¾žľҩdC8-('-&%$*,'%%+*+$" *,.2575757=72++.24<<2./DPMPhþĽ¾¼¾ºúÿľýý½øĭþÿ¼ýˍD2/%%&%%!%%&%&%++'!#)*-1257459CFB60369:<2/5DHFBPWS[tÿþſϾ¿þ¾Ƚýÿھp.''%%&%")*-1*,*)++'!,24757=01AFIB><=69:<224?DPSNGGLSeþɽÿ¾ýբP '!(%$$"(',2++.@FB2/%2025:<217@FFKGGB><<=68>DHIKKK?=>Lhɿ޾ýʻþ¾ƾx3 ('%%&+*,',).+0D]bVJA9=7.4<<864<;DONGB>2>eľĿĿ½Ŀ»ĽҪR$"()%%+11,4-12+8I[lh][VY\QFBFB>;<=<=LPON=25DPSɿþźÿý¾ǿŽȿx7%+('%%+1=7.+036BOZa[V`hntqh]RB86>=>=7;=>DHPYbf`VLDHFKK?<=ALPMURU`hps|xbK>;=AA864=>>;528LmɽÿþýĿĿ½ĺùþýľýýžĽѥZ++:GLMI=72;1/4?@=?A:=yľ¼ü¼¾ºº¼¾z4'8HYb_[PPOF@KJJQSVVV`jdbbdkz|bNDHIDAJJJJ@:=>?=?AGPV\abYLKPPOF=,+\þ¾½ǽǽǽº¾ƾ¾˥O 1>HQS^abetnkm`OFBGJJTVTYgkmszzkYLPPPOFOMMMIKJB98247?N]he_enqgSG>8Mq½ÿƾĿ¾þºĿĿϹt0,>GJ;8COSapT>2-169;3+)-9FPVcvl_`VI>;Z¼ʛJ%-CH>6)-8HKPnzxtm`O>0)-89UchsqnqxtjU:+AĿ¿¿þ͖G%8TR?1-1:BMMDPUV[lwrlP6)%'/127=?AL[l||h]hqn]JJYbttaSNI><9GR]hqshdqlPHIK^t~wre_YQJJMZ^aT>9<=SVGJ;1%'021-8Dcü¼ýþøǽѲW!%*%*Khsqe_abe[PPP^aPPOUOF5*.35555>GRKP\W]h|}j\\WR]hlehdZchh\`j|nI78DH>2475027,%$)-8PpʷǾξº½̿¼ľĿĿ׷[%*,+2Zw|}jdbble`VSVOFJOMQRJ2-,24758?@=:BGP[a`_`nnkjmh\`abtzxUFFFJB98;8>?=>8=GI><9OhѼľĻǿĿľºĻѽ^'/69A^nnmhb_[\`_[[VOFJHIKTR>0('/:=>=?4/7?MMIYVT\a`j|xlezaSRUVWKF@>DA>;DFFNDCA:82=JQg}Ƚ¿Ŀǽr/12@Rhl`jg^WKP\ZSPPOUKJBDHLD8+"+:BGA?74<8CHJJMJOS[gx|nkhUVW\en\IAJJG>:BGMMIJQJ>027,4;=:82)-5AJD@?BG?MMUKJQV\\a`bjrrnhlw¾¼ƽƾ¼Ŀ½¼֡bNS`db\W][WRPPPOJJJSNIDA92/+/76),2>?=DPUF@KPPPOZccc^WU\lww|s\ajmhe_TKJBJJM?FFADHYVTMOSVVJJJGJOSVG>B?79<=;8>>?=DG>B=?AGPV^fwǾ½º«½ž¾þ¼ҨnQSW[PJKV[W[ZSJJJGJJM?:84/+")6?7,+2;>GRYL<;>GJOQY\ZSPR]]^adqheqn]VMIKMPPOFBGD@GJOQPPPORUOMQ[[\\WK>DFCADA>@?B=?FFND@>;79;8>>JTV\d}ž¾ýå¾ƾÿȿ½üȾէlPMSV?7FP[RUVMCHJHDA>@9<850&&5G>0),2>DPPP@:3:BBGMSROMS_`UVWgkmsgSOFJHPVVMIJJJSQSNJJMJHIKFFNW[RJH>>?MNSVGJQQJJQQY\ZROMHDDA>@55743:KVcp|¾þþ¾¾ƾ¿ՠdRUVP65LTKJJJG>GJJCA=?A@:84/'/F@7,+/@IKUPPH>>@?BJJMNSV[^WUS`r^jmhVOJJJSQLKJPPOUVMIAJJRJHFFAGJJ@A?>@IHQROMHV\TKJQSW[PPOFB@?B=8<=:62-+-1:55>3+,>?@=16COSNPPUVKCHJHJOSJQY\VOWsm[ahjdWRKJQNDCLTNMKOMLA?KP\OFBGJCA==?A@@?BJJJCKV]UOJQQJJJGJ=:69<858??@84+)*('&&6?I\q¼ƽ¾þƽ֗TKJUF24PPHF@>DH>>@>BGGC4/8HKJJR\`PB?DA>@OSPPUROcnISenfZROMQRLDGPTKJIKHDDDPPPHFBDFCJJMNSVGJKPLDGB?:8484351679;;8>74362)-'&&69FcvǾƾÿþ¾þÿþĻʽſ½՛ZNMKB98ID@@?:FPDA>@9;8>>.,2/+%/@RPB,+2;AJJV[WH>>@>?NWRKKJQtZN]b`^PIOMQRLKJQNSVOKJBJHIDA>@9;>>GRRUORUVPGJJPPHF<<;4/88434<89<=;=?495GJCB?:2-4<8.,.4+)*?U\Q>224>DHLU\ZNB?DCOSUOJPbl_dl_MMIJQQSW[MDFPS[WKFIKHKJ>BG=?49<;4624<8982=A:848@IH>?=62).,.,+255>J^nſľ¼˿ƽȕh\R?27DPID@@FFAGA?>550282),2/.,>PV\WG62;>ADQY\YQJJIKUPMMUt`VleVKJMSRQSWOID@JOSWRKKKJQNGJJJB9HD@>?72739<8580)%327Opÿ½¾¾þý̿Ҽh]R?216CB?DCGDA>@CA==62)2-,-+-:BKPW[Z=5:=FPSSVVVJIIKHKNS^z}\W][UOJPYQROHIKFJJMNSYLPMSRJJJSF=:>GJJOSW]h\V_`UOFBB98;557@@?BA:88??I><9>82739>BDFFA:=>=>BD=54<8.)-8Pazº¿Ľý¼ƽýΧye_YB2-,-=:>=?A@@?@=:>8=/121-$)=GIDPYVI=:>MSRJNDC>?=DGFPS\qpYLPVPIDRUVPG=GISNIIKUZSPRROMHJJ?95:FFJOSVSVOK\WGD@>9<8584<8CGJC>JPPH>>@>?=?49?@50&*.3F]zɾĿþ¾ºǿ½ĊfdYSF0!%4B?:557@<;1-8:84=5*.DPI<.,>LTWR>6>GRKNMD@7438COSMDFJQQSVOSVGJDFC:=FROMHV[VOJPPH=?A@9GJCA=BGDF@77@ICA=89<=>DFHIKFB?:5:=>=8950&*6HYlþľĿ¼ַt`dWR>2-4OMA:0)0210)%+2-4;-"?l>('9L[YC49FIKMPF@77@>?MNJQy}\QJJINMT^aPCA=BLKJIKHKNTRUVWLD@GDDHY`VSNSVVOWPI@?BJJDPhzn[PJ@?B=@AGJJJA?>JQQB982:8487?MPI@>?=>9<85846245AV[^dʽ¾ΨndlVOA:A^lP92/.,.,2-,%*2-' 1tܾM*(6N]O84BDJJ?HID^znIFFAGUVW\QD@GKJQNGOFJHPZ\WRRJHFLKUL\g^UOUVKRUO?@JTV\V[ktmhdZH9<=>DOSPLKULSVGCA=889579@IH>:=>=>=:695577436MZSF\žþҼpkhVMC@^thG0('-+-/76,%'/"2rѦf6);QSC42?NOMQIKMPFA?>PprYCCHJWcUUF>?NWYQRRUVPGOSa_[TKJIIKMPFZcc_[\a`[VNDCS`prgccdY[[VA:8FPSabVV`gc^WUNMTLD8957=?4969A;:=>=>==?A82)>P[P=Oʽľǿ¼¼ֵ`^aOFBRtmK8,+,2/.163+'" 5v׸zB28DL92:BKRUVPGOD@>IbzkQDFP[nhSF>BD\`_WRPUVKCLT\`PROMHVTRLKZcc__`nna\W]_dl}sqe\J@IPPHJQYbc`VSahe_Y^ahl]J=543508DLU\ZSVOSNS^vtaTKJUr|gJ@I]bmkmc[PPPPHGPTVVROMP\\WRV\\[[\ktpkht~zqy}ttlYC=G^nh]RTRYQRRTRUPVdl}s\KDA927DLD?@B?@BIPPOUKF8438;702,% DÊA%-<;46K^fdZ\dq|h]RJ@RtzbNSzm`OSNIIOSVSQSCJW[ZY\ZRTYg}|njmrl[PJS`lwpfZRGJOQPLDGBN]nzvcRJHF8jؘJ=VnkjyvU@?:9@IC:+,+$#CʼnC'8;38DYbcgks\YL@?Uljdfýt`VVRUVW\[VNUVKY\Z]\WRR^nshahebV\t~yttlh\RDHS[WF@>IPW[jm`VSK>DGPOC9C\qs\YMMDDH=:699;=6?IB2)%'7ʊA3:=5>P[ktzdY[IDRcpkt̸sWRPU]b`a\PP\a`[]b`\\WZcqqgle`ccd}xnkc[NMTaSA?KV[WRU\VJJC>BKJQNBG`w|n[JJMJ=:>D@>?9=OUVWLSVGCB?:DPUVTMOdlg^H>>@>?=BGD:=>=NhqgLKϥzxaLD?8CGYw\S[YO\xªŽ}k]TQV]_ZW[Y__TQWRV]benxqqqpzye[SIDNSNF9>SVOQ]_WMKK=CTdf[MZ̼wZPD8=GJB5*"(Vղb39FGJtsPPUS[xɳ´|bTQ_gf[Z\`ecVVVV]bWc}lhkrcNA=GRVJB8C\\SVlse[]_D>CT]_Whla\YOGJJBDMKFA=<78;>B]lhWF_Ŵa\P?7?NF6)%%/]ӠT25=Gz¯WFLR_zĿvvn`enqf[Z\`_TS[d^f[N^Ŵlhls||`PJLR_ZJB8MZ[S\qwmcfhVLLR_Y[krhkh]TVVHFPJ?=B>:8=<=DVfhKFt̳yep\JL^^H,)K}ώ@+2JgSNO\~ŷk]VazwfnyseZW^f]T[ec[Y_MaŻzlhgfnv|wZPMKU[SH>@N^dnvnhd^cfbZWV]bWcfmtp\W[^OLRJBDFA8;=<78KclaNFɺgTdxg?%'Ll7-F~zWRZWsȴ|vbTNMKb}kzvb__]_W^fdYOSVUmƼvlhgbe__hpzhQJLLKKH>>CTxq\\Yalhg^^hlgagsvl]_cfWMJNMMD8255===GMKbkL6hŽþ^>CgsS6PȜY=[aSVc}plhW\\YUSR_[Y_j~wfd^QJU`eVONS\kzvsplklhljbZVVVV]}yWMROLLRJBDRwfhhpwnhecgnvz|~re[Yd^c\SI<844:>C>@>:Ohd>C˾qwf4:Yw[MƋT\nh^Va|éf[Z\__TOQPU]_WPYwra\ZWVYabZVTQ_ptztrjgfag`\`[SQJNhxhLKLKCGGCO\ay}pmkllsnnqmknnvvx}g\e~kTJC=78CC=A=>>@>>@Vlmkntz|tpnyz|xysmtqqptr~bTN?78CC>@>>NMMQ[B>ŽϼQJlnK=lfhœgSNVfϵsebe_ag`VV\SIBDHFCVvq\T\\\YLKTPMQ]dlwfa\ZQVUSRJC?DV\SI>@>>BDHTdm\\qz||ysmnnnyzzfO>:8;=GJJDNL@?SNB]ʅ>;ZvvgJ=[wrcN^ʞcMQmκyecgd^QYYabWF>:8:>RthVPU]YJNROJLLU`v|kbZYOSDHGCDFGOLLB><=DFGOTdmfbmkntxnlmknw~zcG=<78?NTQLRJB8KSI>ϜJ*8ZkbI6>NMMAjΡ]Kcj[__TOPalj[ID9255\lYOGY[PU]UHFUS]lx|k`\UH@<>@DFOQPJC?>;=B@DO\kz}pc^bmztrjbdnw~|tkTA=<>;=JNROLLG=445Cazn`YOSQPUQ\SGJZW^`eq\TPMFA=>@DNSSNJLCB>;==2Iջq2",21(+MТ_Mmvf[^^`\bgffbYOA=8;GYyrj^^\\PMQWRPJS[dUS]|dQJNJLCJC?>JNIDFGFACB?=GYhlgfhmk`e|ntzvl][er~q\TGJD>6DNVOFA=>:805mŽѰ^&#1}ͨk]zpc^bhlglac^ba\P@<>BUwvpc\Y\USRROJVaa\GCd~~m\PJ?GJJNF6?==<@BDA=GA=AOalqqga__]dslhglhljhkhkrtklv|vfWF>CB?.7?HMJHFC7-,'LʾϡW);аzszhd`enlhljhlhlaS=78A_qqgSNOU[UMKP\`_P?8ctbTQG=<@DO@<>:8:?N^kpqf__]dgnxynd^[Y_jtzn`Y^^QP\lhlpldbNAHF9-39CV\SNF6.,'3|ße<&!\б|~vlhg^dnlmpttpnpcP828Chq\e[OLLW[^QJU[Y_MA=hgSSVU>;==D?==:8@>>BUgnlf[NS[djtrjfb`\U[ertkZPTQLFPa`\hkhs`PPD8478JhkZG=624+*\¿þ̯g\hӯxqrjg`\bgls|ysrjqlW;/4DjeR[SQROV]bWZWVTQ_WRtt]KMZUH@<=D>;==:8MgaJ5/46>/(P½Žϫvvzshd`mtmtzshhpq\@+/?clW[SQRQJU[ecVQP\klbw~lWH>IPKF:8B@I[emtvh]UMKFCBJTQWRV`eVOLLGRG=6<=;5//9>=<=;=GMPYZWVTVV\^c^bhnhenh^VVVVVa\Z[e_TSJL^n`Vatznf[N>57678;/0-*(+32"%P½ýþɾþֱwmm\em|ncfbjbTJKQ]dlklb`\UMKBDAOLLWW[^WMA=GRL?78785//4:;>:8;+'\¿¿ļݾ^khVbv|ncjkc^ZPJN]lnh^V[SQRQFGFIDFNS\[Y_MAGJD9278;/6778;:878;@N\`[MKBKKZvzsvqhd`ZW^`ZW^d^c\clwfZ\hdcfmn\SVB50-+0584*"%/A=AB50ʾϚ`Vvqft~rlhla__TOJU`ecb\SNNMMAIIGCDLRJMQVLD>=7828<844<=;=927=B@LKTPNA@NTk|tkmk`[Y_W\`[]`e^^`rxlacbekhdcVON9-,2,-3-*'#&:JB>;>*"GļٸzW]yep|}xwmkrh\__TSNOUhkhPMFJB>;HFIPKJB>;HCB?.2528<;828=<98=FZkztspmkhd`\UMVaaecb\^fdga_fhhe]TJB>;2+00-+,'+'(+3II>584*,jſϤeJ\lVDVz|spmqfjth^^`e[ODRpzbNJSNB7?ADMMSNB744<92/.,115==:>@<>::8\ljbdfhhe]c^ROV\\__befb`YT\e\Y\UK=;821(-*/.,'+,2),;HMB592/>Ŀ¿ʙ`PD8/?zzsjbdfdnlga_ntfOFZtz`PPVUD?>;=BDH@A=83962,2,:115;>:>:8:>;=BGJDBDA84Cgsc^Z_gnhe`\YTZWVTQV^^\QJNY[TQLMKB=7/.,/.,/,'+,5/'+7II>576'VÿˠkA,':?BDH@19N|jbdfxvb_[Y_WOLLGJNROGCDV]VONJC?A=3232.,10-+396)%%/A?7.2"1}ѫl<,2Vyeag`OLLWW`enzxVL\xnZWVTJB>;;88284:;2),,2,:8=<9;88:>@=BFAC5/3>NL@/?nnhent\Y\UUMFPSVUPPHHHFUYTNFGC=B>785/.,/2114:;2! BkL'++'Lþ׬h:-JzZW^VLLIIWckrzx\GYycGDMM?7.0589844582%+00445396;88@DGJDGJD9576FG;8Ytmkvve[]`\UJHQV^TQLFIIGMKPIDF>;==:87+'0532.259-,Pz/ ),,qխkA\x\ROJPPHHHds}|V>SyW84CB>2)+'28<785/1150-67725984;HPYZ\\_b`WF>45/Dzwrs~~re[ec[QJNYVONJD>FBDHD>FBB>787785/+75//4,23,% aT (&Fٰũ}`PDDMM?=G^wQ;U}`927=<=;5*(57672/.25228<;8;=87=O\qzrP7'+Q}}v}vfhh^OMKUPUQ\PJ?>C>HFCCB?=7/252211503,.*-*/%+sܖ=",v·ƬzVB8=DFFFGPbvpJ>^lB1+/8=>7/02221+,07888===8=>>@JWf~V66_yz~rhf_WNKOPLP[TKJJD>>@?ABAB=507;;;50/020+,020'6΁. $Oü«fJ==8?AHJJJS`hl\?A\mI20'9==888.+,,0,5<<<==88;;FFKVk\Qlxx~}shf\TKKOPLUYOGJJF<788=>>==83>>?7//0220'/888(Fb&3wļزz]VNKDFIC>>JJSQY\[N9=NgcJ66:<766:20'/1+4<<=9=8=>>JPLJWz¼nyxns}sf_ZOGJPSVZYOOPD94788:<;;;56AB=20'/11+,0250/TٟC%O֟O07C>BMSK>@QYOGJMD>;;FS`[D7/569=822178==?A9=878=JUY]V^}x~xljt}s`WNICFPS[ZOMLJ>569;;5788:>>?==1+,07//02020#e΁,/~ύ@(7D>B\eVB@JUH>>?=8HUYTah[D47474333<<=@@@;;;<<=@KOZ^^e|¹mw~rfkstfVNKDABMS]VPLJD966:==8333@?>94==?>DJJJJS[hǸppvmkhgcVNJJJFFKZ^\TLJD965<==?>47CMD:5695695221720'%>ܞ>5Ӡ\>@?>Dc_JDBA94=6AVgmwzhfhpjVB.+18;;;<==88;;;;94564743/0222, (aj$+bѝY:<;8CUH<B==8??A9>DQY`[Zbfkżtwz|sha[TSVVZ[Zb]VJ>==88;;8=49=8>>:5661221721334)1ٓ8)Dc}@2217BM;5=D<025Unp^WRH>9@JKOZL94=FFKD>B=IMDDJLPRW`hljgtqr}xnncZYQPRW`VNJD=588=:5678=>?722=88.133341386%FܬR!&9CcZ/094=6=507C2&?`cK4)/+#'+'+8HD965=DDH>>@QPBAIMICKVchg_dhptwyqr|sja[\Z^WNPRWVWNI:56;;50259=>@613378841+420020#)lt1%3>F^־ܡH)+8==83322=8?B!'13+,386661,5AH>>BMNKDGJICKLPWURPMSTRWj~~}kny|sj\TVW`WRHOPQPPB945;;1+,018;=5047486602056/+%C؉;'+8>D^ҽЊB.+5AB=22247C|ΖQ86-18JJJFJ>=:TaJ1+42:>@?>DBRgtxsttnpxshpt~xsh]\Z`cZYQPMDCH945;94/0-)2:567569;41/02271%&h֔G1+,:ABTýq5<=:B=ID>B=KJ=58@@;CM\ehfmkhcmvz|}ztpd^^dhgc`WRJ>=B82::56133/098=498=A1+422::5'6ܬhD1+=Bü_/8BAI9452*.J։JUbfahmka[\D>g᷈`J>5, (,0,5<=:9=>@:5B:<;8/0988=49886>D?7256/221"!\ܾK48LYO>2JܢR-1@J>3.13+6~Y:O^^[ab_]VJ1Pӻ~\>*#%%&*.0205.+138=DDHKJLPd|x`]ehf\^WRT[NC>==?>>>:224569;494=6AB866661,'%1ܿF/>^ldTA1@ȾӎF/2>@6.+*.6VtAHYb_SPRZYQ7Jսx]VJD?>>?B82/88CHKV\[bnÇxp`\ehh[VWPRTK>766:2:5675:5.166:2:AB=88207//)]޸xJ16Vmka[D4>z˾z9,5A?7/)+'?R=UY][TKOZL2?Ȼ||vdrpjhf\LJOPQNKD;;57==12:::5.38=;;;PRI>>56952&6w[D4>V\[hgJ6=lκܸc,0?A@0+'++bz@@Z^WRQPLJ2:vw[knljgPBAPSPLGJ@:9=>@788:>88=:?A9PbmaM>7<<=, (ewf_B1:JWdhgI+/mи֝J%3JJ4)'%FO>JWWNPRM>+3nѐcZ[aeaRHFP[TKDD>B=?A9><<=9:<;87>Jaxa@:994/$O߳rbva/+APWUWN, <Ծw5'?PG1)"!-}k8=PLJDCH9%%enJJSgmWICKSPICFC>B::56;:39@8'&\z>3LhlYJDFIOH>>BC>B::4748>7B82/!&U։;+JknUHJJJFC>B:?A9><661:<66MhmcZTK>2*$O·Vr4 $+,3*"ذ^(*:z0#18;C>1%MܚD1PtpVNPMJD>@DF>9;;F:56;:7>\svdSP>31%,eˇPY, %%#KϿǁ<$2?>7, /~ܗD)+18;=8'@d@]ztd^VNJ==?H>9@8@:9988=:Vt}yfVJ=20'9ʄKʸJ#MӾګV+/;FF< $Za/.1.:5.'6̓`h|snbSK@:4Tq|tpdYO>86%UЏPȧs>!DҿύI,5AHJ73Յ>,0?.16, /}ݵ~xzqh[IC<>9;88=:?>76661Lnqh]VJD8,;ܪrmI13IÁK>>JJ=+#QT(*33412,%`Ívm]VJ>9;A=438=;<<=9:3Dhz|p`OGD?+'^êҽֲnQPPSK.'tz0!+/56/2+>bSHJD?>@@;33488238=2:]twz~xzvdSD96-);ПOGW[TA%6P*#0+.:, (eqG;CHKNKDA;020556/2+3Myzz|}xshWI>3&8Օ>9QYO=%CԉD),0,,0%0wDFTQPVNJ94/02002+#+Jkxspjhqh]JD=3'+dؚ<0CMB8%T΍N4)'20'DՕZOa[VWK>221200.+*&?vmgc`\a[VD>;0+>ܧD%3=50%!dΐ]@2#66#)l⻁b_SSP>86-020551%<«ztjagcVQPLJ>7,%DR!(780#%rWI>020#@ѥzhYOO@:99221721"Rnhhgh`PGCHKB8%"Mf*#;:5#%tnKOZ>,'%bܸf_ND>B=?7/53.$%rvd]\]VJDDJLJ>*T·:%,:/#%k]9N[D4"!-~\bSH:<;8756/%7՚`PPLGFFKKJD>+SQ%)+1%^b+8JJ=%CߵcbVHAA<.+060!OnIB?IKGGGC?>,Jt,)5-%Rׇ4"7IB%Jๅh_VJC?9.+,21!lւK>?INDB?A<9.@̾ܠF'95(G_(,AA&>ṅg]SG;6885-//!)S?>DIKC?A<3%>t,+<8-?֝D)5;/!5ܮxc\SG9546873,2U7=>DND>:73%;ܡB+<>:Fm1066++ܠn]YND:773462(6d46=HQJ=730!6ܻ^06HQR|ݖH3>?.$r֝hYNHA>?<821-%2և=7=HPLC:/(.~<8N^Wsh=CJ9%dٟhSGDBDC?910.+ -O+0CJIKC5(!!hћK4K^WcЋJJVJ.UۥhOFDIG;885462%+l++;B?A?>4(Rݲ`9DYV^ަVH\[=HܠbJCCJD8-54856%+َ4(8B?A??>0!LƁG;S^gjP\dHAp֖ZG;<>:10.6000!)D,8BDBD>:/)$G֥aAL_lĈ\[bQ=\בQ=8565-/40(&*'W46=FLIB5-(&<۹xHAVcpХnV\[DPוJ462%10.6-%%+%yq8-;GNK>9.%+eˆPDPV^ع^]aHFߢJ)//(,---,)))%t֓?.=HGGC?5(!>ϙ\TZSPdɓlc\>3ZZ%+++--,0(*,)$rS*9IB>D?.+%+ܮxlj[JNЖhYF/!0x.$+0.+,10)/0!)rk+0CHFK>.+,$ttqfPGeԕ\F<.\D++21-,2,)+6+*m܇2 ;NKN>3//"P˝qpvdMP}֞Y=><.7d+%10.--'%+=7)bJ2GNMC5-('1ѫvvtWJe٫_:>?1!"[Մ/!,23,&%%1D<.Yg"&?INJ?.+*%YӴwcQWf8;B8,<ݡ=$25-(',8J=-R؍2'9NKNG;,)+5ֻsn]Qkl;=HG;))[޸S&*600++-.95421-,% DՋ\[bbVIB}ÁMPNDF<'+p:7A<35-(2:7.1C%+10)00,)&7٥aHIKC@6t֞]NDFLC0A}B?AD<8-5:FD6+gI!))$'+,)&)+ʃDJNF405CPU@$OU $%%))))) VfALTD/hآYDIG21pܠ]BKSG927=KSA ;b"%%)&*,)+@ДRP[J._xHD<,2tܫgIP[SG9288C:6h#(&&*,*,);fVc\2Pٟ\>:)/jڨkVcnmZA246=.5a"%&%&%&%+P|\dhJJ֯g;=//YݥnU[ecQ9.2:5(L\%+'%&%&%"%t̍bblcT`tGGCN^hr~פaHDBD7))56+ rf+%$%)))&%8֝hV\fechpbbq}teOb؝S7=6+*'+60>|4""&###'%ZֱtQP[lpbVfpvm@FפS7HA,#)56+'}@%&%&%-%2zOVcsnaP\xn<>ޡP7IN>,233%$VL )))&/!MzQkt\JJkzZ2:ו>&?MHA88+;Y%!%% cψ\x}\DCc|t='9ܙ@2AFD6,#am"&#%"yӫ\@FlM)$;ܠD16871!1܄))))1Ы}\=H~P.$+LܠF#)///( V3*,)JཋzdM6Cť\9.26tآB"&,)+%%@(*,!`ʟraP9IОlJ405R܎="&,--":L%%%s׾yn\FbЊV?.+B|4&00,*$]Q%$%$ѡzl\_ևK4-5h]*%/($%0Q$%8۰tv֞P.DnݫL(,-(IG%%!TۺcלI066,#%q8!&%%qχ@1600":*$*%.`:7721hn'+%>؍>.54.+ (]%%%Wl/(+0%II$#v֟P.'+,"%t<%2z=',2, 90%@a0(.+,\m%)V֜I)))-%/T%,qr9)++,%JC%*,*R2,+++"e3%,)4ܖB0,+*, &})'*$Fz9)'*,%!7^")+$ZܡU0%'*2%%OB)%'nk4-049<$+l-%'#+|I*9FID?%7n!%-ԍID[^VK=%IH%%1tbtzfTF5%[ԃ4#)%2atnQF;('nr-"*,'#4фI\bL>?5.e)%*,'%0y439<>F5=_%%&'*%+s+$+6AD/Ob&'+,&!2s*$,399)hZ"),%!6t+$+,..#"A!&'+=})"%%&+"&ш/$%gp%',+*:Y%'%)cb&-8/ )q:!%+1tg=82"%ey.#,%6ƽbWJ/ "gN!%+\վȿſn[kV0 8s1"%*\Ⱦž°ʽͽǿκľsG\v\0%Mّ=%:<2"0t¿½ƿ̽¿ʻηʷʱľͥ`JV]L.#=ƽž`"%FI:0%.h¿ýοüϾWJD?=1)+Ln[/-><888$+hrnv}xpgcdhjgcec^]cd\SPSUSPJPSVV\_bdhg^]bWZflhjnwvnptz|zt~ҽƯùʰɍI8=8<8:6+5^xknvttvsjactz}}^C5%(,+*!JwsjkbdgRQTF>?HLJD?=<>FKLJDAD@82-07779<>@???:<>A=8AIDFI:722=Nc͵±ýƩ³۩xkk]LC?BYsvm`^]gvz\?+"$%$%7JPJB=1:A=FPDAIWf]L=80,5;>ADA><779;><>NPPSVWRD?KY[^bknehqpgdhgkhZJ>88/2JwúŭĬǺνȸcYO=.~ís^V^pnQI><7CHLPSVJ>2,4CHatcdtcVVUS_behqpf]flsvkhqz}r~mZ>,3Vûʽƥp=ʱȾtjM$Vмxpck}÷t~v\NKYgv|½ŵϽrWDA>U}°ýǩxAvgváycG')]~lbkµvqƽvsrndY[gnpmtzŭlB$Ⱦ}xxbvS92>e˾ƽƷzpysjxyfdnwzpptӻvC4D㸗}̷ttͽȿƿ¿òzknx|zkqƽ}hP+$3>?8сD]ÿíž¿ÿκ÷ĵwnw}ŷt]ttP0 )=>,ϓqptοʽŵʾĹžʾĬtvðB=KL5'43" ɞa¿žɽµ̾пǩƯðƽµȾjvºùü¾[QF>?8%''#-Osؤ¿ڻǠ~˓ַظȾʱaJ>2%4^֝wDZϫڜ޹ǿýýȾdA& ZЏDAñȽüʺveUStrW:!.Wl]LCʁNKcYd̽ýýýxjawzvsyzznhktvYOPD=>KLKRQSUZPD=JONPNA=FA=F=8<<>0%,=WlnQ4-% ,DJ9)'ֻT=`ÿžʷ½ľʳnnvsaJPt|wvzz~qppme\Y[^]]caTF>8A\fllO=8A@?DJGDJPSJ9109<)%*)%)+2=9234-%1<>0/-&'++++('%'''4LRJB7) ++$.5;50,ó}dY^}žsxy~}nlr~qggwzfZ[fp\DFQantjWOF7%&8MTQU[ZPV`^SOV^c]QU\eeedVS^c\Wbd]\eaWOOOOKHDFMOFAAA>9=6/5437%'6544<>987<>=6$"% $/5-+.8AHD=6/,2>JC43212212212221=QaO1$'(&$',+ $',438rfpzvkgntmbkzzqrxtj^^ccgnnfafffaf`RRewaPZhj^\eqrlhjv}wnlqrly~zkgt|rlhvklzzgVcnffpztjlrtje^^_zy~y~ywtjjddqzzzxzybQUsstzfI=BKHF>=B@5.*,@S^YQJCRegVQZcg`W[fffjdYQbke^he^]\VLNVZ[KAAO^cWOOQUSVSOG=6<'#2:87>@?==B@=6/# ,8=B@ID@?MJ:88ACD8+*,0/,,254;BD@;5. $*,0+ $#$'212¿¿¼¸þ˿ÿ¸ʼùþý½ʷ˿ý¿Ž¾ý¸¿ƾýýȿ¿ɾýȿýȻŽǿ½ÿĻľýŽÿÿɿǾýžǿȻ½ýýýǿɾÿĹþþ¿þýƾýþ½ý¿ſ½þþſźþľýþþý¿½ƿþýƾýý¸÷ýÿ¿ɿÿ¿¿ʽ¸ýùý޾ýýýþýŽøžǽ¿ƽ¿þƾþƾ¼¼ſþ÷þÿľ¿ľ¿ýþý¼ǿŽƾžþýþýý¿ºĿ½ýžǼ¾ɿɿýýŽþɿþ¿źý¼Žþ¾ýþ¿þĽþþþǿ¿þſ¼¿ǽ¾þýŽþɿſÿ½ýž¸ľþ¿Ļ¿þÿ޽þýƽÿɾþýɽ¹ýþø´½¿þȿŽþ¿þÿľüþµʾ¿½ľμþ¾½ýýýľýýýýĻ¿ʽɿ½ý¿þýĿ½ý´|Ľľ¾¿ýûǿ¿ýýŽ}ſ½þɾƾŽþʽþŽ¿½ž¿½ýž·ɿýſžýýƾþý¼ž¿·Ŀþź½Ľüµ½ýžºľÿĹüǿĺ~¿ýſеþ¾ƾŽĿý¾ſƽ½ý~}xxx¾þݵdJÙ}źÿ¿ǽȾýýĺýýĽŽ֎-=۫V>žƻý¿è~zĽ~¿½Ľv%]ߔ9<þ½ý½þýýɽ·üĺŽz|ǿ½Ľk&4y.P¿Ŀþƽü}xſþžY $yݾQ#]ÿ½þýǿ¿Ľʾɿ~ɿ¿½ÿݦ>c֔+kÿ¾ýýþ½ýþýýĽÿ½ĽþՈ)RV$žƾÿǾ¿ʽÿ´¹žgJ܇*8ùÿÿÿ¾¸¼}ʽžMJFJƾʽſþ½Ľ~z|ܝ8!RsTþ½¿~yxx~~xxԂ%!Z>eÿü¿z}~z|~y|xxwvx^VW%+zýýÿýý~~zܟ> CZ2/sĿžźż¿¿¿½ÿr%$!,0'%'%-PýýýƽI"  "*,0.*,455>PxȻĿý½¿ۘ,!#!#!$-/(" %' 7\txyĿƽƽýýü|$" %% %2+&+++)%'%" %'),04bý¼üʾýn+$#!?aZ=++22+%')-* $5ſ¼ܵ]+!#!(Lz}P034571+&*,'%"% -P[U`ʾ֞D!#!% "*+>VZ=.0'2BC8,$%')-(),0'%8B<@hƽƳyƿǿ́.%' &+'%"%$"*@F=2+'*,'()+,$#!&+ !2=T|ʭlF/Lüp"!!#!% %/234.02+''%'**,'.*&+ $$1Jwк~A+ !:eý¼k  "*%!#!%$%+&*+,2223/(+&*+,04,0.+&#!-PЭ]1|c% "!#'%''*?J=<9-%')-.*-/23/.*-+,2,*'%''++),Oְb"IϽK%%!$-&%'4bwaZP9$".*-+,0455-/,*),*,'.2+''%!#2T½ίg%  bn%%' &-71$Ak~qkbJ3,04,,0.+134.55-()+),02+%',*)(8U}ĵ|tkbVRVZb_VRJLNLIKQYfͮh%,yý͍8"!#!%4>5#*OhlmjbVMGA7188,&+0'26=<1+,*)()+)-/()+)-%H|ȯnVD;5.*&#! '8Otϵt+"8ν֛D!2=.*&#'2=8/8Whff\VW[UP??J8/+,2()+7:=<123+&#++)*,'(+%"!RʻxeJ3+&# #!+JwÕM!8mϾʾM>PHJL<988BIKZ\USJFMNT^\^\@3440'2,0.;@??=<1++%')-(),*&%HȹyeRC8,$(+,++)*()+) "" ()+)&3NxнI+Jkƽ¸ýq%BI\jtx^MG=223/.++9??=BC@8/+*,'(()+)&"/wfJ4*'"$%+++)),*&+,2,()+) %/++))'%"%$!#!3\3!8bƽýüýƿΛ7/8JU`xy`ZgtshV;,0,&%$%++++2-/,*,4++)*(/2+&*+%')13+&#+*'"++%"$,D~ʬ]!=~½ƿܡD2=IK>D`|ztkpn\UWGA@FJFDWhzr_VJ=:2+%+,2,3>HJPD;40+&*" %%,$#,`Ϳl\N?,$##!&%-/223/.55-22222-/(%')-.),*03-/(%$%03)%()%' &%'>sș>Yɾܻb%5'2GVZjpggjK=;58686,*)23/.%'47JzĵxV6&%$!#!%$-/22+3>:7:7:86554572+%+%'4++)*5>2+%+%++2-()%%!#++)2K~ѿ;2HJ?:Fwz1>WO8&'8IKZea_\N2+2=8B<8BV\PVmz~v\K=98840+'%"%()+.0;@86BILB<22-$%0rȹhN?<9,$#,.% ,04,,13+;IKMLB:1+,23/34.'%''+.*-+,:7/(+-/(%+&*"%!#+,*#!1V6;s¼Ϛ?":=8,&#!'2GAHYfZMG=.*-+457=T]US[ltxzh\UPKD>6/(/-*" %%),68OPQQQWb_H-%9?5.4Oh}fJ;,% ":>8650'2634??2+0;IPKDGA5.,*)(,*)(,,*)(,,23/.52+%+03)*,'.% "+,2(++%"$?t}KĻp'/%',DZfVLNF//@W[SVSJ9-,045=J\dUFLZ\etxmje[NGQYQ=2&% "!!#'-765548AHHP\^a_`ZRJ=,$$#2B>868++2;5?JJ=2?JJA=J\^A703)*.*-+,/2+% "+22223,.0;71,0.+()%%++2-(% ,*,4]hGýƽ~0,'*H`|l\^SA=DHJZ\VMJL<,*)6=FMD>HY]g|kbg]SJczkVL=2&%%'%',0,*)(),6,*)($".*,'(('%''+%'%" %2:=8A70.020;;5?BC@JRP?7@WtkH400,.02*'-/,*&%$-7655.,1;@?5134,*)()'**.*-+43,$^щ5Düû͓<%,BC?51,0<<@?517@T^`ZA=PlyeD400+.02*,0450355>>8=<;589?523/.+#!'),*0222233,'VΛDTʾ¿ѠM-<9Kc\PZg\PZeleRFBC@BI\R:++2;.020DW\dnh\]gnfP\hlntkH=228A=DHLB.%=[ggj\HJPmjZ=55413++,2=<98;@?FHD=DHL;58/-03-%!#+,34486,23/)J> |û¾ϡ_N\UWcSVa_N?P\a_VL=<98;@?71,.020;@?FIKMLNT`dWGIKR\bVG:7:OhmsshI4572,3,'*/271,.03540+/8+%#%',)*,'.28=8=8;,0@WVMJ@31Pljb`UFLVg]I455>884+,<@?>=<;9?>;5?IK>:7/7:40+'&%$--7>D<983,'% !=Ngmje[QJQYhVB6/8A=8803=DDGBJF5.488:=HD=<@?40+'&*,'(('>BC:=>8-*)*%'  t¿̚_NLNF:=HPD;5?IGA;@FB886,240+'&6DGB;@822<@?FMD=JTJUdUV\dtkVDB;@829?>>5=DDPVFWh^ez~vtaI45134422--/,*22223,035+&*+%%')-.7:47::7:88:FP\dnsRCVs|taZ[]UVZbP;58/134,1;AHOVgmjZ=2?VL=<@F=>86,*)($"*%+%',)*3HYWO6&'40>Ŀ¹ʇI86A=<@?4:=HPKckbaw|rkV8,-34?42222./6972222222+++*&'-322=@@@CDDD@@CSgtx|qOBZ}zh_YNP\\K;85.+,0226327863.02.+),++*%*&),+2?KGCLVD=@C=8@JPU\\RQgzV45978=;22<0269<63.5.14.+),)+,/:78:DACLONFGMSgvzhTDTybIDWh^K;97/+,69<;LbntxwjD>HYYSQL@:<=;232(").+),),69@@=@CPUVOBCLaz^8@DF?@:02622==;23595/+,)+,)+,1:7/686=@ONKR\\bnzhH6F|^8Cfx`J>66959>@97Aezlq~zh_Y[PGMVO@:ALJ=>66222-*()278595/3?@2#Pƽ֭kD>8026A=4/02.454/.01+'-59+'*.5.$+28=JLPB97869CVafma6/+#*:3,rĽܾfK;2-3<=800327864.01+,)+63.%+/=;2'-5DFJLD=3278>LVSJFJWYNZ}W@JWVO;-*14732722=78@;8986/+,/6880069<;Nd|nbnzhPQxc=4YtjV>22G\\R?@B9D]twdVOUgkRQjwdA3:LQRJBLRJBUpL@MSG=4--353222454859>=;<6327<=A=459>=HYn~t|lqmr|K2?nnM9,+:[qhK269ACIQ[VJCJ]q^ej\D5397OlqhhhY>+'*2884/)$%Uþƽ½tFVO;8,)+,1-),+22-%%&'&'--*()%&%5BL>+2NZPLQSJ:739=@OB>CDDDW^B>H>1-,0,043202222+569<;:739ACIH>69<;HYfxzlGC_zn?:ch8$%5Mq~lG.09=ACI]ZIDKexglfVOMSOVm~nbp|qP2#)2786/& %%DýýԪY>TD9=, %++*++*+32(%%&'&),++'*%%&9UgJ0CLCLO`S@:8648=>DD@:<=85.1@@CC=3978JctpD0=V\7?zS%/PlzrV9,:ɃSLVD86/".96,)+,145,)+,("$+2++*% .Mse>238CVa[I>=;235>HC:1-IjnL@MC81473288422<069=85;89Dd|}}R:1½ܼ]LbY>5.10NP?,)+,1-)*&)+%%,/++*%%7UgV0*69JWdVJ>=8327<:7/2WtmJ>JC4/=@9,+147?@26978545;CLC>@9\J8,+PtxQ2'$%),++%,/+'$++%*HrpF!,JP5.17?R\[PF?=;<5393,3Yn^=;<:15DD9,+17GMO=;<:DRQD=397/1=JHC:72;LPftmtfWY]ZN97^m82Ndkyd4&3IQap|kR@:8Jhzrllrvtstmttlc_YMKMD=;23078@4&"#!*:B>:޽²ʃ63J>1-9nj8$%&%##'$+%%&),% NƟd4!"38638ALR\[N98=>2-532=fmN45;45;F?595/>[eUGCB9DLJ=>>=222=@C82-/6CF\y~nmrrvJ>CPUVD>bhH2?^nnmrJ,0J`^bvlG.,9[qtmjnjh_`ddkj\LJ=>>>@9<1-,7?80%!##'8JF#-ľܼ]-3<528Mym:%+'$ .*&"#%"PșY+.098=>MbhS5+2822268Jt@38C8@D>23C=29Utpglnb^RPQTZ[I>778:<6363./688&##'%5BG(%ѠJ,3IG=Jn~Q&#*.*&"'$+%'$,fֻ=(0262Aet]8'$+/688Ijd41:;8>D5,9[p|v]LACIHI>6<=85D>89,+)69<;:FGIU\nzV>HC:),+?URAOUO[e\>2A^veLJWd}f]hnb^VJGCJPF?545485++0/024/%&%#$%%5:&%½ЕD7Qnl`jg<%+',0431-&'&)=|ϖL#$++0:[jG0*$22=QxwP.098>=8+2OxfM?@HID76954:DA<=.+)45;44:BLRSn~_F?J>/%%D[\`^I>YnhV\p|xv}}nV[ny~l[V`h^\YND>?@B@@3,34/%),+22,)!&'"#!") %}ýľ͔V\zvD*.50*69=6,'-! SڨW(%,04RW@1-,3RxgF78:638V\73h~_C=DPG;27854297861-222/1-29=AJcyxgK<=8800Gs:&>hzznlm~aJVhzva`[\`[PFNFF72,+'++0/*&)+%##$%%")*.*-޾ýƭtM94/%),043)%Yܷd"(8Jy^K^vrI4/.FbVAUí|zz^K=BLH>64548597861595/.01+386Nj`?:8006gW(*Qgn~zshYFG\ee[WY]YYYNDB>5.$*&"'%++*%(*%  %%&%#.010ƿ¾½ÿǹvOBC8'$+/++*#S\$0Cph_wb;-+1QnY_wdfsnS@:AA=45026A85695463./,04318MsmK97%?ve;4ZTZy|T;8NZRDALR\\\VJ>4/%##'++*%*&'"")*)%%&$&'""*.*'4»ƽɯcID>)%695%%,fٸ_"4k^==VhzM2#)>jzafc_\bpF=@OB<=.,04318454,043),09;8>DblPB6%&UzrdJ0=ZP]tskPLHIID76BLHQRJ>/,)+%&%,)+%&'""*),+%&%#.'$##-*():òg_YA=DLD7',LT NtmJ28M[qtdD*%,VxL@MQRJUh88JOGDF9,+11-,35.102-/-3<5>@BZszaMD3,=my[P>%%Bb^lqhchhgec_\_M9459>=@@G=8,+++*%*&)%%##'%%&%,)%%#),+%!*/+,#KȻþľèlGVte>(7tܧP&.Z}fKONK=@O=+.DvĽN9GMHIIJyh>6232-*.*.011-&-*104:BLV\ebP=4--\wPJ>$%Myz|d`[\[\[hnbI2-/9=AAC;2-%*.*.$%$%%!#'$###'%%%&%,)##'+++ lúýwú\>am:>֟J#>}tY_s_6,327Fn¡g2321-,++*+3(*%),++6GHC:AC;2(7}f>@9#1]g_\\bc_hgVA.+/;CD>?8,+&'&)+'"#!$%$%%&)&'&)++'*%%&%,)6ǹȾͽ^_ƷV,0^b, JӉ6,ht_bzh6,34:j¤sJ>FGI>=8AejG=JSQK;2454,.+/++*%*&)++0:B91-,35+'G}D*B>'=fzrd\\\_Y[VPG;147?DD@:1-&-%&%,$%$%%&%#$*&"'%'-+'*%%'$+%'Vý½²Ͻd>TS%;hzU8Q\.MkmhhY78:$3Rlqhc^KA=45;898:<97A<63*&"'%'&'&)%&%,)#"#!$##$%%!%&%#$*%-þĽľľǽ65jzP&3h|V:[I+^}UC;;Cc־W@>@BOUO>662:DJ`v}e[_YM:1),+223,%&+'*%++*+3(,)!&)%;tdID7++PlwdVQC=398=><9=<6361-&-%&'&)++("$%&%,$!"##'%%% '-!%%"!TĽƽȾ»¾żJ#SlI'5bnZ8+a_(PeL?:OʞcA@GGGMRJ=741333F`ke]WPD8+,.+)-2+$%+)'%+)(,.0-)(',(!?rjF,!"2TlcN@G=7CDCDDC=741/-)%&+)'&+.+'%%&(!$%$%$)('%&%&(&%& +¾¾žøz/8VB'%=@6*9}|/>twWA@hԷP>FMRRWYRJ<3-2-)7DO\WF841/22,.02+#%+)'%(,+)'+)'+,.0!2eV7,.;ZgG9=@>BMMF@>B<3++++)'+,,(%&-)%!""%&(&%%& ,(%%&(&*'%(#\¾ſøͨW74&%$%2Oѝ@6Y|pP=JɥpPIJFMW_`\WG9/-+)2=JUP>-)722+#',.+#%)('%)-,.+)'+,,2,"%YɬtUF@JgnI>FD>=@C;2274&%&(+)'+,,#!"&%$%%$%$%&(&%%&-+)#%)%&(&%%:ſſď<%%7tֵV0FlcLF`ánSPRJHU\_`_Q>3-+++95550-02+#'%&(+,(%%# !"&)-,(!$#%%&(&%.+'%& %&(&%q¾ſλw+Wm8@hnbd|äjNNTQNTVY`e]F82+,.0MRZR:+)2,+$%&(&%%)('%))('*''*0.0-0+NØx`\rhJ=;955502+,0-(!$#%&(&%%)%%$# !(,+)('%))'%(#&+)'&#%)%%C¾ɽȲh(%zυF@j^CDDMRZZ\dg\MFD2+,0APRJ<2+#',(%%#'%(#&%&(&%%)-)++2+,02Cyt^_jN-)7;21322/-'%(#&%%& %%&(&%%)-*''*%%$+)#$%$%,.0!%&(+,%+Ŀû¾ɫwJF=.%&(&"%&("%%$%$%(,+)+)241/2@bfMD>0'2>?:221*%&(&%.'%(#&"%#%)4742,++&%$%$%$%,2,+!"%& R¾¾¾ľwDVrw^_bnxwlhkeghdP>-'%2=FM<.%-0'#%)%%%$%%)-,(,47647J\qjUF4&+@TQ>888+$%%+)''%(#&%%%&555550+$%%%%$+.+-764(!$#%% 6ſ¾þú¾¾֙]\vnZ\dqxrvtkeb_Q8+ 3IRJ<'%(1*%*''%"%#+1*+)247>AHNT_jh\M2,?chS=746*%$%$)(&%$%$',.29552,+'%(#&+064(8?:2.+'%%tſžøɿſܴ~enwcTQYgzs|yq`TQO?-!(CWYG1&%)('%))'"% )-50-0+13=@CD>I^e]L38ZnM<3550'%2,(%%#'()-574:841*+%&(+,13=9=JN@95/-'%D¾ý¹Ï\WtrhUPS_rzs|tm\LNTK9+?cbM<0'#%& %%!"&)$%$%!"&),.;2,+.25=>?IDJ`kfM06\neQID:2,+.13+)#$)-,19=64221(!$(,.+*9FRWa\L>3,(%5ɽſ¾ÿŽ˿ɓNFl}spnjddqvnbWPRRJ<2FlaD2+,*''%"%#%& %%!&+./47622/-)+(!$(,%%$#"%%& %%!&++24762:@>CDKSV\dqT2',89=>?B>=64(82,+.1,.02=7466,(%/4;9>Parvwn[B64(%\ú¾ȼƾ֭bTlwtzxk\Z\ch]OPad\MC;>BF@13+%!"&)$(&#%%$!"%%&(+,1227=@>CLSVVSV\M<0+29@GP[\WG=7466,/-+,.;295/)-,189:@Oevtlc\L>13+%5ÿ¾¾¾ɽÄ[a}t^Vh_JFPRQNPRYRJBDCA;8880'%%$%*' !"%%*' %-6/-7>A>?IU\]WMRRJPRN@FRVaglcUF<9=622/(06742-)%+9=>:@KaqtzlYM<0/-+,]þʾľ¾֧\BZgneQNPehPIJNNNNNPRJFM\WG9=62!""%#+%#%)%+)'+,,219>?BCLSVVNNNIDbvRR[\fje]LD>9=74/-7333-+%&(7>F@>FTgnpvn[F82+++#8¹¾ýſþt>8>FTFRktz}xk\YRJMMJPRRJGYpnL?BD5%#"%#%%%$%*(,+/-+9=62=@GGAHDDJ>3\cTQN[]WM=7C>=60-064(,(%%,=JB>>FTZ\ktnh\B>5%%%fùýÿ¾þةh>.2=7PtyrhgYMHNRWTQNKarhC;YgN-! $%%$%$%$,.02074229@=7:@KGG8.IvaJFIRL?339=>:642-)+('%(5DJGGJNW_^ef]WP>87,'5·¾ſըd0"%8Vpzvnkm\JBFMMMJLSfxe=@ws=%%%$%$!"&!"%%,.;2933364:833FHNB606S}lNNNI?501389:;92+,0(,+/8FMMJPNTV[\WP>883-0'cʾ¾ϠV+%8Vptmb_e]L:@@>IRL^qf>PQ+$%%%+$%%%%& %%!&.2=80067:8800=JB@><.9YpeQ^_M<0+227>:8<358005D>8>PRDDAHMRRRD8,.0!%"(ľľ˿Цe18Zjtt^QNPF82=@NT_jtnS@bʱrB1* !"%%$%$!)-57501476=@84;DJFB>+);LNQRRD8,55579=62==@8.Op?5GQG@>INTVQG9+-2-%Vý˿޸r24[vR=7:47m[F8(!$#"%%*+)#$)/4;47<888;927>A><9.+-2=FF@>64:>8>;9>2988+)bL'=PRJHNMVSLF9+-1* 8ýľøݹh(&T`>0-069FP[_jcN38a|bTC;3+)#!"&),/-'%*0.7>A>64255025551*0-0',8B>54787)(!"%+)2,++)24774,(,.02071*0)(!%->=60:@@<3582+,0(*'^l-2?A@=7::83.+'%%+tžþſȾûþ؞c|̱gG2,+339>?PfjR41IdqzjUMF>82+,# !)*0.+++*0.+-)%+++&,.%0FTK92=<9;92.+'4)('*"Ṕ227==@>742-)(!*hþſ¾½úþϷmN2,+.147@>IU\C/>Sgzp\L>?:22+#'!"&),)('%))+)2&%$!*9DVstC/=74/2+,**''*%$=Ў50?A:@@<841%& >ſ¾ʿ÷½þõ`K9)-57-)7IJ>8>22Jbnn~bMA;8<1*%*#%%$!%%$+#'$4Nq߭b=@8.+)2,+++++#,Ҡ?5=74/289:;#%%$H¾¾þ»ܩwWH>6*,450+8DD9+-*0N`\aglJ5-)+('*' &8F`ЈN@92,+.1,)('*" d].+41/,45;2+(&#Qþ¾½|C/==7%+2,+.146*%$(5MRRJG@2+#"!"%&(",=JbzݠV>8>222/,(%%#'#>և006227642-,!"^ÿ¾h2+,4&$-722/$%$%#0=603-%& %%!&(ҳJ&+./841132,%%g¾ÿh2,64(%5=>?5# !#06*,'%!#%bɾt.+-222/(&%$%$'$R}3%%13241/,,!"p˿ƽ¾ÿ¾ʮv8+40).>>8?8& "(%%,#)bՑ3!%,22,+(%(%(*'->!04686/)(%)t¾ÿüý¾¾ƫс=&,+*1=9,22($IY@"%2?8kP#(/0)+(&,,#$'(j;V!+4B<3.'("kýÿļс<"(*'/0/)).%!NR:0$).=MM0$Hф2$'%& %+(& (/!AӍ0$5F>6/)"Yq3!%,2-,+%&'>r`S;/0>JSR? 7]""#%'()"$'.'%pϽ[%,;=940$Füýü׷a+$'.+&%++&+xڕepqVLNTVTA+_צP""%''()+&+.'=̊/+1=940$2Ľƾݸ])"$'()%'.,#$QeSnzmcbb[J,#=֟F%''%'.)(-,$/r@$22572("%x¾ļݸS%&'+.-,$+&2ڗTVtteflbD#%tΏ8!%%''%+(&.,,+A[%)6/685+&[ý¾º·حJ%'.)0/))'LօN[tte[gsb;$Iy,$(%(&,,%'.)0%,mj5+726=9+(?ƾýľ֟A$'%/0/+,#$,nnC\k^QVksV,2v޼^$'%/,+%&,,-,$Dc]<.3.186+&)ý֕=&%'.)0%+&C]:Wk^S\kjN*Dٓ4$'()+'(378885>D4*1-,3.1%nþ¾ºΏ8!%+.-'()]ܔA5UeZPTVO9,a]$'.+#%,24646942('(33.1,+%Jÿļ¾΂0).1+(& ,n18SRMGDD4!0Ј-(/72)(-+&!%8?<.+.-220/-, 5ÿ¿¾¾t&(/-& %%NV+:OG?@>8'PܰQ$'.AHHH=,#$,JPA+).1.,,/0/!$fĽÿùh%*1-& %(yJ*;D;4694ẗ́;$;Q\YK9& 4PTG8,#$,*1-0/-#:¿¿ºüýW#%,*'&%9؝=&885.3/"6کS0L_[J7')GZG8,+%!%+225,#$Vÿ¿ļ@%,%&'"bЈ/"464,+*gݸh>\_H4$'=MG1+.' (+.-2-&"# )ýÿľºľօ.,0)&%Jf$46(%(&DςDND/" (+>>2('+":W<"(/!"# cļ¾ýļj$'%/,)"(I$72)&%+ܙD/+&!%+0/-+&+$mO"(*"# "Gƾºº¾ܫR%,+%&K܎/(6/)(KܢG !0(%(*+&++>V2ý¾ļº¾ܕ=+,)($(j$+1/) )۝=$5+"$'%#%,,Y #vÿü¾n0$/0/+ !JJ""+(&M׊/%,+%&!*x] "#^ÿľ½P%+068.,n׍2,*'$ws$'%#!0c"W¿ƾüԝI+,.37,4g%"(*"8b$%&'"#9e#!lþ¾ƾ¿ȇN72/0/+DG %%' VٓI!%+#5h%"";ƽº½¾þºxPA:94+"_Ӄ-%' %*a+ >t+ !m¾Ǿü¿¿bD;=B<,$qQ!%' =~7$ ;օ. 9þþýüļײY0/=9;/!0~փ- '()SՑ= "# 3ٍ2'pĽ¾ÿľý¾ƾļ¿a-,=94+&=\ #%,#`ѠP#"'(+؊/ "Oý﻾¿z>>K92&%QܵN!& %d֥T+&!%%&"(y~- -Ľÿľ¿ļÿʍLDO?8*'_ܸS%&"mg1%''%$*tx+"Sƾǿļüÿž¾ý¾՟S;HH=,+hش^(%%!+ܺr;/,+%&%jz+¾ļüüü֮V:GR?).tٟQ1+.!%+M޾|;$,+%!ax+DüĽýýس^AQ\P18ԘH+.-''(!x~H+!%%Sd,yÿýֽrQ\c]?@צJ%,2,+5Ǿ‡D,#"A߳I?ƽüˑaS\ZGJۼ^%'.0)SƽʉH+!9۔3]¾ÿխnIINDJ؄2"(*(tˊJ*2n%=Ƚüÿü־|DDF>G~ܨS%"-,2ϔA%%*xG'yþſþ¼ý¾DŽMMG?Ds޿v2"-0$?ΐJ%#%*sڄ(Dǿ¹Ĺ͍SQH=Cf͍9%88*FΖK%# "%##lڹU#!}ƽʿҖSF>+><+F֤Z6)&%#%'%[ٖ7##Aʿǿýþ¹¼ҟS968+@ܯmJC:(&%'-"Jm""%}ǿ¹ýبZ65;;F٫O.A=)=غtO\P9+(&&%2DSý¹٫]68<68|ۭS.5;+>z>7KD.%')&)sЂ+ ǿ¹¹֮a42;53nخS.061AD'-(&&"WGD¹ܷh61=>7dڰT).52Bخ3")&:Ї*s½ڽt:0BOAYܷY-24+9~΃)'%jܯU5ƽýؾy=6JSFVٱZ652 +sV6ҁ- \ʿýƽúؾt>BOLJ[ӭY880jܕ6%jخO!5ƽy=M\PMVܲ`=>2al$9ׅ,Mʾ¾½Ľ¹½ЍGRh^VJ`ڴ]6/-"UDž< n߷Rn¼þýқNQml^>FزS "&%FϗB  ;e},!*ſþþҘSQfg\?AݳQ'%8d&#5kҙ>! "Bû½֘VO\aYB=hްT%#,#,Ľܜ@(&&"Pٽa!%#_ǿþ¾ûýϟ_NQ\U@7K֤I%).!#xýt(,2#Hϗ>'%#xʿúþ½¸ϫkTRPJ=6=Փ>"'%`٫J%#")l͇/$+(%}ƽþſû½¹غ}\LB=@7;́2"'Uև5&%./#S͍3'%#~ʿýǿſýƿ¹¹șhD8<=6Fw)'%QňJ=F>:@7"!jМ@$#þþ¹þַxD880.\x&*'%Oɤzgb\PM1@Ӟ?4¾Ŀþû¾ÿȍUF>/5zю4 "Pӱ\LZ^>9τ2Bʾ½ʾƾǿ÷ȍ^VI3?֨DlڰpF>NL',]!#sǿþ½ʾ½ý¸ȅZ^M2BߵO=կwTMD)+z٫@_ûþň^gV15J#jеV8*s}'&&`ÿʾûĔvxa?Ak?8ѷv4>ެQ^ýǿûþäq^[_czʝ7Pַb$+nۙ>#ûʿþǿĿýƤvWR._ټĽșs׍. Pƽп|}]Kx},%Dƭɿڽg8÷ʿʾĿĿ˸ZPh%&%!%8hȺҙ9'%(&eǺƽ¾¾ʾpNYV+-+"!\Ї7 ;=0,+sƾ¹Ŀý˷n[D\J "7=6*MҘW=MVC/-0ýǿ¼þļþ¹˻bJCjG$+BOB/^ԵyRZ^M,#HýĹʻ~Z?IǫT).IPB/'s‚V[M2$ nþûþƿýӾvWFeԯZ06IKD.0~HC/<ǿȰm]e֪_:@HC>"?~9%#}ÿ½ſþťpv֪eACJC3#ey,_ûḻ̌П`FLJ2 +Yc*gþ̿ϓTMK>+-cp%%>|ÿ¾ˍSPPGIn¬զncƿǟxlsļý̸ɽ¿ƽúƸư~Wxʳ}Ⱥ¿нɽʽ͵ò}íȽþ·xtljžȿùȹù·ú\S|ļqz}wz˽ȹŽvD6T}vJHvíI*B½~}vprzzȽĿù½ù½Ƴ¿¾õƽzzY*8Wf\J//NehhpzqrzywzP-%Ch}sqkjqrrkjgb_deb_VVfla\ab_V]WVVYWVZV\ab_\[bhfehfeljd_[Zeqrehqrvxjlqrx|}Ⱥ¿¸|[2';MUH50;MQI@DKMOVSGGMU_dde^\abgbWK>//7JNLVSTUH@D:7<>=647<=>=8876:70.12,,,,,2,//,,,88760.57<=7687>:2,/88BJV[NFFFRYR@.%&3AOMB=6.1.181).768,&%&'(*.1.&%)" ###'(12.180.54/6:>:47B=:>B=AAAAAFO\nnTJQ]W]~W`jlqfe~vM1Ŀƿȿ¿ƾĽĥƿzD,:[bI0O}pbhkjgmpbVD=DKNA80(7JK>-%';FF@.$%6H@=DLI@>:-%'*.=DLKMLINTUPLIW]W]^\^\SO\y~vpwztlwzztntzwzzrkrz}zx|qVDJtn\8ƾ½þú˿˽ÿ¿½ƿ·tl}qJ2;U\J8W}stz}zsq^OMV\\SC6B\hcUHJQZVJ8(*+.,,MB@Wktq@ǽȾʾǽƾý·ȿıq\SRYRfvpa\[ZRYRIKWpgbmpvx||tzktxtncUPSVS\stlVOVh}~cbw}vphP3% #%&2;JJN\n{hA'2;ǾþȾ¿½Ľɽú·yhYW[ZRV@47*.T}wprn|x|tzv_TZenlaeev|fNLnjlxtgbpz}laWQPQIDKTUPWQPhx~hA%*8>5ƻýǾývp|thYDBJ8Dw}sc[j|}ŭsYW[mpvlqeqx|}zr]Wcs}~vsv}j_[I**8<4&¸ɽʽ¿˱¾þžw¹ýv|ƴĹ}}q\[bmvpagq\J`|}prwzxbhnltsjvS2' +32'­þƾûòƽ̸}qhsyh_gqzscbbmpvl[bc[aqrrpzywytz~O\yV4&.%,Hнƴphpzžʾɾñͽ̲gqxsqv}ywnynnmv|tqrrxwd_nz]WO+&%%  +GlŵvzǾȿĽ¸¿İJaȹyaq¨nĽvf|ʽ~P-+&" $Hkùпú½¿ȿŭпWkȼúþŹƇMOnZnʽѽnR2'-W̽ϾʁſƴϽļvph]gt^B%%>]nn]WVVٽž˽ýþºžt|}ha\[Y_[V[fela\[YTOM\b_VMB@GMLKGG;0(+3Dd_J@. &390(%(*+¹ʼýǺºe]nn\UO\r|s}xjhhn|wztkehfeqncrzhYW[b_VMVS\TO\bZD6:Uzy\=+.4776888B/##//%##'&%zk\SCDHJH<>@D:;MQG;DML813<4-+3<9;0*8<7+%#/:70%#% &%))"# " %,,,'!+.( #%%&' #%,:DH=+% $$$ #%%$ ++&)..1.YJ90+.(03DH=BDH=:>:2+%)..//,,6:+.=D?89BJNLDCB=:JSG2 #%,,,'&.+ ,&')"#-9;.+39500.54;;0/6:70887.*&39.%%6=F;0/:76:+,&'" ##'//,,&%#).4//6:80" +.180#!'&.+,,,8;;=6$*)' #!!$$'(%+}xzſýýýýýýļþ¹ý̽ýý¼¼úôþ¹ÿȼúɾʻþúξʽ»ÿý¹ʽĹ\@8A\˾G"4tʽ»þϥVcʽþþ;Sȼþ»ýh,9ȷ]+^´ʽd36vʽz>Pzúzl8",w~z^KJ<% \ƻ~t>#)("Ný}~z}êb,,,) 7ýzt|~z=%>8' ^ſ~[('B;' JòjbeZ3#)$  @qeeeQ6<ʽ|O/ #:|~tjzýn= =|sxree\Zt«|>Dy}zw~vcZbegzsÿ8Súy}xt|zwt|aaz2_ó|zktíU(tǴ|zh%2ɸ~bBy}KUwvú|3%lɱW3ſ~wbez5@»}ycZyǪ\R}wÁ0 l›K1³wV=JtpnO Rɸz}zz>8hDJ\%ȳ}|z}I-j7Fh$@xzj+88?rzæl2!%33Rýt-0\zsſ¾[++2#%yſ1,ıO)[ý¾ž¼ýD,<._ſw+N88ſþ¼ƺ>7F2Bľm+8ͼl%Jƺ¾þľſ|:=SB+n,6ɞJPʽ¾¾̷thlszzxtv~ʦ`,!=SA"%jʽŷt-!,pּt-Uſ¼ôø[C=3+(+,./-059?HPWbnxt[% ARC%[ľ¾/#NիISʼ̿ŹhP4%%%%(+./673+1/%DVH'Jľ9%2ʋ,Pžľļʸh@&  "$ "#)& &,.+(2/-1/"!D`P/<|D!%#p[P¾æzQ7%#)./%%(+.,.+/-160+,& &+.IVK1+}̾P$'eߗ6!%Y;ſ|S,!%$ "$-0-'%%((+.,./61/"%%"$-'3Q\I+#pȼa))&#`k$ +, cľþæzN+#" "#&'%+(2)&& &%+22/+,&%%(#*ARH0Sſq. ",Vn<(+.,e¾¾zA,###!%"$%%(#(+***&',./-%%((,./ "#44$ ">ʿƺ6%%(,VvžǛY("$%,02)&&+(2),4==3+%&',),./.2-0-0273.& &%%02)*%!%OS'O̾¼ʱ\#).)&/3,)'+8DH@>>=:4-'''% ,"(,32/'.**,,%+*/32+,&+%(+.,.+,/85,,59/&%%(,0/-%,BvvP/7ŷýз]"$--0573.6<../6=?AH@60+,*%!8»ŗZ%##'&,1/+$'*+(#).))&&!%$(+&',),-09:4)&7A8,.+/,51/+/&%1FF87>"&lóӯS(,373.68O\I>=>>=:IN@6./-1,)"%2Je¼ǭs7!&,1,.+/ ('%$%+*/-% "#&**,,%.5?A5,,,ּ̾}2)1/59/5?Vkk]PJD>=>C=;;;=3+2302,)=hþĽý¾ʴI+"$%,032++(%%((,,./.'&"$%1/"+273442308DSM=323021/+/+4[P%%|÷¾|:#).)),5:=JP_tvk]RC875;;=C=5,.24-'%$+BPmž̿c2#390+'354-'##!*+,&+2/+,)"%%%(/1)1?A5./FNFA@>MWanhVVU?.+23+&,1,& &+(+6D8 *¸Ɓ2:\\IIDBJVanpnnfcnrhP0+:=4429:32+2671/5% "#4SþĽϗF*0+Ahz_:-0-+(#%,,./ &**,,02744:=?V`]PJLRQOTbtv^\\L;328+(#+,&%##',5:7%Pȼ‡8!=S\\fkdYWb|wfRR^jm]9+27,.28752-+,/,51+##!!%=a»ýNJC)=?BPnj=,)"%%%%((%&'%&,1,-39;;=I]sp\I>AHDDJTbk]RRQI90+''%++%& 3B:-%.|ͼƻ7/8P\Ze}|_SawzkVKT]^\\J;302742385,,&'%1,)"%,)'7aĺ¼þ¾¾ӹ|4(4=NVWb|e6$$'*+'%$%##'&+(2)18>?H^jp\A8/18;HPT]`]IDMO@+,&%**,$ "%2D>-'P̷õ?"5?J_\FPsgY^hgYOP\bVMTVN>78:018:882+'*,'#%)-0+% +FpƿľZ%,68=LPQczc2$%*,-*&%$%%**,-+',6=CNceWJ9,',6HUeplb\YD=CJ9,',&+2,'&+%)8==,'">ÿµ@)(-8:>KS\nf5&+-,'&#%#%#,...% )5?PVVN;.$ +Dh|xx}lbYP@@@701,',1,',1,.=C?.$ 2J$1Se]M;."0SefrnhnsplPBC>?JDJVbV<68468462231,,.1,,-03178.&`ľ־a1,.1:DJT^h}d=,/-&%"%$$%%/-&"'5DJTJ9,+%%5Tx~}tkdYD82522-*/-'#,.1:>K;."*vľür'#8D?8+%%$1Mbgmgk~plPD?8DDDMbzsSB82522359317360++',23531$"Yʤc2"059;BLP_crz\A82-%"%,',&)-'#,;R_U?.,.((?r|x|s`C,.11,,-+"%)+,6=70&+%"Y»W!$1,.(+4--8P\^Y[_nh]VVMDJMGFJ_vsZB83173/-/-6846+',+20+++,"'O֯sB%,317=LROP\bxY:068)!$+446,',&1J_aN>)!/23>mqkdO4((/-&"+%%$"#%/20+%$$NûJ,3*8CNZOAGQQQPV\VMJCIPNSVVJNct~p[G:70,',1369;94+%)-/-&"+)(&VּZ-!$+78./>RZONcnN-*/8+%%+,46,)-8RqpR2% )2>MlbVN>))-/,'&)(#""%,/-&"+%H˿ĝL$1=8MbpvfVVTI@K]_I@C>74--+,'&)%)2$">պV+"!$%)2=>744DT^Zbxj8%)2/'#,,.(+,.=Wx\9,+,.=Rq}gYO>2,+54--+*&")-8,'&#%$B¿Ü\=CJLWeppd^a\LCIZbIIPA8>MPFCITegYOcnm_I94+,.11(-468:82+,'&#%/-&">[zѷT6%&+226>KD=$%0+%&31$+4=CPhn^OPVbz}yz~q^TA8-*/-+,.=3+"!"''*226,.(+,Tg`cef]V_nnhZWYRJMY^[TIGD?HIIPV`e]ZbafsfVPNA5,%)+)!%/?8+,Aq̭wB%%/7846>?CNSKH[ltvsp]<+',+*&*&)->MJIVblttr|dGDF80+%%**%0:0'#%)+123.....'%qȾYVVWTIG`qeRJNSTOJNWTJMTOBCGDFP\dYJV_\VV_caN?8+78.((%**29;BL[UVU\bYD.$0SbVNKSz}^D7+"!",.1123>JC?PbVBLjtcenV8+0++84(#%/=C?<6Nt~}gBC@;9.)(#*&)?DWeYD?JQ[_PJVKAGWQ>?DIIIIPLCRZUOVfc^TADPH6%!$%'#,,78BLDJM\bVB.$ %**%%*6IV[TSVL=,#%/*,3;841278JMDUj\=Nm~^ar[8...8D?."'6>A8-8V~tr|zqz~xc=2>D=2+,'/257D=2%)+14684()-/,"#0ͪhJVK8+0=LRSKJMOP\SKRJ?>JI>?CGDF2>DJMOQ[eg]VPNWeY?."$%%%)+)226,18CI=,/,.(%)+1)!"# +,+,/-/6:>?46>>?DRZ]_Q>Qv|_cwd=-03?J;((/6>=22Kgvsjdkz~sphnm>'*8C>2+%)9GQN>/-&-09;78.(*,-+$ AǼčZAG>...=CPQQZONDPHKSJ?8>FJD?8D4(2>JIGQ^hd^b\[epp]MD70&+',+;23.01,1,,2262,+582-)-/,((/-*/-+593226?D??Gbr`J9:ZztkeRZkdB226?=,#(-4226CYegbg`WgvtrjJ.$0825+,6I\hW8+,.11841,'&)22-(Sû¢nND?812KLCJCJND=DGJIGD=<9;B>?4=222BLNSTgm\V^aZbtregk]M>2+2>D<1,,2-*/8226?603;854317-03...801;9.,6==>>Hk]<3Dh~h\LBCVVJ>?C60&%,6784DGPLP<11+44=>>3;8Kgtwyzz~t^VV`ecU?A5?Pp|J$ 127=>>?.,*,-%**22ÿʸ}VbTC><;BHBDDMFJMTSB=8589;226?=8==?JDGJJIOPV_pRJH@@<>FLC2+,/>JH@..4?Jb}rnnmnmnqp[NLP_bVN;98C_vsP+"!...87>?2+,/+%%+)PľµÇSKYH6707>A=>>?JMOLC<;;9.2...8760?<;;C>JH@CNSVV_PJJMDUZOIP_ngYbgV;3;85:71,,29;7593278.(*'#,;F>8:>JMOLKHJIGDVb]ehPJJHB?>BC9,+,4FLWelbVNFJMNS_UQQJMD<11+0:DPD.$ )(#%)2=HB82-)((%%&t»»˩VFYI@9413;85226CJIGD<;?84+,/1270,.=3=CPQOA=FJMNIIPLQ[]VPLWhxxhdR=85>7444--46>A=4-59;4--+*)5DJ@;=>GKSUVUPJFPcefngZWPNIII>?2*&2267>A>?JRUOOPVI;39@@JVWTJ>7/'$%0*&)"&)! 3L]_Q:'*2&%=ĸʵzJVc>8>.+,+..4684>KD>?CB83-03./25,.8DGJJVVHB?C>GKWegYOKSjbg~yh\VNLPMJ9<;2,...876:>JWeYJCJIIDBCGAHB?9,+,4+''*+"!"!$DfrtkO4...$YõƑVUj_?><+%/78:><;2=>>;BC60&369/25701>?JMTWJ=8@LPMJMT^nmafs}dB22>DC>543784A=4543;9=4--4226?=88==6>K]hn`MLPUVkqePBBLNTOB60&+',2127FJMWJ=>?CGLC>?CGLJI><;?9413235C>/-/6:,6=D7-09FJU\`MLKS[TKHJ[e`W\ntqxG+4HIJI>825888:><78==850++86846218;98>KW`_SK>GJJn~nVC65DZbZB.$0,.(+,58CD=D=>>?DJTJBC946,6/25,!$#*&!3n`M>2++"!6ȽD.CND=.+6>KTenfVMQQZMGPTVOJTec^}l>.=TVOD?8784<=C?<8:8=2+,'+,474--4284:DOPVI@@<>BQvn\F:71F^aR=+',21-*/-+5543;@KGJRUUOD=<9=5,,+%%$"%)+!$%'!$;nz\;(%)2+"MÿDZq82JH@2+2,.8:78822627842-09FVF5,6>KRZ]SKJFJMNILPMJMMTWal>8DVVJ>=85059@@<>82582+,/+,.1:-03841:DOTOB>78:>J^nvfL=882BQN6%!%)+1)+,/1+',2Q[VNDBA==CJLBCILPMDTkb;BUVJ9:9;7546>>=8589=8/-0...876:8:828DNLFDBA8:>7>?C6317)(#*,.11(+,418CB8K]_?*8-%"7ʿvfjdb\RqpN-"''1,&&+21,,2--03."''1!$+>?707VeR@@D>?4=BC@>?LPMDRq\=HUN>/235301;:><;?9>?=850.49;B>=>>323>C>88:>JL]M31738==/-/6/-&-,'&)2+',216>AG:468C>/((%%%)2/317)"'6FDFPcVbcD%*78. Jµ|xqzz^;(+44/-&-,+%)+,/-0.%0Mb_?.C<11J_Q>HIA==>?DA=FJMNIUty_IOJ941320++86>?D=8@LD??<11>?DA@@<>8464:;39G:46>A@KVF5784A@2$2KL8%'/2,&+21'*24<3+2841,"%/23.0-'#,BQNOVffcK"%593+"!e|}nF/257882317)"$%0*%DtľQ,3..;LJHQ[RD=DI@CBLNJH@Pjj\SQD9;7542,+5A=F>?=FDF@;=46>ABC9:7=850.68)3;85288>QWJ=8:>JC,0SV;-093+(-4,'&)2+78.(*12,'""%,/)01,'#,BKHJ[`_H(23>70&+,zswS5,.=35?HB8+%)+%$$'VԥW(,.118?JQceOAIPA;BUPF=>Gd^SPNND9;7@2.4@KG>78?DC><;46>>=?>B:7=70,361,,295431D`_SF8=CI=+"+FN=9HL=,&%%'(% %%%'"&+%%%$*)(193(%,8@CGPUF&?P@842,&9}vlO=2,.654@J@.%'+%&5Pָ](%,25999M\b\KJMB;DUO=<8LhcTMKOG8@J@54BDKO>799>AB;4259C>A>AB;42+,893,&+.-,-5Pll\RJFGD:(*AYgV>-&%%$ ### %" #10% %"%,81*)+;DDKNB+=_]@865-MľkRC8+%19=984.%$-bU +4:99MQ\bSKOGDBJF>93Hh^PKJIGDZ[@86MKLOG?>7:9BD;44:9942;=90+,,811*'(*)+,3G`dWMBC>A5*"&2:9F[nsgVF>1079BD;865-35459CI=,+2,%%*)+,>A>D?+6VgV;4:91 h¾pUOL6%,>A>8+(%"8z֛= 19=@JLUb\JFJF?><=90IkYJMJF?SgV;=R_][WMJF<8=CGA<8=42510765-(%,2+%$%%*,-,3DKN@=9842+$%*0A>7:79-2542%'+-&'($*.-,&+.6=9<=54+%$&+,3D[ntwyznhhpqeal}tY<8QV2%,.-,/!%Drʿ¾վf%$25=CObfM42;742;7=RZB6?GHWdL@]mN==CONJ@:9B;=>965-3,&+.)(%'+-/.,&+*06565-3,+.107F[mnllz|xtmhptqmnzz^BJc`A/.=C8$!8_tmJ65-3,K»M;b}S6?cmN+'>VxǾΨW (16?KafU9)(/.22:HQJ<2:B\qeCGzYB;@JLH>7:7=98465-(%%+,,+%$&,-42,.%,2,..-,,-4219Penlnntzzztqtzsspllnsvttq\KWlkYPF>,-<=- 4Nhj\J<222*g\/5`rA'(Kkd58g̿΢O,-4F[mmS6-,/.,:WWD:19FzxVbyylOB;@CGA4:9>7/.22,&+)(/)(/)+,,+2,.%+.1+,,+(+.1+AYjjjktw|}vwrplllnwytqlssph^RdnlgP93,3=2%"5P]c`MB:2,.%7½ľK+Y}J+.C_mS&%V¾ŻΖ3"&2.%$0F[VVO=<1*'$%góU %WzY,3Vgl\G8a»¾¾վp'%,.KxcGDdncL6*cylZH>AJF?862,%107+,,+(-,/+%$&$%**)+,%'",-,"&210%%DUOLD?93,&%4ʿ½»O"5J6$8_h^>9nǹttє=$2<^nWMmlKB=2O¡~bMB:>7/8]wycLB;465%"8NTMD?8+5/.'($+#\ľp/)(/%,8D?2FȽþվz,(1Sg]GPjjkbMF>8I~ħy\K=9<>4+.OnztSA>H>65-3,++.1++.),(+.)(%"+&+,+,,+(-,*)+,89?PRRUbx~xxvvtlnnnlnmtzp]MBCNJ?854%'.=RVO=90,-,/)%'+*e̿F%Lhlƾֺe)'>`q`\bbM=9J@:qʱ`PD30<;4/5Gf]@?GB45:91+.1++-,/++,,+(%%%*,+,)(/)++%%*,8>HQJOWlyvttzsysgflkjjks~qQ@BDFNJ630(%,@86DYJ54+-,/++%%%Sľȼk.1ľƵľ׫R RzzvzyU9@JL]jVPF2,107+8DjaMGDB:HKB=.-210101'(**)+,%%%*,+%'+-'%!$*.8DLIDKNhpyyxk^^dgnmhddc`gnxvV>4@PD1*,-42,&%%.Kr¾ľh7!$,aĹƾll͑ACt}J:HKdvUFC>74+2,++8nxb\Q@B:HKC8/.=2545*)(/)+++&%'(**'(*/+%$&,2:CLOJMZlsrphYJLU\bbenmhdnzt\=2HWSA3(%'3=CG86?G9)(1654+-(%""%=Ytʾº½hhyĺj\ľ2-\|rKBOWwV><>A>994101>n\RaYJ54,-42,892,++,-,/)(%)(/)++-&'%,868>DKNT^hphh[NBJU]]]k||nlsrZ=,2JUB+)!8p_;448>D5*)-542+,&+*%"6eϾƾ½ľҼn,&CthNO\pxa@86D?>DOWV^^d\RJJS]cbfs}vwhS9)(,32,% $tՈ9)59:D:1(+,-,*)+,/.'!+Yȿƽľľִe)4s}YW]ctS99>D?DD?94:J^^F656DOKBOSK:2+)(/272,%&%'+*)+%'"&+,+)(%).6=>9BTWKJNTOMKTML\bbeg_QM4 %"%M|4$*.80284.%%(+,*)$%%%=VF1Sʿþý¾=RwyhjspleT?>OWPKJF<<88HLJ<22-,/+2,.%+$%'"&%%%%#$%**-,)%(16;=>DKNI=?>AJSOGLOSTMLKOPF2+%)$%*#(}˔G!"&2,&%$%%*,+% .-'22$$ľ¾ά\8Qssj~xkdj\JBJZTMTNB@=7(%"+&%*06=C@8+,,+(-,*%'"&%%'"&%%"&(+,*,&%#)2:6>GD>NTBD;=L\b]G8;4/$_ØfM<*" %%%%%(*)+,%3ȿýзnARjlsthheTD?OWPIGOG?>5*)%%%%'(19==90,+%1,&+*+&"""&%%,.,&%###)259::9BGJHQJB;@7HNJ::9B;;4/,02@CFG@=>7/,ͻtL+ '65-(.-'%üľĺ˥_]pllsgJ@:JF?>A>JKJ<842+$%'+-/88922,.%+-,/++%  $%%%%%%#$ %"+,259C=9<>DLI>A>954+-,-<=@=786)!!L¡zP935..A<4+)-,Lɽ»Լxk|nc`gRC=LQJC>AFNJH>62,++,*)+,8=9<54+-,+%1,(%"" %"%%%%%#$ $%**2279B>A>BJNB@?>52,../.=CLH3(% %"mŴxJ!8Q@+&"'(2ɽºž¹ўrha^^F>HWWTKJIIGFA<430(,/.,,2:C@=72,../'(**'%%%%#$ $)(%)&%'+*259::>A>DGJH>96656/.,,*2O\J&LZ[VPKJID?D<>7/+,258999><8/.22.-'(%,%%%%#$%%*,+,-'($+),329?AB@81/16>HJDA:61/.222+(((% "'''%%%+-/1-((%*+-346>CD>BB:62-(%''% "+F}!Hȷ~nĽԥ= "$%%zÿþÿū|\`jqpbWJ>8@POR`UHDIC;;;<611/3ASVL>8611.22-+(%'$%%%%%((.22.((%*,2754;A<;;<1/32-+&()N`>ɞeW͛7#%%+$8ÿžŹÿūvFNjzt]J>8>KSJ>DACLUN===7522+.>SVOA<86/1--(%''% $%$%$+,,,.(&(,27==78@;;<811/.&%+YJsԻy>8Rɖ8#%&(#^¾þ¿üþKSjzp]QMJRZPB:==AMPG<>8675.22=NTND;40-/,,''*+%%% "$%%%(+-,,.)&(,-/==A954401/.&%%%@pRBJLKJD;47526114;DPOMJ=2,,.)%%%(+%'$$%$+&(,-+,.2%'+,*+5>B7-/,((%#(Bպt[T% 'kʡA@Ľ¿Vh}wvvj_\`UNJLW[NDDA<2-54+2FY\WPIC?<-(%'&(,%%++(%%%++,,.)*+%'022+.544''%%%%BqƓkO($`պ}.hÿþ˜\^rmbPADWlsn[IRUHDAC;402752F_f]ZZZWN:+(%%-/,(/+("'((.+,*-(%'-/401,,.)*%%9_ԠhkzvF)TþթO9ü¿ȰwTNRH2-?\tw`MJD?<@?<8119<6?RZbmyztjT9)&.22.,,'%%%(+,''*40(.22.,,,.)*%8]ԫjOZtmG)Jӿώ3eþüěY7=?<8TdksdWSJFDAC@?<6276>>@PgtS:62544'+,*-%*+5((.95+-3,,'%''% 0]չPOdeB+%%Lƾm $5¾½ʸv8(.:BYlmj_\TNHD==787526=B:=VjOJD:1/)&./+'027/17=2,+(%%&(##=`ŨvhJ40((TξթN$%%tþÿ¼ßZ-(+8Smqhe`[UHD347526=?C;1>rt]\WD0-,27.(,,.46867,,'%!"!+FhɿJ,'"'aƾ́2%%@ºþ¿L+-Ztnb_a`\S:27@?<7=?=2%@ñtb_`M95;A;/+0202786* !"8]αzC",}þдQ!nüþüʺ|;1qtdWPP\WH;A;>87=?81'KǷ|j_\TJD?F=2-867,27/!)Gtȷ[NjΓ- @Ľû³ɩc-8l~nmZJDNOA:<>>8>CD8()\ƾhZZ^SJ>81/32'$,'*8Rzӿ¿ؿm"xĿ¿Ľÿě]JYbmyePGC;:69BGHDHJ=%*dȽw[U\OA1/.&%1HJJL_ƽШJCƾzhn}hSPA:<==AJPPJLD02_rOA12-% %@TzĿƽȅ*!tįl_aD;@?8IsvZ=,'/Ktʾÿ¿ÿʩW7Ļÿ˳}wmM>>=BDAH[kbPB54Ss|qaO<-%%=`þĿýžɁ/Tü¼žպqOA:@DA;JrxK91!+dƾĽÿĽϓ4LĽú¹ǚwv~dD;=BDB@A95+2-5J\WPO<-("!#[ÿǿþþþַ`!"!n¼ÿ¿þ¿ļ½½\PO[U@8;A;>811&,AD3+%%% "+0&>þǿ¿þûǍ8!2¾½´¾ž´½ÿνJ98>J>/1--4867'SƥnbjqdWPY\_UB5h̾üÿÿęDeü¿½¾Ž¾øüþʐL4;D@1/3:<==2%>ętmmjcbfkbN=6yÿ¿þĽ¿˵h$!yþ¹¾ýþÿĿǢe<6??.-8G<<>8%1ѰtmjjqpphJ4;ƾüþĽýȾ˩O*Ŀ½ø¼¾¾ɱyF167-3AJA<8/+[׽xxxtwvsnP/Bо÷øÿͨJ0þƾ¼¼ýÿøĿзn9501/<>>>@4%@عqz}~vjN,GʲžŽÿϰ]#4ýĿ¿½ü¾þ½¾Ϸf2762-??<8;/#Zթlh}ymM0Mõÿ¾ÿÿ¿Ľͽ05ý½øüýþ½¾ʲf28@867>=95+2qӢ`\z~}zlJ2Vƾƾ½¿¿ƤA 9¾¾¿þýþ¼þúýþɯc7=?=86==A9.>٩cZt}wv}nJ4^ÿÿȾÿ̧D PȽȾŽĿ¾üÿ¾þý¾üŨdDPPJ><>>>0&Fܱ^MdvvlmqhC2eĽþýƦF5Ľ½þþĿ½¾ÿ¿ǩhJ_aPI@?<7% Pܷ`>SmjcY\S:2l¾üÿú¥DW¾úý¿½¼½þ¾þϮrVaOGHB:61`ܷ]6FhhUNJL2-mƾĽȿ¿¾ĿÿÖ8$Z»¿¿¾¾˪nU]TGCB<4.+ٷ_5>Y\JDPK,%hÿ»ļžÿĽÉ1!%.=Wù¿ÿ¾ÿ¿ýƤhOVWPFB<7/'@ۼh:=PPDDOD%"k½ý¿ļÿÿýË6$+27$Jʾ¾¾þļÿ¸¹ȥmPKY\QA:6//VvA:NPKFB5%y½ȾþýΝ=$.278%:¾¿½¿ƽþþļȫz[V`gZ>44.7h|B8/"4Ǽ¿ȾÿþþͩF)9=7/'%5¿þȾý¹Ȱc[bgZD:6+=|ʃ?4DD8880%Aý¼¾¾þþ¾ȨH);A4$#(,xÿ¾þƽþî~[V\\VQH9&DЖB0?4,8=,%Jý¼þ¾þʦG"47*!%$+ký¼ý¹¿ÿ¾¹ťmNP[VOMG7*\զG*;4.//#!Uýþ¾¿¾Ư_()*!!%$%kʾýÿ¾Ľ¾¾ýÿÿµȗV@JVTGF=,/}ܫF$690+%)e¿ý¼M"$# !$m¿¿øýþ¾ý¼üºİÿÓQҥA1:6+%+r½µǽ= !")qþþļ¿źǽµ¾ÿþǔ\HJPKFD3*`ʝ=+82+'+wý¾¾¾¾¾ֹn,+$/}þÿ¿ÿý¹cROMH??//ʤD)95,%*zÿļМH*!Aÿ¾¿¾ÿþÿþ¹ÿìz[UROD@;/@ձP )95-+)þ¾»¾½ʷw2 jþýþ¾µŽúù¿ù·¾ě`OVQHGC6/UҴ[&'222*!1ļþ¾ýɩZDÿÿýκODNPJJF05jҿϮ\-+.21)1ļÕR9S´´̷wIDOGCH?'8}νЪV++37/'1ÿ¾¿ʾ´þ»ϲwPKN??C6 >ԦG*,8:6+/}ƾÿÿ¹ûƽÿ¾ø¾¿ýϥn\QH?ID2J՟B )2790+wÿ½¿ž»·û¾ûý²½»ɜgdVFBPD/"Uϓ< )&-6/"nÿº¿¾¾²½ƽþ¾˾÷÷ʓ`]TNP[J1#]˽ˇ7$(&',%$_ýÿĿǾþ»¾þ¿ʾÿùǽOMHMYcR8+hùȁ5+3,,, !P¾ý¾û¿¿¿¾·ƲļxIMMNW_Q<4v¾|5+522*$#Dÿþ¾¾»ÿÿ¾ý¾¾¹¾÷þxNP[TTTD2AƁ6 -631)FǾÿþ¾ÿļÿÿ¾ýýyPP\\RO;/VÉ:$+052%QǾþþ¾¿þºýɾýƽ¹¿þûļõVT\VG>++wǾȽ8+.21"%hÿý¹¿ÿ¾¾¾ƿýþ¾þ¾þ¹¾\[bU?/'@¾?+,, 0ÿþ¾þ¾ýÿý·þ̿¿þ¸¾¾¾ƽz[[bUH,8z÷ÿY%(&$#:ļÿý½ÿýý¼üÿþÿÿ¿¾þ¾VROLJ4V>$"Hȼþ½ºúľ½þǿýļÿ¾ÿûû\JCB?9l`%$HƷþþþ¾Ǿ½ȽĽþýþûþûÿû}\QJ=7:z7#!MøǾ½þ¾¾¾¾ľÿÿþĿþ¾ļÿþ·¥z[ZP8%@řIMùý¾ľþȾþþþµý¾¾ýþžŽtac[=,Uɨ] Iȹ¿½ÿǾǾ½ýĿĿ¸ý¾ÿǽtdegM8qƷv'D̷þ¿ļþƽžþÿ¾ʾǻ¾~lflU?~º1 ?Ŀȿÿ½¿ƽƺÿɾ¾¾žÿþþþzmegZJ¾Ï4"8οǾʾ¿ĽžŽ¾ľÿý½ÿǾ½¿¿þºpjkn`]5$ *m̾Ǽÿȼ¿þ½Ŀ¾ƽÿĿÿ¿ĽpntpjnĖ<%Mÿõľýÿÿÿļÿý¼ļÿþ¾tvylf|ƤO(&:þĽ½¿ýÿǾĿĿý¾¾þûrrwlkȱd+ 313tľÿÿľÿýľ½ÿ¾ÿzmgtvtȷr5#>;/nþżþþſÿÿĿɾ½þþÿþù~lhxmqŴz>+TG-mĺýÿÿýĽǾ¿þ¿ļÿyvy]d÷¿@$6\D%fſþȾµÿþÿÿžÿþ·}}vS`ø>%:O2Pÿ¿ϲŽþɾ¾¾ýž¿þþ÷y[mD7QP)7½ŮþĿǾ½Ŀ˽ýſ¿}gƽLIeS(%rý¾¿˽ĿþýľȾ¼þ¿|pϼPRdH%\¿ñýȾȾ½ľžľĿýzxϵxV\[;%!$VýƺþǾÿýżÿ˷fh`>,.2^¿DZýúý½¿»ýqrkN=>Alýþſxý½ºýlhhZMJItž½¿þýýñhýžĴ`VVVVM=fú½¿¿ýƽ¾ȱǯ|^QIPR>/\Ǽý»ýŹúý¾yĽyh[LIA07mºƽÿɿ¿ǼþzзmfcWF8@kļžĿĽǢwmkeSDJtºǾſ½¿ýýżDZt~|eSSxſýľü½¿ýĿwz~ca|½þýǾŽqfn½½¿žppqryÐxϿþʷ½ſþŽȽĽžż÷sfhyxqrɿȾýľļdSýǺð}qkxhsx|~wm½smzzz|~wz}ztt~}}qjv|w}z|wzz|ywttqryzɫ~||tt|~~ywzsfdeqxjqyhajqnky}znjqz~|tt|~wtpzzyvwtpppmwzttqttnpjbhsvwwzqrttqtjqzywrxzxztt~zztmkppz~wsoderx~zz~ws|~nkjj\PWZMV\YPR]lhb[UZa`\[de`ajfc___f^QHJLIA?=IY]VLIIIIPRPJLPNFDD?=HJNFGNTSPRV\YSPVVMJLYVLI@AAA=>HJNUZWZde`{zb[\aebilh[]de`yIIP[m}tV}|w[DIPKK\gYPS\gZMDJWZUU`afhfhf`\bh`K=8:=>83BRVVM8127=>AA=@A2+*,.7=AADJJLKKI@FKKIHD@FKPN@<=>8212?Vg^V\glhp__fjzlhpthhjjqsaeaj}xV6ømwm]Vbqrjqznd`afYP\a\S\hhj\[TS\gle^V\gVLPI@AKKIH=0)$%+///,.//,333007=>AFDD@75APV\__]]]cWRVSP[]VLUhslhx~|~W8 |p~wmwr}zzxqkrx~t|ttywredpq^nmffc_qxhZamwrjq`VVVSPJIJOKKIPI=>AAALUQI>AAADD@@A<42H]dmrk_UUbh`hgfhnkjxërD#!4n]dmhaU`~|lhx|w~xjn|wzs|yztyd`fhf`swhh\[dfhy|pgfhgsvm\[de`WTSQPROK^nppmZF87Qhslnk_U[mwt|~|wzsF%3BCúxhaedhhaG.2IPT[dxyhalT[elba`TShzgY\rzppz|wvwwlbpqknd^QH[~|ytmy|kehnqry|~zhA(2?C7%ºżyzhC7D__]h|dS]zvwl`hhj\ZVVSO[w~ztedhnqnk}tx}mf_dpzz~nK&%6?=+õ¼½teqy}j\NNTSVVSZrzcalnvwwm\nzzszsnkjt|zsL-'383%/ƵýľýſýŻz~}hZVpzl|ylhxwyzb`aqzz|yrkfhfjqznqwy}jMkJ '383%1Mkeƽž÷útct~¾dzĽÿsf}bhntbarsftlhx|nfhn~zln|eS_mw~wJLY-'56%,Dh/rȻúwz}h[nyå³w\PWlppºy[\z¶kNJ4* "$%>_sK\÷ý˷ýºԧί^cnºƨ_Arz\hǹrW@3) "Arý½ƺDZʿ;ÿƟDZvwlʾĹõǾôº}ŽytU6%0Y~wlecȿüƺǰjký¼ºÛwy}mknfdlvwsr~wledbfnnlpm`agh_]\PHJJAJkpZM0#2AJC@>;104˷ǽù¼ǿȿúyw~kV[lnYD=Jdx}zry}wllcfnptsrtph_sxynffdfdfb\\^cag_UHD\P.:>DI?9>C@/(+%!#*+++2323+%++2,,,'.0++2) %(2:CRJ/$$$!#-.*+&#$%w^MNWaglcaghpt~xvwka`jrtprh_^lvpbfym`hptghqz~~~}|ts|||zyzzr|sry}cZ\^aglv}zy}~xg\^ahkg\MN^^]P@JJMNBDQI8.:@JV[P@8.*5C@@JVLJPM>;DQOPH58CFLQNHJK@FLV[TV[[J=HPMT^MDJJMD823=JQB9>DB93>JJIIPVUVZZ\]\QNdbfn`QN\nzyg\hpmptvsrqhkwlYYfdfVUYfnflvv\FFLJJKLQ_]V[ptg_hke^]SJJKTVUPKFLlV?9MmkRJJTVU8.'-.40+,(&#$84042-.>C5108>D9-.:7CFB9>D?=<=2,8>:3)+463258510-.% )++,10-9<51%!)=C7&#&#*+&%(0+,23AJ=( )%)(&(&($8.0/(*%(ɯ||shnšpMDF?3&,tʷҬvH78;3&"tǿʗszʥkMDDC;( :ϔQ6=gɯkeVC;&,W̐P50+9lz`D8%0nؾH-4=40DkwjVB6!2}ܼ]8'*28@JF50<>=950/ڬvK3&,0+8`k, %)+'5¡pA289:81;`h5"%)'*!NȾץtWC6289;9578?>3&,-)+"/~ʾǔ^D=4289;8333)+33.*,0-Kֽ}zshhn}ϷZ;3402=972,03.*56,%+0+n¾ѸbF5('*!"%+++7HPQWhnT@40-)4=95,+0<810+*,,+FľľÿӫyO3&" %%%%('+1CQWH>83,03957/,+)28920+*+0&4Ͻ̽ŽڽR/ %%(%%(%'*,0-6810,0-,03920.*+++qǿ_4'282,++,684=9;9>A>31;.%%%+020)%)150/,.*52,++"I˽}W9*"%%0FL8%*D`yzeP5('+++,#'262/,.++750/+"!mƢz^NCHPVP?-!+=>=F?/8gdO=.*'*,50/+.*+,6:20)+èzL2!%(-./2=<>TP?56JçyV8',0-6.*520+*8@50#>гbF1,0-6.6=>808Qa[K>,%O֦sVPPQL=4233.20684-)483,FܽQ6.*+,9::>ADMK>>S\I+"J١L$!+08>=50/338;8101,-../RϷZ4"08><>9::=>IOKFL>+"0U[%*278684-/25;940250/+.*lɠpD*")29::=>=>87=>>ADDF6,/256,/7vЅ-)/2563./25633:80+0+*+'*1֭ܽ̚]8%"/8>=58;?>40-5;=>>DC?N^cVB+",cޠJ!+8;80+0468957/0+%+vs15[ޓRnx="0289;8338:802,8IOJLVZTP`bP>80"c\&$.684-/26846,%%"%+0+%;zg7q0`ۦS%*,,46684646686=>GN^adltshRKF689=.%Nq0*2702.*+,9-)" %0F_z}ddو&2U!fᾁC#152,8957/4025=DFIO`hhsqg[DCVhZ;" :w֓8"/20.-)/+0-!,Jfӽޤ<Cަ7 zלV*"/2206338684-/2<>BG^szt}ta[~|O,+mښK" *270,%%+0,%%=tD#nq%8+1,-/256.6781,-/502!#''*,)%,+)+#8@rYJt,#',0-6;9>78140-+0246688@5>Ogyn]ǿ_8% )%,,0-,,+) %mݥ>Kp$nz-"/22./7==>>=423./78;80338:=JdztnyD*#&$*,,-43.%%(%,Z?".070"2376-*/7637:763239;===88==BIb}ɿ`9%#&*/*/*1/00('FrJ$fݟ>Hx5'/0884/0-23937239359;HJC;=HJDNhʗ^='#&++,.1/76,)+bb#""KR#]x."237:5276884<70006768BINJCISIDUp¢f3""+,-*+37282%#0hχ.#&9c%#'Ί6'+38=37:JF==8:7-*37:ACDGHJVYMK\n|ëlJ.#&+-*32000876&%j֥?")1Ʊm2%.1&;֞K#++'4BC;6@JF;884<239=DNGCDDNUVUVhty}tL70('++,459<71/7+#Pۿ_#&+'Y]Q4"(88=_ΑL",3,)7BC<7=D?:7-3728:=D?CD>?FKNUdktrntyxtrjF+%,3.#%,=D?C92,)!%Jф2#)+%%8V?$",9BI\Á5",3.239=679;<<71.18=345<<>?>?>FQ\ahfclvtfVJ:*%.18)+4BF=<71.&:ݢF")(',3A8)#+8=87Np2!*/*14;8008=89;<2,37:5008=D?CDGFDNV^\YeleV?==2)+,3.27==8:.11/!G޾^$&06372-*+/73,%#7Upc,"%,*/*19;<2277687687763206==B@BINF=PVJ2%#&!%C}ޮZ &*+,--2<7=:7-3455679:=884<=884?CDDV^VG528=349;<2.11+%%,3AA8Pz $ &000+3Msf<%'+0(.V]#"%!*0003728:8200087687==B@;8<1/1:GH?FGH?ACPQG5/08679::7-31/1'+8HOL7Dۃ-*3!%(06=32>Zk\D9220-+& Dc%*%'++,.6794556679008679::>?:=8008=CD>CDD@BGMD976320/73:7-3-*+/@JVUP59w1A8 &,39BD975CLONJ<0(++'"Kޯ]#(0,)000+88467909;<2.52/59<8846528>>>;8_eZRP2,Q'49%%,=8CLC0(8HRPKD=2(',+%%Jwޯ]-%,3,/0-820(2778737281/752876320455<<8455<879;<>>2778528>?:81/!-RpeVRY8)mԷߵ]#%,**/:=8>>24BUVJC@JI=-*+/!%(@J_ךP2+38DGB;8<81,)0873063.1120372-00087679006=3763245562,34;>>;;80)+In}eVR\B/bÍ\JZ՟Z3&*#&/08>>;;5CShmfVG=DOL;/++'" BՑD&088HbtgO;9;5,)+203.18)2393552/5.67909323728:323277000+89;<281,*4Rwx`UV_J:\ڍ4"!7ޚC%8NC;620:GDG>>[zzq\B=DOF=,)+0(+$-Rއ>+2<<>Ste@594.1&++-23232455669221/7+2393551/75000870008764556-*3\m\agfQ@UV00%VK&Png[G537CLPHOxznTC;DGB;802,,.620:yB+?FGT[fy~P545000&*+23203.203.6-239,)083,/52/54/.18)2*/:<<844/(0SzqqqfQDN׃-9;5=Օ?!>>;20388=)8td'&;DDNd_\a|kL=82,3++-00032,34.11+2==2.000+063.45000032,39==B;/(0Fr~m\L=DO#6@;8zܢF"ARPVhf\ZRD>?CJ[ahhf\n}gH5287<<>7632041/19fլ^+&;GCDNddddbtwZD82000032,0008701/12=>:7-31/086203845562277=DI?-*=kk\J9=kz4"'+8D>4nϘU6@JZc_VPJ[spQ@@JIUVRPKO_ngI=;8?:=82-2<==2.CtқI /CLPLO^kl`Ut]HA8/0-1:7023935552/59;5455669<7=:<<>752/59:=A827\vgO9;<>)+,(8HJ;nˍ>,F```ZJ:1AajMDQ\SICDDDNVYTMD?AD=27BC`ڵz=)=MTVY\gyw_Phx`WG,)08>:.11228=342778537:A;8?ADF=<6790938=?RwzlUAC,'4008NUJz٨N'ISJC;/#&372-08730Frн[**4BNUY`htg[djI+%,39=00%-2367277=8799;<>>==8:<@B>:.372-01AZ|ϺQTЛ\7%"4;>>?HVhnt|wzxr~~J.*%'/73,)0833728:845<>>2;===26729;543728:8CD>N]dknJCIF=4/8HJC;=2,)++,4@UgtrV2$Dլr='"(+2GTZ>24@BG@=82,./+*/*8=3#+,.n۾M)/7?ADFF=>:6@;>?:=<71;<<844+,-455MZRLHJ;9BMKPVRPVTVdqklhVD=8<7=:A8==DD>,'++,2,$/C8)Nʠ\-%&*+2>MUOQTJFQchfktshmtrsP54/0-1,39820=D?;8<8679::52/5..112DUQ@@6@Uf~y[[zc=''+0594?fN1m|dkg[V^bfc]]]NCNUYKD>>;;?FGMKGCJZcjhmspTC>:7-==8:?:7UsJ.';GH52>LONJOQSICCLZevl`OYYTRevrdR>2,34.+,--++-02770/0-1203.245<>MTD9==OWbttaTJamPA8237:5829LH(8Y`cVMTV^VUV_\OKNLPQTRPVA82BINOQ]d_V_VUmtcF82976,-2362038;;Gann\N]aT;*4\zqklsnkg[?-$-:GJF;9BDDJOOLPJC;=K\wfTMNA8=MTOLJOV^VG5/063.)+,(04500--23629;<>>===8:DLOVhzzqqdJ>HAMZP5*/27=87NN12ٮfQPYD8=MTVKDLOVRPVTVUPMKC;DCDDNUdb][aM?^vt]QI=;-*+/232,3.27O_YTLO^g[G,'59467909:=8>;869;.#+3?FJF;9@B>ALVUPMHA>?HVtfQPGCDKND>4>_z_PKNVG0(&*+2>CD>>?:=<;==;8699;5=@BGP]pvtywfD0%#DdR8)14679::N82ʐYDJO8)/CUOD>CGTZVNS\ZG=ALKDLDDKNLPMTOF=R^ahf\O;,)0833.1&1+2>FD=2(&*+'+05>>2++-021469;5=@A>>;;CDDDJUVJFDHJDISaxm\LDNQOLJ6HnlhcVB/+8PZegfYMHA>9;<28323=D?;>IS[aekln}vS=,#&CGPQLONZRD==JLC;DJFD>CGISJDNJL_kgV?=BC?CJHJO@BGPOLJD>CG<7=LH@=CLJSQOLH@S\[TQT]VD1++-02+,-42038;;,.6==BFD=ALRJC@C;>232==B;FKC@B>ACDLODD=8>DD>A8==DJ@=8DDDGBGHPKNVVYdkgO;3728:367909:?F=2:==B@;@BGHOD>CC;6227=8;=BINOIDCJLPQJC;=9BDDJ]swz~tyzvlb][a``cltf]HHYzkUI=3,/.5287<455<8:GQTRgyywz|U6%',)+0/0-1,++'++"9՟cV`O@A23@JI==8:IDC@BGHA8==D==B@;@FD=AC;69BDDJD=DKScqfQ@;8<868=?<8=?ANUJMDD>A:=80==B@;@?FPQFKQT]SIC==8JGCJ_tqqnggy|th]]cmfbfpm`ZRL>RbOLJD37C:=87325=FG@BNVRSev}ywsO5173/.0+++++280++#.WR^ZOC80?H=;=;9>IFA>>>CA><<<7=NL@=;AGQK<7=CAA><;=<;32;=<<<<<7;=<97=CFGPVYTHDG@<<751=FJJJG@GKG@KUYYTCA88=FJIOVY___YYlp`Z\\VRQTNB>>C`teUTc]A4M]OHD80,17A>:::?>:97/3ANL@==;M]VG@GDB>AG?>9>>A>BDB>:9=;967=<<<ABDJHJRSPI>AD@==97:99>HJHJHNVZ\SQTennhaWRKG@BD@32;\mlhmwnNBZj\VM=2/3322/>S_W\hkwwgN97<678322-%7tU$&'z}J\nT=F?04<<7;=;AFGB>88=83732/4AGG@<=@KUSJNLLIFDD@?>9::?DPOHIFHD8567>ADDGIOLIIOLTSJCAAKG@=;HGKUYVMFA>>A>A>AGG>739>HIFDDIOVYYQKPOW\ZOI>@==@==@[|v[Sbha_U@==9510+7FVY_^_nx_D4332;8067+#9&!&!%fܡJ>efF8D;,1.25=;9BDA><;::2/33567=CB>A=@HPOITSPIJJ@==@?>AGR^\[ZVMF>:99CMMDB>KGGKUYYQOHIOPOCAAKOHIOFA::?>A>A>>CG@<43;BDADGKNVZ[SPOINVNG@GDB>ABPc~s`dqnbhYCAAD;6785=M]V\[mv[>0473960+7+%7ؠ8%#G`/8SQ?7=CLPD=>JJ7396;67>;6,1567739=;9B>ADOPRSKGG?>9:@K@=J\aWW\Z]YTJ?>JVRB>ABG@KZ\SRMMOHI>A>ALIIIOC85=MI>@>A>A>>77=<>>CGNLLPOWNL@BDAD@BDCA89Gejgx~wt^B6=@97/56j~Z>:D@317A92/;=2/287=88=8?>9:@>JTSPNLLFA>CALICU`ZOP\hnnhUGKUYSB>8DB>KVYOHIOPO?>9>>CGAGG>56ITJ>67>;;=27=<>;=><J@==@[ygpx`G;=A>5108?HPOWbnnqxzrgSBDB>32225,PJ+%*}ʊD4DPD46WzV=;9617AC85254304743388=GK?725=JRSQWRSQLIIIOVZVJHIT]qwZOUYVI>@>@==@LPHJHIOLD@?>A>A>HD803::?886785;656IJHOJJGFA>><<<<@==@L\}wt~q\PIJLI<728CMPQTk|zrttngYJHA404747.'t\%$0+$gƓc]gYRGQzxV=97<2/283797/5---837--8-7KUJ8068=FMT]_UPI>@>JRH==@BDA`ynUM]VD;<<<ADDGKFA>FGB=;ABDJPPOINJ?51=AGG>5<<7597<6=@BBDJKG@FA>7396;6BUthZV^VRB=222545=;AFJ>6=FD@F]hUDGSJ928?>>=@BBGKFJHACAA=@HPOOPROPRC8047@==@::88=8?<<<;=AB>A=::28379Kk|f[_d\PG;739J\agpwztmlqnj\JHAC928*6ך:%+#2ϿzT6++322-,1.25102/23222-+22//:AG?7328?CAPVPIJDB>KH=8=FFGPc]B>AFA:32;>>CGNIOLDLPHCAA=DPW\PIJLRMA407=<8379886?HA>943;97<6739=;=A[~td\endVMD:04BUanwtrnmwxnn`GDGC80+%*e٦= %$%\ѯdA/.-756772///3353222-6=@<.'++3>>C73@UTIOLTSYWRG@G97<6=>Ja^JJG>>=>>=>HPMMONG>I[SCA8BDJKTSPNRSPC85=@B9767>;;>>C79>>A4322256778=9>>Od||rg`djgVRB=)&8J\jwtjlpxzzrj\J?>90%#VڤH%#("2ηjI>22/28A>B?7212/2,%#(DrwL(';BI>28UYJHIP[_aWJ>=::82/>LZ\ZVJ=2=FJB>AFFGPNL@NVH==@KMDJJGSQLJ?>A><<<<<<<<<::?>73@?>9:2/23732873@?Tc|~xrnmkjgRG>>C7/3>Rcnhghmlp}yngN9:0%$BکS%%'V\D=>5102;B?>A97/)&!&)4R˟`/ %:I>22DPDBDJW\ZSJ99>>:0+4MUYVYJ83DSPI@=AGGNLLPOH>@BDNLLFD@PPG@<=>>67>;@==9567>;;::?>783042545678=FWlwzteefhf[MDOV`d`ZOU`bhkja^`pngaOC8+#2޺`%)&!2}¦ĕQ8=;,%,153::22//%#(5BZ}2%::2256=GKU]YTSPNLPHC@BJCAADKOHI>>774<@BDC=;404747<<<<<:254504B9787.0SYJ?>=;`pZ>6Bahfb\VQKPSYJFAHJBDJKTMDB>>=>7=<>;=;9B2//:=;AB>:99602/3>;=A[^hww}mT=:Is̢r[_hfba^`QKD;6"/rȄ2!&)"&gčA>׿j>-7MMO3%#(9rؐ>+2/28A83DFGBIOVG;76028=F?>9:267>;;:32;8>>=>7::22543;=FJ[_lmlz}rS6*C~֧x_\[d\RMHD80+7qՓ7++#=xׯV;Υb=@cnU.*Pؓ= +4430-7CAA=DBD732--HgS<72=enP8Jp¯v\POPRI>DGLIIISJC=;96>A><::82837982/;<<753222=@B97DPRSPj]AdztZ&!CѰc]TK;9NךD%1(,eܜ;G|L(*Pte8+2Fe֓7*2/),<@==@?=@92//:SYJ25=JhUDPvõjVMRSKDGIKNLLFD=;AB>8=9>>=>H<<<<<047@8379433>>C79M]^Z^nnDG|vqxm5@¡hZV_nxԤH+%Dܗ2Bh8#2WqN 1Yҁ/!--,,6=;=<<97<6,6M]V886?YmKGst^ZUTICALPPNL@;=ABC85=?>9>CA899>923:B>56<97<6?72;M]dddsyU@`dj~|f>8ֻ͡J%#(Pvܡ=7tt<#0BUab=0Uv,!&)),97<6=>7F?7GelJ56I^zxYc~bVYSJC=DPRLIIB>A=:=;AB;6597/--860257=<89>>:=G?>J@DPD97<>>=>732;8322::?>7>774602<8=9IOLMTefhfRGV}rnmYcm^5'UܩD&),9\nnwzty#'L@%L__I1Gq%$+44@BDNVgx`G5=qѥte\VZdqz~wVGR^QD97<6=>NG73@D@?837988868=9>>:9962545656IJQKP\fdd_OV`}|bVdwzp`W\bQ+9۷Y*NaWJJRnם8#,%&8D@3,ef0+7:AIT]fzgN=;MЬcbnn\KIFYmz^JVf[D;<;;69GF8=FJI@=697<6=739=;;,/33537396;@KMNVahef^Z^v}xgN=Rcg^Z^_O=,\̉8/A>/%/Ws`ֽh"8DHgd$,1.8?K\kphNB9>kνOCa^B:ARcvhRMVRB=B>8D8?>@KMIFD::828<<<<8=97.0047@856778DGLQ\b_UVYhvqcL7Fanmc]gW;,M׿ZDB>+"(HUMg֨I%lg*+2;MU`mwyUMMDOǾ\4Ap|S;=M]dwlP>CH=B>A=:=;=ABIFD:7396;6;=<99761228797/556IJLPRYc_UV]hhaSA4JkwwlfdH-7ϴxN9:0++5PfvϙK&!%Sg%#(DLPWe}hQ\kYyĨk51ahNCHNVaznN6=JJG><<<@=;96>D@?8602<88379825108837988=FWPOO[ZYYQSeeZVRG>PkwwsmY51a˷z~ѫ~r~[$/3>Na}y^_yzپ}:%FwWRKIFDVqnO5=FJI@?7;B?6785@==@:837982551088739656?HIOLTMV`ZOUNVZ[QKPHPbnrnmkJ-M߭U%)4KcwtjѦP+=q_DIFDDBDV`YJ>>CGAC85=?97<>=;96837982558304<::8288?KHJHNR^gd\RPOWNPI@D\nxnhfQ8=ީM%6Rkwwɵw7=glJ=;9B>C8?R^QD=>DGI>>=>7:>>=>73602/1225677297<>837@KMIMMSYanhaSMMONG@<^tzrjK7=~ڤP%2PgpǾP>bsO?721>A>6=JWRKIFGPNG@<=>8?><<<<<0122/33597/5::?>78?RYYQOJR\lhcTH@KM@=Jas|nN6:y؟A*7RhxNhdL@4,6FA:3DLIIW\HJHID=>@=69739=;83042/2374338<>OPPNOPRgh]YOH>@=22MgxtV28rt/%Fda[jUC88>B<58A@=D\_D>B>:58@=7666762432432111238>>:>=<>CA@IPTW_nhOJJA@932=Vlt_>:m֨A GgaJ_r_MGOJ?9:5524CQPA@99311?CA88>8823112321///8A=<=<==;@==<667=D?98A=52//,0112332=;9:;@A@IDLNMR\WNUYKC89>=6D\r_IHzt4<^t}vge\IVgeYMKSV[fc\UG?C>:7628A==<6243=<==?9=32+*,0321588829:;CHIHNU\]YSMRKC9:;C?CVkgSV֖J8^t||bPMRZ\_M>24HQVa[YSMA@D?98:@==888243==<==?<52,+,03436:55:@;9@JJJJUek^TWJA436:>BVgj]eܸnCf}yzjUS\d\SF931;LNNMQPPJA=<==8>>D>4<=<62<>;:59:52/-2388;938>C=<==?GIPPW_aVI9382-2=M]ea|̔]jtjd\]eqylO:55:UYKLGIFHC>:>?C>>C=<57?C888B<;98852/-436:>609A@99=DIP\]QGORM?.0988;?KZh״}re\T_ntsP9:BVaSMAD?BD?;9@@==@==@88>C=58@=<67688.043638436:>:@;GORjdVJJJA4,0<>;GIRnənVPWfszsS>BV`YD?BDFHI@==@876BJA=885<>;76860243=11288292319:5>CDFP_\W\dS7+,5889AV}ծlOQahgqyw^TU`_MDFHKKKD>?CG?9:N]YJ8238<57436//231938>>:7243==<>HKDLPMFOS>(%+,,0DgpDN]haahtpgecdWNV[PJD?BDDFHCACTekUC5288;885243231938A==5889@BDDNMGCAC9:5+"###2N}τH=Mbc\^bcekjdVUegSB<;DGIFAFOMRZ[VI>=688>885<<52436:9:;436:>CHIIH?9=<52,++"';d֥Q&3JU_\_a[ahk^\d_MD>:>?@BDT__\VRSVLG@==8885<<7628885<<788;?DGJJ=43841+*-+*Fz)(8829D\pnhc\P`_VJ:552432;9@<>667885<;GB<;762,03.0&%;qܭd32=NC8JfytjgeYJ>B>HCFOMZhxt__ntl`RH=7?=430885<BCAC9938431+%#%;bߧV,+01.@[f]RS]YJ9:BGIF[fhp~hOUwygSBJA=89384:@;=DII@82-431.#2TԞT-%+;IPJACQTM?88;8D\rw|]9WhY]PDFHC::55:>BHK@3+,,$"#$,JҢV(%6M]k~b@kŭyljd_\_ZQPD>?PD;93+#$>rجb@88;_aTMFHI;98=439AOh\@tɥzxzxqmbPVRJJJ8(%%+##(3Jlߵq=$>t|sgP<5=<67:@;WnhN>s|stmWDFHC:+"'3CQTTeܴ^)5s·rVB<;7FHIOS]RCHtpa[YI9/*"$7ZإV=hֽ~nZB2.@]|vWD82- +SƢnZ\_KCMRCAC99.8829SrmW?901) Yt|\@5-+,+%+NÂaaWNBKe|zhO>2'''+,$9V[c\PKKD:7!PБPAPMF76FOMD?;(,JhphYJA=80%#7M]V5#UܰrVQG;//>KJJ=7 6vgaV=DWnvg@3eď`NC528>=A@9+"Nœ|}wglyzvgK@z̚`F=4=IH?>:7!,rΉjr~tm\IH֛\@=CݭmRH@BDI@>BH[߯]J_e\\fsa=4ެjNC@BIH?>@JZ۴^HZ\_TW\R52ܬjLG@FKKD<43R߽gDT[VNMJ:';rPMFPJD?9&+]k?KOJ?KK4JxPDPTJ>82!0yzCAHC>DN1Y|OCNMJ52/2ۉH=A@99=) fPAPN>-+'3ߖD)5=<=5# m„PDGI8-(%C=5kJACB<2+#SK%6D=<2hv@==FB<,(tW09C602h޽q=3>DGB1<ߧP%321/2%+nܳe88D>?=,LI$,+05(%qW:KTD;5$VݫM +++/""lY=PVDC='dްV%/--- !dUARQAKH0+s߯V#(0+02&!UژQJTMBOJ22ߨO$++/;-BՎUY[OJSD+2ښD !%/8-2ԓdpm\\\C(:ԓ@$%%-5,(}БfrpcehG+Cُ?&(%/8-$tzKTWTV^F-@܍<#(&16-$vܵk>BOQQW:&8Җ@#(&02,0֧^LNI=PڭN+,.0#QϖH'2<;5+&h%/++p½ڥU'',.0"?ړ1+++%׭\+%-41%g߷_&)(+֬Z++/;3 ?Г,$(0إN$+68+W޳D#%.؟G$0+""kd% !,֝J$%,',Є3 )ԜO,.0"8ٔ8&"%ؔI,.0Gؔ8%%#ρ8%-.\ڕ8%*&t5$+0#)nؚ>#)#yܾq:)(7(0~ץF#*j߽k>--?,.ۮK&)]֯V224A16ڰO +VםJ).3:1GݱR#%Pˊ:)160+S[+"JɁ9,97(*^h&((>ܼn82>=',kp,.*4ר]:8DC16zj(05,-֤_C=JM7Ab(:G<&(zəbNIQQ;Lܮ^=DKMTMF=FKHLIF=FPMF=FPPLJGB;5+753>B>BOx_CZr½ǵl]C( ƾýøȽÜüž~~~ztny}jm|zpzzzts}zpwrppcUI=7Mlzyzspjhd_]c_\\[WY[QJJM[`ZQ]WYYRQLLIGMKF=7=G\kxϯN$6U`WC(Ƚôʾ¼ʾȽ}jgqneV>2FtƷʹtst~fVmϼƷts75:1+287538>Urz~H /DUWI%ǾʾĹɹʾë}`@3Jt;½nƸƽžqʷkMK@@FKQJB;D]xvc_D('GZQ8%#Mƽ̻ϾƷǾȳ˺ðvz|þƥ|wqyvkwngp3.>JM>)/Kfż½ķǹȸæ©ĹyžʷǸŷżʷϾtŬƢx_Tc_%%==DF=FPPPLDC<;AKI=>=AKC==:972.,9DKSVN9.&!!,9@8+8DB;?>638568^ODUhcnnga^]jhs|zzzzga_jvtwqfaehlpm^]l|f[`nsjh|zzzzzzzwq}wyzz~y~ywrpwqf]d|wqnqtysjan_CFRsq\PD6-A[e`emgdes}ytlkqn\RVhrhb^]df`_hngbehg\UMKPLOQQPZQJJG<5,8DKKMA18?=:;5=DCBFH>1&((/",95,2JMA:802>>8976-68DM\\G2+,++/8=:JG:)1>B>FKLIGJGLC=9@@@JQJDKMSMKV^ZQLIRVNCGMTVSQJJF==DD;76>LLA8>NQJJ_cdd_]cWTVW``ZYcnyt|q_cn}tg\\lz|}~t\\nl`_krhghh__cd_[`_\`_\RS_mmgjvtmqxxwqf^]hqhcnghgb^htmVQWvzc_n~~P/-BOWTKFDC128FKHNYRKM^hjhdhnaUNV^_cd[OZceghh^^aa^WWYYVLNSV^hSPUSJMQTML@9712*%%',9B@@AKPVTVNIABOa]K?>=8DGGMMKFSdfYC0#)=KF=FJG?:80468>=FKNOPLJIþþǾÿÿÿþÿȾÿÿÿÿÿþÿÿƾǾ¾þþþ¾þƾĽŽÿþľĿþ¾þþþÿǾ¾¾þÿþÿ¿·ǾþƾþŽδƾŽÿ޾ľûкþÿþŽÿÿӽÿÿüþþѸÿÿÿúϵÿþŽĸþϵǾÿżÿ½Ϸÿþʽ²¾þȺÿϴ¾Լÿ¿ÿĿþ¾þþľÿüƿƿþþÿ޾þϼ¾¼ÿ¾ÿżʾĿþƼÿſ¾ûƼÿþÿÿÿĿÿƼ¿ÿþúƿ¿Žþÿ¿Ľƿƿý¿¾þþƽľþÿȾþʿÿ¿ÿϾʿƿ¾ÿþþĻƿúĽ¾ľ·ľÿ¿¾ÿþ½ֽ֠a>6CmϾZ+,vzVJIF?80'$%Ss\D0''$!!!)(*2&&W¯dJ80#!+/,(*0,((2.-LƥbK8&!%,4<=8777,((-('jϢk>( $%&+.-77<=;87+%#%7hֲwD&(212861247772+)($%&0AMlֲP($+/+%+/475124333,((-'$*23?C4/NٳNJL,%%#+/+0,0,012436133,+)'$!+7=ADD>;Pbtr6+/+00,0+.862.28><5133,+%#%4<>;LQJ>J|Ѡlvօ/e`&%+/+002+,(/6877=ADD>33,+%#%%+4<>=AKTR<=mhBܦL,[#%%')(/,,(282+12:>DKND8-('$!%,3:><=;;PfT;8ah2]C^#!%%+*+/+0,4513/6:>,(/0,0%%'!!8d_>6R҂%Rܺr.2[%'$!%%%.2.)(/+)/,28986BPWVQJ2+,%%%')((_}V:/Nj4̙[/b]#&(((##,3-((,+*,+,3<97>JNG>8,+&"*,0.((,ThN4-[S%%$hNjH("Jn+%%%'*,&%,3.(+./,+,+.;:58=<0.,3.4-,3.46/,+@fzphZF27qۥG%%MD%%+7և2((,"**,0((,0.,+*222./2:=6/1/&(,388=;/&!),3AOU-((1/,GT"%%+*,02,+,584-,+*2143<=QetکU%,*,&)1/,.7>=60.,+./219>=CHGD@:0.,+.((,0/,+&('*,-/7>=KP=+RϾ)#*),-/27>8,0.,1497327272+*U֜O#$++&*,021/,3<83-+./,+,+3<=FIJPPD2,./2*,&)'*%%',4DOPPJFNTF2;Ǿ֛j}dPKPbݢ>%%,*+*2:UYD;2,.22.+&*!"*-tǽ̍F$#'*,-,+1/,322.32/,+*./2*.79>B?AOWR>2,.,+,+%%''),*2H^mp`SUTF97wɟtjsg%IeI:,+:UlǽY $%,*+%%>bs\>25845,+,+%%+*%+CYVO^mnbjz: "**/,22.3/,2522.3/,+,+30.3<8;::5>Q[PD2+*2*,&#&%+*%+8Nh~tb_\UF;f۾M,+,5DezV'`nYC, &bt+%#+./,&(J`YD==22ATUO?6/1%%+$#',+,++4MqympnbP;Vܱe2$#'$#',-6QzʼҽY6]l\H9&cӈ7#*),;@:,;FIJD@=COI:832/.794- "+./,&$#8_޷l5!",34-,+22.+/,2.(+,+,++*,02;::22:=58>A?==60.(+,,+,++**,02Unwtqsd?DʴW+(+*%+#$+8IIMVnūacC2AD5++Vӂ3$+./A_eQA>DJG?==IIC<96/1/22.+/584+2,.*8W֩c."**/0.,+.1/,32.(+,,/,+*.*,02;7.(+8=68=60.4-,+2,+,5/,+*.327>`xxqyqI:tó|V2%,*+4-,+2)#$!"%1DVetĥsS25I:,"**7q\)#),305\}vdVUTNG=<8=<<9632/.211430+032/.8AO^~ʓM,$++(12,.,21122.+/),305+&*+022.+/).794584+232/.2/,+*.*./2?DBFWnwtnfJ=cūl\H9)#"*,021,+,++%%''$#'$&(,Dbb8!"Q̄8%+22.3$:UlyymphM>8897397399730.32+./272;@JGGPcy=%%.6/1%0.,13-+,+,++*/77.),*,3.4-((,348379730463273-+,,3489>BPZfrsdSH9/>l}bJ8,%% "#"$++,+,++*/&(',+&'%%"$,;FRdcB%#%]ʊB%+752/,2.=QttbS@32736/18=;/0383,+,5/27>A>LO?744633-+,,,+,+3052/+./,81/,../2584=<8=6872722+).794=81Da~|tqnR83@D;2/27440.,1379:=LOQUON`ѝP((327J\jfJ:5>6/1/-/21/,+*.3/,2../212,.,2038=QA32/.8=60=Qemfahq`KJPVJ=2,8A:5>@:=<8038=8=6=CGDHPVOO^sƥsJ-(149;LYjsY>253-+,,,0.)%%.+,+11/,32.22.+/)27>AA>8=6=;::2/7734683:HI:,,.7945&(',584'%#&(,*((,++*%$#"*+&#!"%%''$%%+$#*$#'$!,GnܧP$+K]TNA27>AA>8>JLOQPZ]_\\`j^B03>8179::=>7=CB?:=>>=>AMPPJSZ\e™nJ-'*8=@DOYVWb_I:81/'*,-,+&*!+**,021,1430+./2123<8;837<9>;52/79:1/,..B?:1430744+./,883,'%#&+*2*((#%%'' ""##"$%%!:\c.#3I[egT83>AMJ837AMJRUTNGDCB?AGJNMPQUcy¡l=%#&/8=@DQegTFNUTD8,0,+,++*/&/,+*.*.2722./21233273:5/,258<90.3283,/,+5CB83460.32+,34;:52/+),*,/,+&$#%%%,:=532/.""$+ch:08=KV]d\D@=DOJ8468B?A9>HPJUwtYJB?:;::2/8=;DOcywjWRPI:>DA>8>DPcdPADMVP=/,251/,..-/2,+,+30552/+)/773:5>652/+3<8;,34896/1/-2A97391-/-((,:=>>584+2+./2/&!-KrwjWMD80%B޷q>8HV]TF=CV[PDCGD=<8=>=>=8, +Fnǝg@*!"%%,2:9>BPWb\SFAD]lV?6973,+,++4-/21//,251,+,5/2973992+)279>1/,:=>752/+)6/1/32/."'*,;@A>8584+232,%!)+*%+OnWG51/' Ph=8>;:@JGGPSU_enrxqhYD=;::>;::22:9CHGPPQ\ee[ZfkcWRPA>85Kj̱hD, (,+,5/2>DAI[fhcWPCAYrsWG=60.41/,..-1430+./,2581/,32.22,.,20584+2397304+*2152/+3-/--/29>;83721120.%%6Qj}tqƼ}b;0:kt6/:OIC<1(17DQVPCAJNG>868=RUJGNTV[\`ktqnaRLOQD@=D=6,3<<963BP\een}vmkhcS@1/9Srynlfhȼ\>*'*22.3/;@JGH?F[n}~r[H>J_nhYNG8,0,((,0/,3.4622.>>=2-((,0.,+.1/,25814633-/2,+02584+23283729>122.+/).,%+O˹߷W+3<=6/1/35=JU]TJ?DBJ?6/18MPQHIFIPVW\^`jxqh[PTNII=:56872+&279>JW\^hj^ahhZI:,'*8;51/9CPZn}wj^S@*!$+8=;/7>FIPLC1/,328372958454633-*'+./,&)3nܮQ.7584ADML=25+0>JFACHGUYWRPRdhnfPP^`TNILOA>85/,+*.*:=>>DJ@:DJ@<9630.32+,32/&1D@:,/&&%+2;@A?==?DOPOG>>LcyhZWRPLc|t[@3,+,5/+02,+,5/+5845474-1/,..-1./21233223273621179::8372,+,+".(+&1/'),-fܮQ+02=KUdzy`KOPOGLOLHIF>25+,+,5=CVUY\Y[]_YVWVOO[]dhhqtb_d\QH94-19GD=4-,+246339739=<8=ADMB?6/1/-+0>GDHDC=:560.320.)%.G_eR2&('""$C۸wegRL[emk[PTVOONGKJJFD@=83+(17=A?>AFIPQNG=;Krt]O?;83D`jaR=1(%%.+,./,+./2/22.+/)2584583729552/+321128<;52/+3-,%#,]ʩU&((#%%%?̯~tVV`YW\YZVUTVVPPQIDCG@:=./2128=6=;DORU]ltb`jafrtym[]sqkcRU]nfjskSHBFGD=49>B?==?=>=<963B:=>7=DC=?DB?DOPKC;51BjxYD=;<96JVUTVD0%),*,/+-682112046333273:;::29730,+,+305>A?32/.-/2,+@<)#)&(8ștj]OQUPS[]VPMP_eZVQH?DB?8<463339>B?8A?>>8>;?==?<>=>8=@:5>@BFGII=:468B^|V?@D><96DP[PJGNPbe[UOG?==7446837295=CBB>=>=FIJDKJJHIJJFVec_eyyy\HGPRG>8=CBB>DJ@<=CBB>:5>FAC@DHDCG@>;::>DC=?978<;@JgkSFAC;::2/3>:560468:5674408<;5681/,3286%%+468:=60=nwI!&(,*(),$0ٱz_VOC5,9SVOCKPSU_KJJCB883,/74-179::NTH?>=8;@D><>QvYD=;<;0.149;Q\UF6/+02,.79:5/-272;=<8052/+32460.(+&((,++68=AA>85/6*!$,+&'&%%''$%ԥjRLJ=20;@JGHMPQHH?>CB@<584+52&158>GPWRPLOI>889?==?DCG@NTPGD=49IOIIIC>AMJ>AFB?A968=DJ@DJJ\ZF>8>B?A>32\jH99>;1/,32.2./218=;/0/,+*.3/-/--'%)3?JPMD9>;1/2,%#!584#$+'*2bɗ`BFG=6:=>>DBFGCBFACGJD;9>;1/,+,+3=DFACGSMDCB889>B?DB?AGJFL[n~}bI:JNMLC9>jbUONK8,((,"&(,0+08372,+&*+),*,),3=JV_nhW=1/,.+.(+.5=8,%%%'')+Bݳ`B@D;:HKA=<8=>>JFLHJNG=<8=,+,++*/32=HN\lh`bcVJIPLACJI?<@DBACGGQvl[WVJ?<=?C?<=HNGGC?<:::@=?<@DB88<:8DB=:GGCRexl\UZ\UJADLtzn\LA2+++/2/2.++/3530,+*****253=L\mrnndN6++%()&()4;8+%)&%()&%$,vfM?<4;LODB=75=?GGIC?G@=880,0210,0)08DA><@DDGGC@=;8<:=:88=:87>:::?<:8ACGMVamwyzvzwnt|znfZMJMVv|jO8.1=GGPSK>4.+-.+-.+-.,+21<@DQ\aghh`U@.&(%()(),35-&(%&(%&',+#wҖrnfM;84;LIC=?<>DBABA<:=<:875212/53-&.8<::@Q\mraVYQJJIOMbzn=2=?=:35444;8DJIYlbA>DA>NlsZFHIBAFHDB=DLBA953354<:=IIC=::7>JJ>DB>9;8<=:87,330,+253==2,33:::7>JMJC?>9;8<:KJDA>9;)&25686=:38757757721.870,07577A><2=?<>A>DLOV\Z^\ZRFGQSK=?Ge~dNIC=:DLM?==?G?>DPSJI?>9CPLIC=:D<:=<;>DBA>4686=DBDA>FHDP\bc]VWSK=53==FHW\y}qL+*@PD=<:8/287168>MJMRZ\ddd_a\L7***,+',+*,($'%&(%&<ܸ_Dg]DA82**25331880553318444/)752,33:/28PN<:87QVJIP_hnnkg]NCGQOD=?GetYGGC@>JOMB:?DF?=FQVJA?<=BUSK=A><95875;=B><=:87,88=?@C?GHD>DIIWMDA8;8<68DGOe~|wN6>Z\MJMD825=2,25=JJJVYemg]cg]N7*-.4,+','%**%!%($%(% <ώGGcO8:?DMJM:0,0721242/027/285=70$%)'%8PYG5,7PYKJP_hjgb\VF?DLMG@8KghICFF?DLOB:7>FH=?GSTLA><9>MJMDB:775777>:><=75710688=?@>DIB:=?<>=<5,7:::7MVg}zrG;WkgTVWF93:?8.027DGOS[bkgbce[H6+686,+',#++FkL( ($2`:GN=28KdznW>/2./,+2/35-1--.9>888.0(),$+>ST<.8ACB@IPS[WPLIKJDLOB53Mb\>>NHCDLB95<@DDBIRUVWF@=;>>>?<=8953===?<9>8533686=<:87;C?=88055752Uy}}zyhMJhrh`fZ?.12A>2/02=HNYZ\deklhZF95870'%*($*#+ʑ8&#mєB:=>9;>Pq|_>-&.+***,10.444-7>:,=hmF'(>MJA8787>:>BILOV\ZRPLIA44\lR>DJG@DA82D`bVJIYQPNJI?HD>9>DB=70,077>:>354<88=?;8434;?;:?D=?<>=870027/:f~xzxtb\hnpjghV=28=?<92/02AMV[W\`pyvfO>444/)&%$#%)[M WƇDALADBD_sdA,+',+,+2/3-.+5336+%4kӿkL2+3DF=28=958;CJUcg`VOMB8DLMDALHD>935-125=8.016<:=<;2124:::7=?<9?<=810.44=Qzvzsdn}ynnnfMD;=B>;816<:GNS[\Z^nzxhVJ70021*##(),%g!P^Va]VJUqnM2*)0,+2/3700+16<6+$+aƨk6%2==28=9686=MVg]ODDA82@_s_D844;Y}b\dUJIIC?CJQJHB:=>880557878.128<:610.4453368805:::7.128=Lsls|tqnhw~~xeLA?<=895+4AMLOVbc]hlnnfUJ>4668-&.+*B+Kܿ|tz}ywyy_>2+**25:5331,33:+$+M͝W,%2686,53==FWPB:77256MjgN=:?MVF?]}wyyn\PNJLA??IOMB8><686=<75777880557212420,0),+25444/027;CJ^njv~~|tpjmr~~|^KJDA=?<923DSTQV]cg``bd^VNC8712+*++%-D!<ȯrR>30,25=88712,+'!%9e۬f6%+42/0253CPH62567D\mnU@85=Ucbj~S[zx[HPSJ@=;GGC9>>?53===;8434<:8/.128=9.,33+*,1060,0),4;LPenjt|xtsrmrvzwvnUJJJF?=8.+-.NVYVY\ZRZcg`YQC8810.,+#c_%"!2ȷ}]D871268>8882*)$)JÅ?'%810.44=BIB:7@PghH624Jnß\>V~w\LMVF=:>DB=?46;=B>;757770,0),8@>//8712,212++%**2=LZl}sgh|xtsnjtzwtthVJJ>><6.&!)>Zb\Z\VYVhnpaQC?<444/)Yy+!%*e̾ZA43540/28510.,%2UРQ#+2/588=?@>>Z|pL<53Myj<:Shln\ACPF=:8;8<6><=7757778<:8/2/025,3::72=:5212+,+',+,4;Laxtqnnprz~xnnpzwz}znVRPL958;DNPF?=FY`VW\VUZlshVDA82,+%`ԋ-&!%NɾhH30,7571.+-'%&@xݥN .128=9@DHVsvVD75;mзjO6?JOYebF?DB:77275;<:8/253-2686=444/62+*265331840,0/),+25@DMjtmgddnwznlnlnl|t^KLOBHNdzvWPYZ\dZMR`haQD=<-&)g؜5$%%9Ͻl[H2+3;843-& !CS)&%,3:@DMcy~tYLH62R\F?=>>?/)'%,^ܱ\%"16@IWtpdNA>2Fë~\B:772LghTLAA><9>=?<9239>85,+,33:/212420212420,+2/-.+,33:?IT^pyv`\arzthbcekggbhnpkL2>l⿉`VWUSMJMDBBA1!.Yh/),,T˷}ν\BFQD1!&+9`ޱf6 /8D\yq_J=6?sƩnZA5336Uyy_JAD=:>?<=8957>A>2.128=62354<887126/),,,+2/-,3?CCIYem]OH\z}njhnlhfgrzt\<5nڿeLC?<495870!5n֊B,3-9׽VW~ßjIIWV=%1Sm}8"!8BUrx[H2=c̲zj]O@444C\zlMDA><9>=><=77?<=354088824;958702/0,+254.12354DGGMbnbL5Ckzxzxznd`hzm:0gəfM;:::=:87,HݞP/2)!)hګQ,D|Z0'>ZP/,NӑJ()>Sl}gQ::VҼvfOF933:AMfskSKG@?<@DDB;8<=>466700262353-21060,+2542/02A;8<=JUfl[?ַ֦V-& #<ûר@6f\4%2L\G1c֟P%6Pf|t^I8D|ůq_UJI>9;>FHWYQLOB=?<>=<=:8;:52880-.4,.123,+2/3-106853368=?>\zzxz~xnbSTZckL('nڱ\+$#múܱN!Nsd;*4J]V?I۔:0A_skSNCYǿrfa\TLHIB=?<=8IPSDBDD87>:>368644;1212+027//),,,.123,4449>DGQSVOTVI=?Zlj]gz|j`\PLVcV)Ln,9m AeL**DVWFPTLAJH=28=96880-26562356864.+-.,-0,0),44;LILOVMJMDB=?P\ZTS[bSZcVJIS>*~3#!C؜=6P>".CD>/DߨJ.Ja\LSm¤|jWPB>DPVJ753368GQDA>>46672567444/95870253-2110.,-.+5027;JJJMJMDJA?;BIRI=:GTVIUSMJA(0p֍8 #FVO@402106=GD8753318/2.88055722/0,/),,,.10.445:528;CJIIJIHD>9?CJG;.1AUSMJTV@.+gݘ<#!RڪN 6f֊QPU@=Ptֽ\UIDAFQTLA44-/-.9PLI82124:4.265621*'***,1+*,(/2./5=7=?<92>JKJPI=:;=B>;>466IYVOTWH*#\ޟD&+$%0[wyy~֎>Z٦nST<>SlƷkL@DMJOSKG=:3,+,2BUSD82106850,07221*'*',.+-.,-0/2.8:::=C881?9;0,0/DNH=75777844-/-027//-.+,,+254./2./575>>?5:::=CHD>93-.4256FHNPTL.1]ަO!)!0\yh`hnwЬ~֍IPj~jWHDGOZTKA>?IPMDA5,,+254./35408212+03.+-.,-0/,+2/-,/289587025;=BHNG;843-,3:2/>PTV@4>qإK%(0P\ZJOSVPVpצfZ_swbNC8?IOPNHMVSLD<2,+.4500011111,+///.+)*,+/---.+.+.1188453338?=BQQ@453//..464@MO?2Fݺj% :PD<@MPS^s֯rW\sx[D<9=BGHMPSVMFD<6--.8=;45.+.1)*+,+/-+))*+--.,118555642338<@CHADPH8400111,33=GHADVեU$&883.4>Pbrks׷yUUnx[LD@@FDDKPPNHGHA9453:GHA11,+,*,+))+.,+()//..,1556237649==BGCHDA=6420011111:GGAQѳeVVG($!/@MG[ŁJD_|}eVLDHMVSQQPPNNH>9==CMOD7650+))+,+(+,*)*+-.4118520088:<@=BGCGA<2203..+.1.+9FWzмzJ76*$.=MvГO?\xjbc\S^`UOOOSTOGA<9FPPNHA883//..,+))+,,+(+,+/294453/=;=;=;=BB>9556.+.1)'*3KjаdQQPSLWn׭lN^~hThjUOLJIJPSK?=8=IVSQMF8203//..,20.4,+(+,$*3642038?>@@7//453/3.$**%&8ZнNjRPr|lYDKttPHNNOOPPN9449FW\[WRC3..+,*)118.+.(),+/()118..418=I>2*,3..+*%$!%0LzܭcDYg`L>A[pVMPSVIJPH828DKN^aef\F0%&,+(+3..+--)/64203203/338HM=;Qsxh_VKJC<@IC<9FPHAQgxhD+,6-'*--.864.418//42379==;445.%&%# $3V۽n76=67JhnaZJD7/8?94220=V|h_[M?2-;:76@MPI8?~ÿhJ=62*+,*203/,+/-765203/,,+/(#+N}аl9--9Pjwna\\Q@88:?=DIOVM>@ֻ}^HA83..237555623883/--.,+))!%8Ls٦\3'8Qaefb^YPD@@BHMPPPaeTOֻqbSLFD9449203/,,264.,+(+,$(Cpեg8.Fetna\\SPHNA=BQbhnp^YЫpd\\RJD>A=673..+-,/*%$#,LsܻwDIve^_VLDH94<\xtnYJgqf\VSLD@>@:72*+"#%#"# .PŐysc\J=2375JhyxmhVBHkr]SLJC7652.++#%%&,8LhѵqS>64.,@MWROOSK?:GjrWD<92300%+GetstдtO?>957ADC3,7A=67Da}wn\J7+'*-,%!DwͪwgRCDF=;=A=.+338<=Ga}zjZJD<-#%%*%$#MvÍh_b^P>@FD<94//..,6=;=Thm]H80%%#"% $3lܻsLR^YD<9?=8/*)*+-.ADC>HRPI8)$! $$!]pJP`L;==;76*+,*20@MPIC<93.$"#!%&+twPZ^H556>;==50'8Mfk\Q@4* %05?2-z͍c\TD<-@@B?D<--Tv\F2,111:KPL50͠t]H=;DF>2;C<9LյWRICJV\SG00بsVK>AMF8,7=;=ܜc\YPW\[M?3?ܠdKJFDD>2211,RГZ^b^[`[M=6MԕVBDIFD98=2%.nʈOV`[\b^P8.VՎQ@BHLD@>B8#4ωGPVV\baS4/cЉQFDQQP@@B9(CאGHRPUUVS20lŁSLJSTJC@@20[ܖJITOJDJD72q۽zTOFKPLDF>(4zޙJIOMFADC/5rۺxSLADKJFG;%BJ>@ADC>A.4rٴsLMFJPOGD<-\ܗB888:?C<'2yְkLJNNOHA>22zܗB203/8=27׮gFDQNH>=6*:ޟF00011:,Bެc=;DHA8<2$DI,+/-77/ U۩`>;CIC<8'%]K+,*20/*)n֧aBHLMF8,"4N+.,2*%$6ў[DNNOH8#"JK+%051'%<ɍQFMFAA5"/xG $350'?zMFJ>@:1'JA)5*%$BhNHG=;7+,mߟ<(4&+"#L֝ZJLD@>4%<ޚ7 ,%&#%SɅMODADC/%T5#%&SۺsLMJCKD+,tۑ7$%)*+$]֦hNTOFO?&8ي4%(),+'%dЛeVZJLM3!@|,%&%)*%$b˔k\SLWI,%Rݺ_ $$%)*]^YNNVK+,fޢJ%)*+)N״kLLJTWF(4z֐?%'*-,"=٭cADKNH> :և8#%(.%9֧^HLJF=,HՈ9 "#'*$DФ\FOGD4%(d֍<%)*+Y͛P>@B8,%2؎<'*-,!n͖J1;=0%%JٛA!%&+)(ϖD,15,%2n״V "#%%7ϖG088/(<х0$"$FϙI0;;1((0;,>ڍ4BIJ׹m4%19%9ڐ8=Ѵթ\+%.,5ِ85Ŵ֞L&%-(?ڐ8.؝D%(0,,Qݖ8+֕>%(,2-Oߕ9'Ї6&%-2(Iܓ9'yt0,8;1$Iٓ9&!rּh2:KH4%Lڗ=%($dҫ`;HWO6&P۔=%(WΛ^JR[M3 Pه=$%Gʽ`SSSG+Kp8%(%9ϽWOJJ=%W۵U+%(%,޽OD?@6!)l֨D$"(%+ʽعyMC=;1$5֟@),,%8̽ЦjG=;5' Cі8'-(*IµܼS@=;*V|1!*,,b٥`HD@6%"7}˿U+%(,".ֿhTOD(0Yт;1532+?ƙ}t_K;1\ܟV3:WдsB=FF:88<94;HHDK?@IJLNHLNPPPNQUWTW\\VMLLNPD?GWlx}γIJb97G¾ɿxgmqVRPUWTF<CFB=>AAAIJD@9666119;1$=Wlxʻʿ¾¾D7G=%ú¾¾¼ǮľſükVR[]Z\t~s\MTbnpa\ahrhdcc\brvsj]_t~zskYNVUPPPVZ^TJJKHRPDGJC=DD;;DG?@>GPPJJKPPPVTO;8;:>>>BK=,>R[hjklʾʾ̱J0%Pg[/úľÿƼ¿¾¾ˠmbphbck|tjnzvxwjVZhstzq~vhf}mbbhtzlccekfYW\jstlc_\NPQ\bV[UMLLJLQOOhWUhtjU\\bzſT*:KVG+¿ø·¾½ȽýêybTTO@=Ksnn~srk`Yah\Sn|j_VZhz˿Ƚê}tympz~r]RPDB=FeJ020327=J[hjfgmtl.&>RG+!Hµνÿ¾¾½ʽƸv^TQOOHWP:(6Yvh\f}ſƵn\Vcvt_et][Ŵt}~t]PJ=gh2-6>CO_jllc_Z_]bhj_h_'-7=.2Zzտʷýľ˽´̽ǽűýn\fnn]G41DhúĴ|shsíº¿}mkwxphwbC=K\kyr^Tc~tL&"&8;,"!?`yϽžºɾóĻzʿjnȽŴɿȿʺýúy~y\ltkwngzY/8G=96(,Cf¾Ĺ¾žʽ÷te|xʻʻt}¾°pbnnū¾Ƚywz~z~z}mk|v}ϵ\?91#>rT?&Bk¾ùÿϽy~qz~ļºʹyr|ĸzʾbhtthdnz|zȷôɾ~fgɾŴ_PZTUhm4%=nüȽŴz~zsv~}}zsjʼIJϿȽſ|ƯZdþʺwϟtɨz|umY>#+Hv¾ûıĴ˿˿ʾźַnǷݕbѥʷîùünQ>-"%PʺðƽϽд̽Եİ˿|swtH,%,Q~paUW[UTWVTWϽɬ|ȽúľŞtwr]`kflstlrvyqa\\ccellf_\^a]RNHD@9Pk~s\D: !),'-7JD<9-(114148:ɯ¾ɿ·¯ƼʾfV\wv^ZTQUj}~ymcmtwxwnntyrv}tzhjktjU]_cc\Z_mbR?@VyzT:KVLNJJK[`YaZTJDJL@=5/-53=@GJC5<94;278;:648.' ),;HH[U=,!*4=;5)&.8;1503:4.4?GWUWbnrpjfnwx}~z~ph`_dcnnhdcccenntyhYab[\bnzyrrsttwxnpvxkfcmvgbht|vqp}t][bhj_\_jlqjmtwphklgr|ybYTQU[^\PPJL\rp\VIJO[\^UPTO@.,2H]Z?322)$*4>CA<9CFGJJJDAAIB=>>:AIJDAAIHKVZKHKKC:=FFF9BKJ=96BRP;103D?2019KH@8;:HLGDDG?/(,,%0;8668;DD;?@I=@GJ7328;?@:45332@Ra\C0&%-2,,3J[]75:BKPPB=FJNQOPWPJFFFAA>:8=@GB=>88@=<7328AA823227>CB=>@GJCFFJPQJFFJTWMCDOVZ^dcTJIJSS\bccgmj_Z_aUFJQOOHLNPDGPUPPV\VMC>;;D>GKH@FFF>>=JJDGIJONHB=>=;59M^\G=BKf}~sU=;5,,%7LNDG?GPMC@B>/("*4?FFF@BIJD<9?@IQO;15539;;48DOSSN;("%-8;:67=.$"(*,.882ȾɿĿɜl014%#Gľ˸ߢbkÃF9<2#(Ožž¿ýCDwe@yɤa8-20(-[¹ƽľÿľT"Bړ(7ʟe@8?>01a½Ľr3JJٷK@GB/=yĿ¿þȾýÿݥ79JxϥY25KTMOx¾¼@ TܬVCШ|tqI,-KqmPJaB%$'ڥU%tʍD&+89789>@PmwhQAGkľžG$%^P%?ֱl4%,9IPQS_lqkV>6IpſľN&6V&%3yح|UD@89>@@GO[gn^G824CfĹŽ_*']зc(-%IֿsP9-%.2#+2./>Uh~ɺs~h@8:86;=4-+,+%$ !%$%$-7>@FVtþp$ ('%%'/+$,2,+02,02-7KhϽG>G>2,0,+01471./%%'&%"!%$*=hD)+,*,2,0,P`P3*,2++'%%->TdmwܳmA?F82./3896;89630230,+%+,.%%#%JſĿr*(,+,*(%$5dtM530,,+%+)+%%'-zо]=45347896397:8?FJHIJH@81./626;8+Wܚ="&+(,+,+%+JhlaPC=45,+01++%JǤ~Y=-+./36;8867886>GRY^fh_ZO>26;88DID2!6R%%'&(4CJH82>T_ZPHIG>;82.14.%':JOVtرwN6&+3872697478=<78CKMRcpsd`\NDII72=BD@=4nþg('%%4Nth@8JUSNMR\ZOA6-+53,+01+/FID\߼L264-7BG;83876;=DC=>GRHIWgaPKMKMKO8,+09DC9qȾþ¾g*&+(4N|m]]SIJOVY[TJ>/+14.264*',93**8>]ؽ]2#7DFBGDC9:83876:AGJJJOFHOWO>@GRLHF8,+09=BDpľĿܙ?$%%+,7Lnn^WOLHJOJ=-+38;83478=53,%*Pv⽓`>/ 1STJG>;:A?82.4718DKMGIJOMKB>>30896302-%$-24JwƔ^>/+3.;@DKOF>>GTWO>72023.//6=Vÿſþ½¿ſܙO8<7-+553>GR\w\C5=B*'4=B>8247853,2,0982.4@UΘb>2,+09=IPLC=>=<=<534;834:8?;@DPY[NDJOVPH>6023.++;@DIɾžľܤH,JhS867:A?>GarN;@D++78C>;>>32,09264;@=<514K|Ьr>*',38;5=LSNFB?896;534;9>@8:2,.//0FVG>NĿ¾ƿſžP$,YvmR>6=JOFI\xxpzx^OVY;38;>>AG=9744712:8?;>867:R^azӳG%$-262:>6=HOLC=<=2489>@=<=A?B>;><Oſõþÿ½þܫV(,>MbzrN>@FQSLHPTWO\kmjjjheTJG?@@@@@DCC=8,2:>8>=<51@G@81\zJ2,%-387/6=>38>=>;><;8<<;8<>>@@@DD@=./36;@JOJPC7[ƽÿ¾ߪ\/+GYcpzkVA?JORLID>>GPTPTZcgn|lM>>G>@FJJD@=534;264;>>KM<1./Nț\/ $,534;2534;D@=DC9>><<<;:8?;867478=DC9>;8<:83387/-7BDKOMKND2.tþþûĿ¾Ž٩a>>UhnmjZVWOLQSLOOF>8DKBCFIT^rfQLC=@@@D?FD@=97446='%Atϥl="!+2896398>84=B<8DK=<=97A82489630/6=1$Mǽſþ½ߢO6Omw|lS=4Gaj^USQSLOC=JJDDKOS_q|bNDJB<8>MPQPH@@@DJH@=<=962-+:\t@(2269974<<<;:7886;=D8>=>788/2643876:62-7>DCC>60867/148>NVRLIG>2)2j½Ĺ¿l,+N]SPC7,+=^rhYQVWQG>DIOVPOVPZcq|kVJJ?89AGDKV]]a[ND?:8?;85343.Cy࿍V9-$,9DC9:7886=<=9?896>;788614.2023;8<:62-7JUbga[N=B>8264;>JH@=>>A97+DĿǽ޾ĿܫL"5==456;88=Ua`VRVWQH>?@GTRLUSQ^fktx^VN?896>BU`lqhTJC=>=8248;8+'K̢f7)2@@@DJ?89653>89>@F88>A?B>>;7264*38DC9:76;Yv|xfQLHJ@8:<89>727DG>@/6qøס>";834:NVPHGORTOOOVPOD:148JORQS^a`ezq`P>8;@DIHTda[QSL=45,148:1(;ßh:'/=BLSVRC=8>:8:A<7:A?>=JHIW]]UD<7-/6=JJDDDKVygVRV]]OF9264;4=NVP>2,Hľ՜B,A67Lh|zgVT^YQVPHKMKC51,2@GJJO[\USbztgaSCFJJ?>>AFITKGI@8:A?:1%-Mby˱_G0"&=JOVfvmP9;8<:6>;72>;7>GDFRcpn_M>627>DD:?FSqgVTO[faVMD:8378CU`O8FBHOWYQPTdmzxzt_RLIG?82:FB?DOOOVfhQA@GJ<7[αW6&"&=9DOVatfL=<51@=<51@=;@DIAGJbgcTJ=<5:A?8DO]}`JJO[gaSWVN?974AU`O<7-&VɽΝq`c]h|}vmnvmebV]]SPJJ?269FBLSVVSTZcbg||~l\NUS=<=96/6HOLjvjjttS/+TؾtM6-&%+2@@@P`q|d?826;=867::AKMGA6>GRLI?896>B>GRjsqSIH_la`]fWF8,09PYOF92%>ʜ}sxtt}wsxtg\STZWOLQH>1?FJPYZVOOOVV]Zcqzvttt`J:83-+5<<;Tmp`V[gfV>+2@cgahɺ\J>97+'/38;5AUnwswyD26;==<5:97KVMD;83;@=89>789>@Fns}_R`sqklk[I7./:JPHG>2,.nܽ~|lnmjjxznfnwnf^ZVOKGHIA67;@JSTSMKRSTSMVfv|yhU?8267886=GD:86>C=>.%')+%+2JhsaP<1/6=<1.6;88=Jh|zeq}I7:8?;8578=KVH>1248:833896>>=862-7>GKMKOPYZW^fkky|zyjQLG>;CK@8,+%-+.-1./,/>B<*''/CS_jjaPK9--79>G>;,2@@GJIPd~vcpvH>?;834:6;=DD:3085==<73085A?BW~xpyrgn|nVID>CFJFB?7.%>Ϙb[gWF:AYlkaVTdqklb\RJH@=788616;==FIJJO[\`e`\hwqklzt}tgPCLSI<7-/471,25==;834(ɽܾ\UZOA09Pbg\S^fkkZOQSSIA?:6;88=867AGJLSRSSTSM_la\`a[jsd`hsdP?@;ID>26>GKIPSTSMLC=@>>32478=DTWO@81CS\ZPHV}vx\A67;<<;:786>C:8:6;=D802=BD@Pkyqkwslqxt^GA>@FJID;8<3$j֪s_RPH@7DU`YQV`e`_RGOR[NC=>=86;8<:6>LSRMKG>DGOR[PQJLSgyqwnmz|bGDNMG>2;@DILMRVWQHQGC?@;85==GO\SJD@GJ\faI72==;;;;;;A=;86=DC816BG>>I[nzpmqxj_swlcPD@88?D@D@D?4`ܾp^RJC>DR__TJKRVVVVMPWULD@;;;=;847=DKRNJD@;APMJKFD@DO]fbadbalzzpmcPKC><86;CFDFIKRND@I?486;:>ITVV[JJRQZfheVIUyml}\JD:8886;478886478DC=;8?DA=?DO]tshhhlc^rteVPJC947>DIKD@;%*gʿ̞z[JC>BLTVUL@@JROQSOQLD@@;;;:>8837==DGKRI?4>CFPW\][Y^dmzwltreP=5887==:3764>DIPJRVJK\[cqxjVQzx][ePF;64220168886478DFD?>>>@886G^zpdbfb^\cqm^RVVH=;8?BG@@;8*2pҦwzpNJA=?OQLJC?DAOLJKFDC=8664258585585=DKTPB;;;:DIJKRVYVVdbfnz}cP<8;;CFVV[JJI?8886>>58=DGC>=;8?[zv`[cbZT[hhbZV`[JC?CFDC=8;6(1nwelcAGD9FOLIKDIFIKKCD@;A:8376200177327=GKSOLTI?4;CGKCAGNSMJL\[PWtmO@88:>HPW\LD@JJB;>>C>73>IKD?DOVQPVVdvtsh^\m|h_TJ>>564,06425168=;AGNB;>AP[nt\[ZYVO]lj_V`pmPMLJPJI?8=5&.nʂYtrUAGC8?DAFGKMJLRQLD@;65221622058=8128AGCKZfbSOIKRJB;>IKD?DSTJNxvUA;;;:APTVJC?CGKCGD=;8DCGKMGDHPVOLZmzyn^\a\c~q]PB:8394%(188864;;;:AGD=@HJKFPJIB;585501722056=<8.16=DKTklc^ULPJD@D?G>:COQLTbH=>>>=DKTJC?CGJD:DF;7=RVJMJLRPVYVOftjreVZa\WmmOB;5.173$%06>@;;CB;>AD@;=L\bjyp^RQU\]VVM_snQC>B=DG?>>6#0}hTljSDF9/8??>>=;ACFDD@;227=86641/81/9>>5.1:22020(*29FD9B@DC=8;QadawyjjjcY^dedb][T[[JCB?>>DCGD8#5ןBLnQA=?APqnYG>:2;;CIFA=22773>81227227>>>=64>DIUVKMJ?=DGCJKR?DAFGPh}\GDCGDMPQMJGKS\UA28HPNS][TT[[adafhjjv|}xjhtqdb]__UUhn^RJB812227288B@:8>QZW\pzdbfnpmrtlcf\JNJACFD=;ACDC3 <߾m/CbHC>7227>AGC>>5.7347>0)))*2478DF88BRQUPBHJJJBGKMTVJFDFKUq}lRC>BJKRTVJFJJNSSD8?STOLTVUSTO[cbktshdbnbSYVSOLQSf}~dD9200168=58=>>DIJJJIA=JJNN\ppmqwz}xme_TJLJPD@D?>D>>>1 FڟJ4BQMJDHv^=-+,0;;;:<866488864+,1)$%%*5FD?8N\PMLJK\\UJCPVQSPJIMP_}x]P>>CJKROLIGKMGNSM@JVVMDFMPQKMMYft}la\nzRCJJB>@HJ`xJ4017227216=DIRVJMGKCAIKKJ_s~try}la[TPQSPC8?=DG?=;8&VύPWVVMKRaK41+**247==5229488B32;[tlM,/CB;>ADBLPVVde_SOLQMPQKJKOmxSD@9>DLJGKMGNMJLRKC>IPMLJIFIMJGPhtj`laRJGDCFPJCPspZJ948;65379>IKKJD@D?GDIMPQ_lzyypmc[YYVC8?=DC34701k˟x~|pm]a~a7+*01,022772001470)$:CO]dhe\UJPJIMCFD=RxrLD99>DFDFKOLIMJLFD?COVV[J@@BIKKJcqmnne_cvl][TTIKKJI?>IP_f\M>@;812?>>=:8<=DGC?DAQZdmt~|yp^YVOD@DDC34.1+5ɿԸnJ4470379866481/-+#%0Iԥd4%*168=58[mJ>88BD@DDNSMLTQMA=JS[baV@@B=;APVY\[ZQSP_}gcYUUPMAHD822=L`ldM>585=B@:017>>CJJ>8JU\pzwrtwl\UPJDIKD?:83+%>ǵ}S:8<=88BD?485221.%%=tʼnD%%28948;KRZW\WUTJNC8?=>fgM>>>C>>@N\]PPWRQLLT]fcYOFIJC9AP[VV]P>=Dcv`ULJURQLH=>227>NedT@88:>DI=54BI?>GKCGLT]nztkgc^\VOIKKOF?>>=)$H¨õdD64>DB@DH=6.1+**8JlЙN%#0227>AMPQ__TNJAC@@;GseOF?DC==M^deZYYVSYVSY__QMJRJB8>IMJLRPDFMhrWPJIMCMJLA81222:JUJC9A:>@@;89>IB;>INJPWety}l]VV[\[ZQVQPB@D:)TƱrW<27>Khyj__baV^d]UU\[PQSPH=6INRVYnzzbSPVe_ckgaRMC>77+"ayjO7+37AB@:5.+%$/kQ&$/:868??FLTQV^\ahynM>>>@HC>77376281,0;GW\O@528?418??FHPNR_z|`I?DZmkgwzn]PF;76.(nϽpkg?.1<>>563,@ޫQ&+:>8>P`[RKMMC>JJR}~\JDBGPVe}lcfk`[R__ne_YYMG>:2CFPJIFITVUSl]>68=544781/22025Fa~gD82;;;28ACFKRNPVenzmR<8FcvnhyhULJ>6,0zоm>6BLF;-#/TJ%*948DRVQKRNPD@I^wOF?>@Hbl]fhULP__bf\[PMA>>>=JKF>>>DLLJU}vU>7:8397348886+*0320;eS:88B:8<=BGPPJRKRZjzwzxjQ=;LhnjjvzZTPB:,8ֹvξcGKVO>.%$%5Z֓8*379BLUU\[YSJKhhGDC>@HydY__UOQ\U\he\GDC>88<>@;>>56@JJJj}lWJC92001427<9401722,8nnJ99<8.8?@@NJPJCP`lqlrtla\PD:J\]amt\PJD7+3ԚRCQƧk>DadF*!+S~|-,89>I\c`cswgczwS:DFDRjv}h[Y`VQV[UUPWJAGC>8886>:?4857==F^l^RLD95,/1017>=5442001TR<:>8>=D>@HJNJAKhrtn\]V][PB>DIPN\tyfULJ>7+3Պ2$B\4%?lc9'>vh%+5>I\clrk>5FLTvbM^ts\V[UPVY\PJIBKC><88<>:8<=8=;A>@NYVOD<8;220.1<><85,/+*7_]>=D>=;A>:>HLJGOelmqhUNJPJOLTVVMK[zjVTJNC:)7֓57mW)$4Mn]?Y^$0?JTcv[;Ca\rtF;JUrt\U\UMJU\VQKPJD7>7:?DA:8<=BDIM>@HCD822721,02948;7.1+*4MthD9FD?>=?>>=COPVY\]apz|qncYG>Hhzn]Ul}laWPFD?89ߨJ%Zv`2$5O]l\eެV**8AG_}}NJg}wO:>HLcqm\[ZJCPUUPWSJ>AB>>>D@@;8>DIPNHDB<8;2162/+*035221.-470?YtwM>>>?>>=C>7@JVNSMLP`wzne_SL\|qvqdZTPGD=HB@:=DFMH=>25221220./18320/8<8.>gcA888<>:=D>=DFMHKCJdz}lhb^rtnznVVMKK=5zקB%=S:%*9I?>Q֋8#.BQr]>MżgD88<>Ddzne^\VKDCGOLIG=DG?==D>=88864;=;A:83412721470364,,0;?99\S522973>DC34JK?>>D>68<;653470361/-,0298886>:2277@Jap?485017>=642=DGCTcedht~~QCJU]fl]OLIM6(n޹cOe**8[S>a־cNJA837Adwe\[ZQNSML=;LKC>=D>=8:>8>017221/-,,+,17=8;;C>@;8H\v~Q8664))DIPULPV[klr}lWC>B=H\VO>JK73p¿ԥY,?V*,R}nMY˩zZJC>732;PpybSYRQLQSPC?JJD:=>>>=6<8;2199<1/-,1/-,6486;?DAFNe~[;8?5,/=;848??KHAIKRr~tsshVVd`[WPF58=>>8?F;7n¾سzZgݦK(Jwzn[ϵrVK@948;7@[tzn[TOLDFMHKKMMC=;8?88306>86;:41+,+,+,376288?@JR_}f=;A>:=;A:28AC=;8IKKJIT[[VV[RCQ\ef\[D8223208??5O~؊ADnyhtƳgSNH>8885;J`vxh`\SD=>DHHHC@=>888;625;9880---///,0462325;DNWmsD5;C@B>86238=>8:CLOPRTQNHDG@=Ndme[PD;65//,.32,0;JZj¿ÿþdDgzvɷraRNHD=8825@Ottjg]L>>B>>8>>>8>;D@=8825///,.3221//3;>>78=OZcszP5/=>8:468825?>8>CLQNJLOF<;>BRbf^SNH><;32,,00-'%&+6F`ÿ¿ſƽľ~Lf~ĬvfYPLOFC@6238Drvf`VH>BG@;>BB>>=>B>88;882//332232238CLIB>OZhnw^;+61/3223825;988=GLOB>>==>BDN[a\PLMFAD<5////3*%.>MNH[ƾ¿¿З`nwlWHHCBGGF<32,?_qjgVC9=>BD:;>B:;@D;8825?98250-1/3623=NWOJLV_hp|v~wQ1&%&,00.341/365;9FJB>>=KOJADPVWMFIB>8>;882/5///Od[>8dƾþ֮zznbPD<;>BLOB81/;RbfcS<04>>>:;@<58=G8825688982,00.7868?NQNRTYV]l}kjtvx~pR9,+,+)%&,,00786;DHC@BLOPD<=DNLD;CG@;8>>>:04415HYVB)HaRLD>>>KOOD;821J`meU@55;C>8>;8988=83;8825662.--88;8>BCLIV_\STgzn[PSdmv~mPD<0-'*%+,1468>B=>8CLIDB>8=DKOO@=?C@;6//9=CLQP/:ʥhJADPOPPVYVNH<01>UgfYIB>AD<988=<586238988415--/25;9>DG@CLI]aRL]twlWMHYjt~y}|fYSdn[>8//,8=?>5/=?CDB@=25;9>FJSNHA>?>>8/04>=DKT68ͥrlW>>IU[a_ac`VPD<==D\cYIB>A?>5558852501/;;>3--/.348>=>HHCHHJ[h`RTfc\SJN]t~zzztgȱxT6 $-25,00778@=214>JNJG@CB>>?>>;1/369=JL6>ҡtnbPBGR\]abfnbZSNHA>BL_aN=>8C>8/0250132,,0441///968889@IKOONQ_hg]V]_VMFMZjnpnbdmytqjxϤ`2#&+,+))%%&1//5;HTQIB78@C@BDH?>55:CIKC0DСzmmm\IBMZd[V_hneVVVM>8>C`n\A8=G8:2.341//.-'*//33558786;=>HJNJYdfce[UU[UOJJLVVVVMOZq|we[w޼k\VPD?81#+6=>HJLD>8=?DA>?DHMNDGU[U9'CСrajgVIKUgg]V\gk\V_\M>=NWhp^C07>>721.3410-'*--/262389:;@ϟ}vxnpfTQZ]YVZ]dfbYVZVH?N`vp`PD322;65044121.,0077882/568?@=?@DSZ]dcfc\OPPKIBFDH?>BL_nptggȱdD.%&1.-'04>=9828=GQSZ^ehh`HCküƾԠsn~wle[aj^JATgf^VVMPLCLIdzmVH?62389:786;21.,.-1/;;8829=C988=CU[\]_qn[OJJDB@>>I>BLWhsnjtËH((1//..3415;9>>>KDGLOV]eV>>>aŻШ~wtljgnlWIK\c`VPPVPB>IK\tq\I=832155832,,0--/20441=>8C>:21>@DSZSR\ljSDA>:;@<=>8CWhd[\z?3;B>>?32,?DH?889@;>BTgf_I,!2d޾~ytm\S[a_VPBMNOD;DPey}kRD;8221.,.//,..3410,0;<;><5863;BIUSNMZWVH>41578668?@V_\eyqG@CBOJA78@CH>40441=HYjjgV78@Nlְztvrj^ZSMFICGIB7>Uqt_O>6231/3652.+.3410,5587;6568?@HNUOKOONH>40044--/8>FSZsحlB>>IKC=83>BC:2+)0>MPV`gS8>RNDnܽ|wl_h~z`PJNJJA=>B>DRjgS81//525688623121.,:;@<=:;9=CGLQSPLMKI>8/020-'*14>D`ֵnQ;13;8558=>82.+=>BDYdT67>='Cʥq\SJRb~gSPLMK@=?@>BL_s~n[D2.+--/2055831//..88;8>=>BDJGHNUMFIC?>50-10-1*/6KnֽeA21.,./5;9>32<;>GY_VD2,+6ڵrL>?Tlzxtj`VPFA?>>;;DPV_hy}|fD2,-&+,.-1250132154157>BCJLFAFJD=8:;941.-12--8Wܾe;+%,007732ƾʍS<=Njtvqj[WNB>>IB>>IVVV`d>6550-10550-2=>8:4558CLIDJA=?CD:27>=.%)0.-'DyP/!((*/66FW[OPP]YD2$Mþܵr?8Jh~nb]RD>>>:=>BLVPHTϾ[D;C5///32,,09=C?>>>BCJA>?D:;9=832=8+! $/CdХk>(+4H`\PLZjdN2_þÿ֝P5Hn|kehP?>>;4;DHPYP=Nӯ\JB71//..)0.1>@>>I>AD<9786;2550-.-1+,1=\Ŝf>/&!6Kcf]YerjR2)zſ¿¿ÿs==>8CW\gcN=TٽnQG@;8;6/0441=88;8>=<;321.32<21'%$(8QvílB,0Jq|h`p|aM0$D¿_VzsfYIC@BLJNYdnhQC_ӷq\]RIKCF<368?;650256786.-12-'#3Q}ƙP)5fyhdzvVH2)eЙwh\VPJLVPRTYhp^ZjȱzmnbZVVVJA8=?=832--/20,+)%&"!(8Uyܑ<'I^]lsfTQ92ƽſˬwnbTLOPLOVU[s}vnɸpnke\SJC@;=83.-'0,+)%% 2PtɅD;CUmm\KI>9þƷkRGFIKUgnhtlWfva\\SJFA?882$#2PqljJ9PkjVH?3=ÿʲdQNJJL\zyhnhP?P~rLFJD=8=7*%+*/662.044C_gý¥z^SVMOU`g\S[S<=S~z\@56,+))(%8Yw}||BGgkdT>6TýƳz\TYhYVVPB78PPB=JaqwytbP:2++)--/.RvKUgfK)%dǸگvxwl]Y\PD=32<2,0JTYYVN>65)%+)-1&%>͇KOdfM+,e¿٥nltp`UOKH>B21.,.PlPF7*)%%$(/:D5%R΁AD^]K0-\ٝjtvhVHJNJD;82&%GгgJGH;6BGRQ;*kr6>VWD5%DþפtwsfTGLQLDB>2&2߲n_adf\J26p3;NH<.+ýܲga\VPHJGJND2,\ܯmmf^VD2Nv8=OD2, )sþĿċ]RVPPJDAFJ<0G̚znqjaR:2fp7>H8+)-1hþ¾ЋSNMJGJ@=?@.-fËnwytsnjU8=_0>?.-178PƁMJOHID@AD@,?ḁjwvqrjaP?C;.SܭtcsqlneUC;jڑ?,?D;<=6'%`ÁMBIOZUIDD;/eڥj[kleeeN43z4)@JDG8+%GŇTHITeeZUJ80vٝ_SdcYYYH,?s-+I\[Q;.-!1wÎ_SPT_aVJ=08֑THZUJPT=%Jk)+LeeZH@<&"ReUTSVTH9,+AЃIDNH@IJ,_g&,NeeZRPG/8dWTSTSB2%%Jz@޽W%,?DA=679,,⾂VNNNQJ=0%4zd79853,'hM"'12180&0*9zPDMQPD7+%=N,6660*:֛F%%*0@D;/-!#QtG8IOH@7+>ܞ8!183,$RΊ>%%%8NN?4))r߿yB:GLD?=0Fӂ)343,!ht2"'&:GD;/"-~‚H@ID@BA-&\h"202+.\%+,6;4)$9|PGF;hySPG8@A2SQ%%532"M8&,+,62%%J|b>DtްnPNN?@<,pD'+34. ^|(%%*+,74HHScOOH?=3,5։8(220,n]%&,+,6Jtt;DܜYMJID<,+Rk1+343,&G%&,(&4\e4 ?͇QJDDJC-1wJ,+34+'.ێ8***%%5eP18tPJ@AID,6x. )34)%2ܓ6.-+'%1an<,%2mSPG==7%9^%%*34.*,F,0*)%,^wO+,&=ٜgUO>>?7$>V&,+,3,+0b.-,+.rc9,+%$KЗkYM@JP:*TT*002.,%FՁ(&**,%4ߴ^43,%h¿ҝzdWT_P0*kD#-4353%eܟ;"'&-!Vg1+-!,˔saV]`J*0zٝ<,02012F)+4}0GþyZUT_P>%6ܭC.-,1+>ߢB%&[ܙA+~ް^JOVYM7%&#rq>>sߠPGRWTD/Jք7$.:8-&_ܚ:%*mԷSBPTF;,%eN%+>8(2}ܖ6$,08]ީZADB:1+3k),6*<ܚ5,GLTZUJhV<=;4%%H؍5%+' O>+IklaP-/O79<2*mB##%%[IDhxV<&,}J8>?7%9K"'&%VޥQPxP0&.ܜI2==7%PT*'%Jó۴pcB%%8ܞG/>?,+eT)++@͜qzd2"'8ژD5?=%,z[**,>ݺtcj>%$%6։=6>>#8b('%=ˇ\D%&"=r0*83"Dh(&%:֠V&%\a+'74%Vk)%%*3V%1^34@<,jk),-&6^hU9>JD6yj20,$2s  UC-=ID-&%wf.4=67Qj9,+ #f[*0>21Vc3,5$UߪN&4D;/bV212+CܖJ,9I=6qۓ@,62(7ؿyB@PZA>||-&12%.b@PktPAz+01*~bRgwvL=֑=+,7+/ϕa[kpc;4ݟO721'8tPJ[^S+3ޠP92"PL==DJ:%9ڙK=0%7ɼv34321'%J֍H@7mޮV(14,%&aJB19ȸ~6!,50 )zݱrRL50\Ǽb4,8:.AИeYT=6f˖bNLI8,5?ȃ\\VD;Sșqkl_I/=wݽyY\VLDI]Ρvfpq^MSڷsTVVY\T=[Φ|kll_Wa߸xVV\m}qMS㽁gklcWaǓnfphr۴tzq^V_ˢ~qsy½¿½¿ʲnf`csæ~|ƾź¾øµDzð½þȸrdcljapϵû÷Ⱦ½žźŪʲؽthbnfFQǷȾzzzqqsrpvy}}qhdc]\\k~|qkz÷ǿʹɷʷɽʦzй}~|hbSBcǿàü|pf^ZWPLQPJKT\[TVce`[\eljkllhdrynfmnhbklcY\clwzz~zxs^T\qwrvyrpwrmnklcfkllhpqpqpvtzѷǼùW?Bcn¸h[h½ĺĻǷǼyYNLOQPNLDIVPJJZa[TQPTNL\mm^TY\cVY`h[JMIFQ^agkhb\[VVIFJZ^ZJFQTNR]\VNG=638<38<LQJFJMJPLPPPVVOJB><<7777<=6=JK@>LUO>8CDGC=>CGC988I]\ap|_1,AUO@+2b¾üǽrpwwr[6!?tɷýrdtyxzjpr]a[\_gvnUOMS\VL=JVVVakwztqszzjnyxwuO6NYK,+Pÿ÷ȼ׼ȷr]U~¾ùǿ½þþþï}nnbGCJBGJF=LQ;8^fkvt~^<)"7HJ>++ImýɷǽǷ׸ý½ƱžɼƾǽnQ;821@OJBU_IPmnI%B>JM?+,Nnȷŵì¬Ǽrlq}~}Ǿî²ĺ~aSK:.6G\fkvſzeT=*RnI>+#Aqŵȸú̹ý}TñɺǷzxthtǼ\\kklt\-'Hmy}Ⱦ̾ɮ܇PýùſѺq¹þɮ¾ȰĵwcJ?DH>2+)!,P½ðtӹ|־ÿĺ̻źx_RL:.,`ǼȷӹĵíɾþĴZ=(8tymny~vmnksƾоɽþ¸¾Ⱦƹ½Ǽðt||~yxnlj^aqkz||xzvyrnfme`mvtqkd^TLQWPH>LgeTA-$'+$/M\VLH>DIJ><=ABW]gklc]aegkhnngkhhljkkllhphrwmdjwrdegjjjv~|yxwy}y½y}|szzwnfsznZLQjp^TG=Lgwznfs~|xzvtqsznljkxsjjgbegjjptzztzn^Zdjpkgfk`[RLNezfKAJKSOJGJOZWNLD=JO>4>=67?BNLOGCJCDGIF=67?HJ>:.&")14977724>BDIPPABOQPJJ[bWTVcVNFILQJJKSTNRABFI>859ABBDCITTNRQPNV_aM?BNRLNDAB<ITTMIF=<D=6=?<;82381+'+3:FQIFOQPLIDAžþżþ½þþ¿þȯ¿þþſżſľƿľþóþ¿þýƹù½żþ·ſ¿þžþɿž½þ½ľýþ¿½żµ³ľþúļǽþþþ¿ľſýþþþĻ·þ·ý½þ¿Ľùÿù½½¿·¿¿þ½ÿùþþǾ¿¾ñþþƽôýý½ž¿ý½½ûþþúþýùÿ½žþƹ¿þĻº¿¿¿ºùý¿ºſ¾þķþþºþþÿ¿øʱǾþþúþþþºſ¿žñÿ¿þǽþº¾¿ļóýƽļþþǿþþ·þþƾþÿƽ½ýϹþýºþýż¾¿ýýýþþþ·Ǿ¸¾ýƿƿ·¾¿¿ÿ¿þþǿ½¿þý¿þļžÿżƽ¿þ½ýû¿¿»õƽż¾þþýúó¿ùÿþ·þ»ýþƾ¿Ŀþó·¾ýƽƽùƿû¾¿ú¿ý¿þúþþ¿·õþºþ½õýþ½¿ļþõżƽĻÿþºþþúÿ»½½þýþþ¼»þ·þþ¼ǽÿ¿ÿþüý½·þþ¼½¿ǿüǾý»þÿĻúûþý»þºýþþǽýĿĽù¿ºûȾ¿Ļ¿·ºþ²þ¾ſƽƽʿ¿øĿŽþǥh\ʻþ½þ·ǽûþþþɿ¾þ¹^hÿŸe+DƹýýþÿĿƽýüǽÿºǩV2bȾþŇ*jƿþº¿þǾü»ý·ſĿ··̪TdſԨ>>ÿ½¿½Ǿ¼ſþϿn+}׷]hÿÿþľƾþº¿·ļ|#D׿n%+¾ɾ¿þ½þtsҽv-VŽþ¼½ƽþ½þŲþºýĻþfP|7%ſĿþº¿ǽ·ºùþ¿ż¾ǽķÿÿȺ¿Ŀÿָ]6ā6@ĽºľžÿȺþ½ÿƾǾƿžýżǽٽc!#nѾ}7aƿþþþż¾ľýżŽþżµžþvJɂ4("*żſÿƿ¾þǽþľƾþþև*!4O%%2#QľŸýƿž½¾¾þ¹þþþο½ƿþǽþľܓ.#$,HJ;VW844,,tſǽǽÿÿƽþþþÿ½4%!%','(+.20(",]ο½½ý¿þÿþźþĽD%!&%'+)!1+%%+%1PpžýǾü¿ýǽþý½½ݸ]"%%#+384?I/ %,'#0Wÿ½ľÿ¾¾þ½ľÿÿ¾q%"#"#$%);]jH+(+(%$;v½ÿƽž¾žĿÿ¾¾þýĿܽh#!""#+Vv^B4(%$%-RžÿľĿþþƽʾ¾ÿ·ýýì߸\"$2@C4(3TWUH2/0&!'"'Bcþÿǿǿ¾¾þÿ¾ǿʾÿշߴQ" 'Bmg=(1DYUCHO>)!%)$!=ÿƽĿÿ¾·¼þ¾ÿžĿӽٓ8')!#=hdD:K[UHP_\A+($%% Mƿſɾǿ¾˾ÿǼ؜K%)+($4DScwvghcVOR\`VB/%)$+(%.h¾ǿ½µþ½ľ½؛G% '),,,,60,4-*)&!%$8`ǿÿÿŽž¾ח=$NlcM;216AF>0,+7Tz\<-9IJ@95.*)&%$"*/Hÿ޾ח:6PlxfJ:81/07VpvgH2:D>6665.*)&%%)+/%Dÿё>8P_lnh^JC@CBBB<95+8P_bSBDG=7378121/)(94-%$+AhȿʇF16JeqrcMTefSB>@CB<9302=QYZ]ZRJC?729C@4(360*)&,%$(H½|:2RdlnhW?-?evgPIJHA>8581/FLRd_\d`K66FUP?-,,,+(,,&!%+Púݢ8;]bSL<-"=cwzl[UNIJH?=21=>@RTezpL?IShwZR\gSCHPHA4-.*'),66/*9zýt 42/=58>@=QcdltjRJHAC@47CDGBHVeq|ypkbSLLRQPH3+(+0221.*'?v·ž¾ÿ¾½¾þc':27202=HGDGRd_^pth\J?>HOSISchxwZD:AFMV\WK6./0+029;1/;vĎ92mǿľžƾýǾܼm|qrf\anpf\cwtbSV`b^M;8<9=>9;>HNRTYZL`sZRIAFM^ffP<-,,423=2 <ַb#0aɾ½ƿ¾þžžڪyghfd`LL`nhTMVkn[U\`^UM@9955588>ISYUTWM`|qth\POQ_stbD1/2162/0a̅/2wʹĿ¼¼¾¾ſÿ¾Бkbd`R=2=WbSLL[eaZSZ]VTMD:6-*-9@CISYNIP_bhfkzgSISk}vV:21/2*/3+2՛:Iýÿݽþ¾¾þž׷|^fmaD1,6GUUU\a`]POQNRSSG=95+.>HGDJSIGMVRJM`qxaPQYa_M@>860*25.*(RױQ%W¾ĿþǿľŻÿ¾|^^p[=7FLBHKJ[\[[UNLLNIPC@>850;FGD@NRSLLBBTemww~}qWKHACKA<4-.588%0ĉ8(9;D`wľýþԾþ¾¾͝|m\lP?>02=CHDJSQPHACWbjtjk}^B95>HNJ@8428<%)ʹ|úz) 8PWKW¾¼֟yymWZD>IQYWKHJ@AFC@49;>2)2?IJJJJJPHGMP_rcet_A+(+;FSSC@>81":IJ|q]P=730,+./1=Ql÷V" %HľþŽŸ_\tZD<;FSSIG>>HNNIPJMSIA8==7/:D=(*9DPSIGMVRLFDP]ZantT2%)37GUYZV@*.Z~bSLD:666/"'?^пK!VżĿŽƾȂNl]IA16JOF>>8=KUULLBDKJ>0423.>F>/07@KUKJINRJC?HOS]jevrJ1/2*2GfrcM;+(,MпpL8('%$+$ '%)+/-*&')% '9Tzż3(5QÿǿؓNfgGD@,6AF=7=7=JWYZL>85=>95.21=?=306AC@>BHKDA>AFTMTWM[ztT20,+;bqdRJH,%5ų|^G=96*)&,./1,,&')%&,,,+2/0+020,&'-4DVl>,QnоŽ\[L@GDJIJPVJ@9=73<93/07@A>21=??=3<9=?IJQPHDAAF^xT==7=POQIJH5.*dƸw`KD7(% '%')4>@G=730,,,+25.2372/07423../1@G=6AC?=DKFGBBFG>8;;21864238=C@4/:A>A;;<9=?IJH?=<9BQh|kb]P=2/=>@812@hɾv^P?4-%!%')%&,,,21=??=7/-*-2/423.4238423841/24>8(%7\}(VՔ=7hfJ@AH]xy`B<9=?;;A>2/021622162237?=;;58>BB<<9=:IC@4/.*+,,,2720+0>MNB41=@999;DDKF?=DUULMNNRSevZD8==4-%!#"%7NYg~kWKHJ8(+-)!#+(,21622>82/421'4>830658>B72588266/,681./:218C@02=Hc\,׭ma\TWMJMv\A81../@CB<46*%)+/-60*2.2?PTC409CA>7ISPSKAA;2)(++-))!#+0Fcwz|zpgSC720+9I95>81..02=82/4;;<3725.23864337?C@47:<9555./1<2)2:2VǾÿεxahfkzH+,,4429=>9530*%$(./1,',68JM>048CHACKUPJC?HJMSSSRT]ZH?I\xyUCGD@<93/8=C<-,/:30656<>>=73016>D@COB<<66624;=84;>=815975942228466<<<<<=884653/-242;HBAOYP>1/@l}8BĮ\228242284651/+,+%*24+%%'"".BJ>2,5HK@;HNHB>38GVVJ>CG<688=FJLRLC<==A=A=B<28=6628=6>JOBDT\J752,/PV(8ϼj@4;>594594+,)+,)+%*'"2_}C1CG;69Q[NBAJ>CBADJOKNQONMJOKUt}jVPNH;4;FJNUTJ>CBJGDJO[]VQOIAL_`RGDC<8=8BJWcjjbZJ@;642<662-.4;>5=A=97<<<=;69846>=88DT\QONZJ8752%4zǜc7)xtNB>==877*'&,+$-8Lv֚I3D@8=Vg\JKNCDLVgnsrnl[>23838>==853,+07=A=B97<;=:><=88752>O_`]V^VJ>=667nǹ]0gênVJ>;6988DTJ8*'&'"1MߪR9D87L_[NOPC<=<7*/@DKZTD@89/-4;FKVgnrtrnV9,5=242>=61/++,7=<<<=;=887Jcja_`cYA7=<*'0Lkh\J=+WʿdQH<.)+7=7?AB<=88.:OP\\Q>230++2;CGTpzzz|a@;>=6166<5662228@><=<><=6?QVQPNF<38>?807KVWVgtr[NOF<)'"+b©mYP8*'&38>?=8+%%$>|ܔ>%2;97DLJLa|tNCB<28877=A>3:<>>B<=8==88787784659>LVfwzxnV=+'"%.80 8|ê|aŪx\2-9>B<=42%'R~+ 3>BBADHQ_YVWNMJWV\\PKjmRGG<24?ABADNUUOB224??A6.:Qgtws`D0+4AOW^nlW^|xn~sD@89?AB22>DD>7?>==8=8=A=BBJWWVPJ>4+,7A>:<>MVcr|nB+%$$$,%'=A=&-cʿ¿ְjNMbϽ`DN]VD0+,%*JN%8=8BDKS_`]_YMOWVV`WctLCLJA78GLJKNQKHB>846>CGB28UnsmgVC6613>DD?DKH75;HNMFIMPSgtwnV=/%*=FA78=AHP]hq]0%*'.484=A2&,Qƽ֛=#;yʪh?LjpR.%5C^ד8&22>KNQY\\cd]V^VQP]zSJVZJ>;;HV\RLNCGJF<:47=LJ=ATa_VVJGGDJ\jjemytzJ4;FKJG=887<B975,+CyЇ&=zW,+Cwl63[h%)2;MOMOWV`hf_YYP8_fN]cYB<2>JV`VQJBJPKA787=AGJVVVJIAADU^n~x^cwzJAJF?LF<:9758=GDT\>2=FGDCNe}q]OI:4=A=BK^cUC5&2DD?80'YƼ΁ 6eP%!6\f,5^D.4@IIMP\\\RPKSJ>n~cY_YM=883::<8=FGD]s`OI@;D_zphK66BNMJWUODDPS\j}zhchm\@;>>=6168=8BDFJN^nVJUǫxQ:4=@IamY6""###$$ M۳R"`pA+L_dQPޙ2%2;CHKN[]ZYVWCGhwsYA=FNMOP\]VVZYVWC@;>FP[]PKLQV\hqzprhmzZjzRA>:A>3-,66JPNLQ_`]UTOK@=<<6=FNJ4%"%'%!%*'!RظN*'%2;69hݢ9+DD?DNUNQVVJI^xdgcJ>CHQQOV\\\RPMFD:CGBJVgtjbqywz|tlzbJF?D>7?>:<22>KBASvɩhhq}jVIA9>DD9,&%*')+7("8А>,P`]q_%#;>==OPPS\TRP^n~z`hU==><=1*253=LpƸ~|pr|n[H<;=:84*'&'38>-%"+}ÿżϔ>WܝB%5=FNJPS\TZTZjrhjbI9QTDLJKKNQY\\\VPJG=HQQPK\xzxhjpnl|ZNH;=A=B942++22>GVtzztleVYnmYDJKH>1+%$(97DIM\OIMOMTpvh]OIMa_LJKFJNJ[dgVJ>KNCFPRGGYn~}tphmsjj~]JGG?A6A>:,+0383>Obmy}vh]VVNMb^J>NUN>1+("+22282("M¼κG%,5=HV\LCD[k`RIMJPUWc|h]WVPSYrzS5*+,79:<8=,+03/2G]]]ltyldpxwzzzxn\J=CBDJSRMDKSYH753,/-&,87$5¿¿K'.7BJW]O?Ldj\UTOSRT\h\RJFJ_ldSRTQVVamtePA>=PysU=2)+28;61-,,+03=LY\gzxtljz~lddYVa_VDD?D>BJLD@CUTI;690+%"07/.ýþþ~6"4;FUh\Qbtg\ZY^]]dQPIJV_eVMO]]Tatȼ}lT@;6KwkRG6)+2:<8...0+4?LQVVsxsjjzzldSKNQYQH@C@;>FIFD@LJ=9758)%"02&)ýþýܱR&,8DZdpwŭvhhjf_`]LJPV\\PGN]ZTcvvh]H<;IevbSJ6)&,8=A=938>?K^\Rcrwztpdgnlg\ZV\VQJB;=?ABLQVMFD@;1/++-,,84#%z¼z2,8J[j~ʱwlddUORTOP\VZYWNOesdgVPGG>=Phj[H7*/8=DJG70=FNVmp\kkentpdghdc`]a_LIAA?ABABJPQODA>:,-,6/-/84#%zýƾܦO'.CVyʷthcWV\ULJP[]PMFRhzs\RPMIF>7BamV9..0>==><.2DT\]xqhffmrnghnlgbZ\QFDDDJDD?8>DDC=2)259N]Z\vrhjtrnh`hrtldSKD@LGDC><=<97<;=:<>75242,/24+!.U-2Rn_eaW^\RUYVH;=?RhghzcTB<=8=A=BK^V:&%1CA71=LYVQJMamffltlddUS_qpbSJGGJ>;;97<;75;846>=;690..01/++9þýӍD5Qz~lhndgcYTRZ^]WI98=V`]ahzZJ8877884=AOWB38>?=9>Lhr]OPNQHPV]][]PMMaldZTDDDPA7115:<8=;6988=887/-/383*%Dþf8QzwzmpvhUTntrnh]SRT\\PI98AOWVVYam}TD8=6>2-079>LGDTWI=8=OkzlddhK8=>DLJMOMTefVJID@?FD7*/8975884659=A=6.*,585*+ S¿zIVhlqgf\MFQjzzzyn_VPNNNJD===ADCBJWktM>98520-,8>77?\y||\>46=BADGGVd\MFBAA==82-,5854/258;==A=0'(19852%"(t¿}OgvnqtvhWRQKDCQjtws]TPPPMFB58@?>979==A=DMahYPFFA=52.-,55252.525885@7*'(2;720'(@¿Ľ¿vKnynvtfg_NIJKDANqtwtWMMRQQK=07OgeVA-%0Mm|paYI;7==A==BRkmYPFFA30-+,1301115=611525-%0258;0-+$`¿tHjljqnc_^QKIJFFY~sVJKQSLCB>9<==8DM\mraJ:/26-+.;=8><:C[`L9,*'Bbg_Z[NA=:>9<5=PkmSFA=61/2.-3666115>911524'(2@?<:6-+)ȿ|UzhWahmgf^QPD98=Vs\MGMMF68==866==A=DSZ[`SF;720-,,724;=>DMM>+.11D\TP]qgSF;:;:;Nan_I;==8110-+058;0+.9854;9+%'7DMC854=616=A=:Ma_VL?>724/,*,*,05;BHD9+'(2@MWMI^wnYI@?<=Ldtw\>98=724/11+,888+,8854=:/*.2566185'(ûĿ֕m|sVUgfb^b^NI?>78CazdF8=DC=6:;661898=L_cP85420-20')(+.18><7*(+,1GVTPOUYPFFLJA=Qjtp[C888846301&*4;9256667<7*463256672%:¿Ⱦٛjkv|d\^mjkmreVD9<:66FmnQA=CB>1152:;6:;6:IJF20301&2-(%(+,256-+%(,*,MRLJONI??>DDDMMcgfUB:;6:8884/&)2;=61//2698/98544;920 ZȿÿޜaYhrlVP\trlkmd\D=4/058YnQIDC=82.28>6=A?4/8><25--,,0-+$,*,985,*,+''.DQMMFJD@?<=ANSZ[VUM>98=7;7=661-2;=>1104;>9152.-2661-5¿Ľޟd\gncMFUtveVY\TD6-+115PnVJBAA<:6<:668=D4/8<:6,*,+)((+,2-2?D=<:=61(+6FRQHDFA=9=HW\YPFJD@98=7;52.566189=6::;FFA=11+11+1.%NȿƿŽƿܩwnvnVFQaneVYWRJD:/1104GxrfO==A==BA8884/36661-,*&*)((,*,905;HDRen_I2)2=LG@?<=FA3L_YPFFLC8366:;20-252.569858=DHD@7.2820-2# hȿ¿Ľ՟sntjUMYmgPDKMF;=>;720->jndNA5?>7?>78>4'.85'*.'(+,+.1.-,46;HWmvVF:;6:86=AHD@R_VSQSKD256674/09=985,256625@GG>7366-,,0!+ȿƿԓb^hg\Vgn_G@CB>8>FA320-7[yZJD@A=:>854=:3016-+%'(%'*.0-+0,1854PvyaPD115AD>DPahgfbYI@46;:;2.2@G?>30168884@GD=44/%(,/&D۔VUnqfgvt]IDDMC>>D=6:2-/NxrJ7D\y}xxxcP;7=698544>D=3011-26>DGMD=<9+%/2."eՊFFxpryn\MGGML985:;:8/,ChlP>DMF7*4.-3854-,,0!$%(,)((,'(%19=AWzwI25@?9LVQA>>>9<53)(C[VFJKD=<9-,5520-%#&*$#&,*,0+',18DSZhg;,8DShzzpmg`\VC88819IVPNA=572-(/9IVh|dD6:6-+ Sɿȿƿþݾhzsn^F68JQSLJD@94632->TPDD@?<72-,552-,%'**(%&*),*,0+25@PWRan|pG8?O`nm`\ljYPNI??79<53/9ISZwtM661-,,sÿШdF87781+,+04/0,(+,.-&*),+,+04778>FQabzP82.,*4ڿyw\TDHD@JJD@?D=442;GML>>>=61/)-26.-&)((,'*'%+.1.25@BJDCQSZhzz~|wzz~|p_G?D\yzz~~\D9DMCLVdb^NJD::;6:PW[[ltM=61(%Dϧn\\MGVhgPJDD@>DMC837>>CPW\`\[[VU\kmdrraHDRmr|wnbQAHPNUgmgPSLA=572=V`Sa~ePD@2)# Yٷ`S;7DfzsWG@>BAAD=<9=BHWOD95252-(/)((%'**('(2+,25605;6==AJQSTPD>D==LcljntkVP\hgkmdbgaPGMLYmqg\VC=6::8>VaYhyaRJA5*'%mɿˉeM19VtwgT?>DDDC=866=FLVQG8584/0,(%")((%&*),+*.0058366>>FQQIJF=6:>>D_mledbUMRe\\edfgcP;AN\mnjaP>73==AJab^tnTH@7.%0ȿܱtG8?g~n\OB>>:;6:80-6FRUYRJ=61//+'(%*'%+++,2/,-,,08256==AJLJGG>6=A?GVTUSQOB5=PW\ftwgVFJGManpm]I6-3888[`Zd}mTH@;,%;БO=PveVYJA54;920-8DM\dbU>40-+$%'**(',*,0+2/,-+.97<7;=>;IJF=58;DIJLC<:=A=1+>Tclxr[CION[lyn\TA20:;:Jf\NazlVJ>91%Pܱ`?\nb^YI@<:66,18DL_mr]B4/0,(%,*,0+2/0-++.1.+25@<:6<85DIC837=BHID2)->\mP/2Stvt]M>IVP\mvaTPD30?DL_hYJWn|tfTH==+'lȾzDngYb^NJD9<3256@GQcxx[=0-,,0110.-,40-+0,+,+09=98>DGFA=D\~L3)*.00,1320-2.-38:;:A>>>CIDD82.!$%#,8UdShn\OUYR?DSZRJ=6-38H\kmYPVa\cxt]I;72"YַnYIJF<:68>90' *4Fgѓ`\f\NRQH9=@?DI8[ʲ|\b^YG@7*",1898588884@=61(%.OznQ[`ZUM>>:=616203==ATnztf`hd\^\M72-(Hƽɽ˱|p_OB>BAAKVZJ;Stfnqf[VP=*'25@<72882-(/) %;Z}DCQaYNCBH=0-+.189=H`hnmnjhgcZJ2-(/) húͫ|wm`K>DPPP\c\M:Jzsb^Yc_K6-3825-%/,-# 5Tގ>4@Y\NNQK=4,*,06-@sz\WadSWaU=**.0%3Ҳ|dSKDAOUR\k^F=6@_w\IJFH=.-,,*&&"$=[ܜC%3LVKMKMK?>781/,NSFPTHLVQ?4.-,,-wÓp~y\MGGKJKDTaJ/,>Ymy~nW>/,-+*'8[mljf\N98DQ\zc3>LVQ?DIMMFJ>4''_K>GMLIJLG85+0' [ȿƿ߹}d}nYNNQD=<9=KRQJFKL?133)JſxbtzcPGHJ@8<6.,)+M~OD<6FI:%0pKLJDADKLJ><6,;߯pV_cYNGCCC>=2(%";υbhhhb[\U9%O٥bPSF=>?=>=CC>6.%&^뽂zw]VJ2!afJF=>?ADAD9?5,UЄJ>=GHNJQR?1-(twKR]VH>=GA<6,'Uh7;MQJOVU>2(%aʓ\ZRD>D6.pڥhhl^[\J/:ЏWB?=>?AGC=>.$Q81GQJOOK9,) >֯pN?66=IL?@B?1G؞Z_}h\\Z@0M֚W=>?AGGC=87/&PO+7KL@B?:,# l޽nD<7/9GC=<=+2vӍJQtvb[TL33fΠڝV9>D@@B;3)0++M~4'BNG@HC7/%=dD@@863)Bs=Imp_SNG(-ᾁٞP7@B?:96.08<1GV+3FIGHD<7(#pdID@8>FD@81&Wh=Ic_SRQC"/ɥ؞Q7@D<<8748><6=ۉ8&8DFIGF=,) InL?@8874522287jQ"/;DKRPD7%+stK9748?=HC73yhAJQJDKF+Ql8>@83387,)3<6Sx(%6=NVQD8*@sJ7/9=>;D=+;`:CLDIK9,zt2/338484,)8B7J</AJRQC:,%%|zI2/=I>852%CO2>DBFI2I~4(-4843384=I6Be %=DPSNA4,!FzJ22GQD6/&%QA.?G?G?(jؓ>%&1322228@B48n=,=INGD8+!$v}J8>@L?74+)d7/DK>=5$1K,)3/348??1-Vy-";GHDA<,#"AwD<8*G\%&)+/349687OT%8>@>F>2%&!jr=2BF=274++|r# =>;3"cӇ,$)+/1322>>K~7%6BBFA4*%"8s=.?LD<7(%0b,22,+h&"',132248?JfV%=DG;,) $!S~B7@HF=4,!:U"+',*5J#(-/349;9>DPۍ.$3F=+&%"!%&(υK9GC=83)'UK#(%&)$Cv$!)+/1;D=?GGCcn5,96+'&%%(e~D@MB668&+sD,.,%&[D&8:;>=C>PZKJR#+)(-(%$)&%Ar=3F=248 -؍8$+21%nx( 4@HF=2BTnlR`s%"'(%%&!+3(%hk4,5222,9h%!%,1,+}D(?PG96=Twqc8"+%&!+/3!\k8*8>@0+$QC"+%-*1^!+MZB2:J^}ayT%(%%$)&)Vc:6BB?1-.j~+#(+3(2~+2[U7/9[hsxbHCj$&%'%"!%[ݱR8>FI:6.9c$!)+21,+ݤD6SN2/KahbWB*%tr$++)# %"j؝A4DK>?=+>^!!+-*2(8rJQMB6P\_cT8%zt%(+2+!# (}ԇ8>NG8<1%C]+'1322%BޖUP`^J@HR`Q1%ٍ%"+)+)## ?x5AQJ:60!QS',6.02%Y\DmpJ,6OVD$(J)+(%%$\t,#aD(22,/&%nnAah>3UP8:փ+(-/(%%"qÄPZ`^OD,#jJ%/,)'%2܇>FT8(?g\;UB#,1,%&%|SZeaP>,'tp))+/ DƾI>PSVn}h=rY)+/ $!)ZBPZVJ=+&ܠ=#+/YgN^}[6(r#(/,%&!4ݓMBJQKRC*%_!%,-"$vFQpK(75+)##%>ډI>PLDI>%1ޅ2!#++ԓ74[\WM8IR%%&%BׅFBILA4*#LA%(߫887450!\r'@>2660+$+|V%,[,13384%0nߐ'%*A܄=+,)'%!Bb(*փ=+6682!26"%SցH+%+h%%%*#+yt&#n[,740'K?((*h%%#5z%!e]3;5) U5&%1U %( %Jv%$^^3;4*#_.%?A+-%cs*#&WZ4863.(l,%N۔:/1%zv+&%NһܜU:;:;=3s*!Ov+/6%1z+--DאK48;A@7x'!R[$59)9x/18;ǿӅ=&-7<82z$"$WJ!28*@x8@B"0w1(01+-w!Z>%48,Ov=G@)l/%+--+4h!_8(563gq?F8%}b1+1<80=_!jЊ2%42@ݷh?F4#rɿY,-2JI2@\#&yn, ,4*Fݫ\;A6%\ýL#%1PJ)CS%#,V%%8@-GڛH+48,#Lۡ>(8T?HJ&#1>%1AI2Mܕ=/0'#NՔ:*@U:V>#+&3s*-7HJ2WD!+4&]ψ5-GT3"h1&1ܩP,;JVL-]S-(_v4"2KM+!nh%1Ђ@7J^dM+jU-(+µ]3+=PK( nJ1j>>QheB0}ߧF!.$eµF*5M_Y,$w<!*zܤN6=GWI28֍<%(( Dø܉56G\j^3%7!!nw;5<8D1CyA8, /|w=P^dl[+!3%"e۟L-8=3:%\lPJ2D}[nqfhS%#ܘ.%#_\,-862.,֢cRVA+PᾅgsnfhP%(ւ)$%#WܺwD1233.Dą]Y^VBLvO[abcI%1c %"rر~^\U:+--%1wޫ\WgmeRBLQ)CW^V78> %>Чtapyb9)+BZ֙QNmyhUC9K> 7V]F#Cv!]ٹzfhwlJ2Cm֤lgzzh]N68s2)9JK4W=#8þƾݷwWgsngI2MرzhhU=PC#296+!%}ҦܯG#"5ø˾þɿºnfĴʷɫ֢^K`g[Q9=}ЧtgPJýe%,4+!C׮ztM"#%Q²īɿzµпɽɽխ}c\L<80%8b]3+þ;lP=3*#/̯|zvs}yyyzx}|zhswqfhg[PRV\VV]`_bjnfezNGTJ2&]kerK(/+! %Hyyj^lsqx}gapyshgz}rvznq~~~zzzflt}͵ʽƼøýѹ~P+/?/¼DZtg[G2%+Yýĥ½½m`_]`WOJKMNPKO[vyY,,HD+!=ezjcr]NhU'!Sq`grqfW@0288=LMBGPY^L<>LVVSUULMNG;Acrh]YH:PztrŴĴؽd1#7<'#ɽýƿɿýĴ}twshUMmȾýüľ~~zh]Yfhg^VNU]N#%1#%>\jktp\UUmyw>%"$"DlgZeme\PJA+"$"163<84*,;@B>FRVKMU]^VNN[af_bbVQZ_YQhsnge_Qhdf÷l48TW;[ȾƿȾľ½ϧtùľ=-?F'=sVAVjvbH5)%#$%,;F88HJ=-)9FIA88@CCC?FPYSJDL\hedmy|zvhnt|Ġh9KSA+,fĻɽƿù³ǿӫǿ¾tgmϾåV018*%#Rξγ}cn^K8*%%((28@7'! -T~}~~ýƼnH:@ITkwQ@ CUL4$HxĴþʾʾþ˷ɾsagfNPntPKtwQ8,-(Nʺ}U:+23316=GWheF*-?\¾ȯ·zdV\ehlveD$#.;JH2:fúΡhdſ¾¾ɽônfpkl_4#[vwоS=-2Ⱥ[;)+=KS_ch}´øm=;`JH39`óf"\ÿøþñ¾ŸĺοҸοt]Sdʼ]7nʲn^bq~hN@FVt˾ÿ~vº]7?sV2%:fJ-0Kξɷþ¹־ĽʲаϾʼ|}v\J@=>RyVFQl~p˾̿ñz~ùQ0"%8go%.akCQ_côƾŸÿúîſñͻ˽ưֿtvſ̿Žùưξøppÿòh^bZJHPQ04VU2Afƾ¾¾·¾ƻëz|xĺôϾǸ̻x`m¼ƾŪϰͳ̻ŸxfVM6>bĺȾJ\¹ÿ¾ȼʿɽ¾Ϸ³¾˜ʲ`mzʿДbդzúȾƽR>,Iĺ>'%fʹĽſȺñŸʿþǸʿײȂ~tէҮñy~Z&)+%>pxn^Ya^VMSRON3?WؽơúӧxffVZc_[ankaehbUUU\^]YSRPQZ[VOC;9BUlvfF5%(79-,DVTQP>9B?97>A@=2A|ʾ˾ɭz~p|x~z~txwh^^v[;DJR\UU\ehbg`QCKSG0*3($.579A8-02.5A@666.,,,/2.,%%,9BCBD>881.5/2=>>JRLJG=-!&#"3.,6>AJPB66.1C[V>A=8;9:::>87>WeY>*$'-079;JH;96&#,DQPRO]cYSLPONI4=Tdaedabhmnsx}wmc_ae]YWZc`QT^jmc[at~xfTQU]c]cmtvkgens}xphln^Y_zyna^g}zzzs\]nzr[V``fqlmtpkhdchhdhmx}tc_ahlhlndaVTewmPFA@bq_JBMZU>,,87J^]VT\^_[ONZUYa_[aeklgjmhdhpf\^_\JFQ_c^VMNU]`m_JFA5757A@FGBMV\VMK=-'4=GMNJHU]WG=DJPWP=879;=>>DP\bZJ=-!+7>FA>A7*&++7AJHCP`]SHDJPKC \ No newline at end of file diff --git a/examples/peerconnection/challenge_client/defaults.cc b/examples/peerconnection/challenge_client/defaults.cc new file mode 100644 index 0000000000..e2b53581b7 --- /dev/null +++ b/examples/peerconnection/challenge_client/defaults.cc @@ -0,0 +1,62 @@ +/* + * 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 "examples/peerconnection/challenge_client/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; + +// The value of auto close time for disabling auto close +const int kAutoCloseDisableValue = 0; + +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/examples/peerconnection/challenge_client/defaults.h b/examples/peerconnection/challenge_client/defaults.h new file mode 100644 index 0000000000..3231d85dfd --- /dev/null +++ b/examples/peerconnection/challenge_client/defaults.h @@ -0,0 +1,32 @@ +/* + * 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; + +// The value of auto close time for disabling auto close +extern const int kAutoCloseDisableValue; + +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/examples/peerconnection/challenge_client/flag_defs.h b/examples/peerconnection/challenge_client/flag_defs.h new file mode 100644 index 0000000000..986daf64ce --- /dev/null +++ b/examples/peerconnection/challenge_client/flag_defs.h @@ -0,0 +1,52 @@ +/* + * 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 + +#include "absl/flags/flag.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. + +ABSL_FLAG(bool, + autoconnect, + false, + "Connect to the server without user " + "intervention."); +ABSL_FLAG(std::string, server, "localhost", "The server to connect to."); +ABSL_FLAG(int, + port, + kDefaultServerPort, + "The port on which the server is listening."); +ABSL_FLAG( + 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."); + +ABSL_FLAG( + std::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/examples/peerconnection/challenge_client/linux/main.cc b/examples/peerconnection/challenge_client/linux/main.cc new file mode 100644 index 0000000000..ccca7b1c7c --- /dev/null +++ b/examples/peerconnection/challenge_client/linux/main.cc @@ -0,0 +1,122 @@ +/* + * 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 +#include +#include + +#include "absl/flags/parse.h" +#include "api/scoped_refptr.h" +#include "examples/peerconnection/client/conductor.h" +#include "examples/peerconnection/client/flag_defs.h" +#include "examples/peerconnection/client/linux/main_wnd.h" +#include "examples/peerconnection/client/peer_connection_client.h" +#include "rtc_base/physical_socket_server.h" +#include "rtc_base/ref_counted_object.h" +#include "rtc_base/ssl_adapter.h" +#include "rtc_base/thread.h" +#include "system_wrappers/include/field_trial.h" +#include "test/field_trial.h" + +class CustomSocketServer : public rtc::PhysicalSocketServer { + public: + explicit CustomSocketServer(GtkMainWnd* wnd) + : wnd_(wnd), conductor_(NULL), client_(NULL) {} + virtual ~CustomSocketServer() {} + + void SetMessageQueue(rtc::Thread* queue) override { message_queue_ = queue; } + + void set_client(PeerConnectionClient* client) { client_ = client; } + void set_conductor(Conductor* conductor) { conductor_ = conductor; } + + // Override so that we can also pump the GTK message loop. + bool Wait(int cms, bool process_io) override { + // Pump GTK events. + // TODO(henrike): We really should move either the socket server or UI to a + // different thread. Alternatively we could look at merging the two loops + // by implementing a dispatcher for the socket server and/or use + // g_main_context_set_poll_func. + while (gtk_events_pending()) + gtk_main_iteration(); + + if (!wnd_->IsWindow() && !conductor_->connection_active() && + client_ != NULL && !client_->is_connected()) { + message_queue_->Quit(); + } + return rtc::PhysicalSocketServer::Wait(0 /*cms == -1 ? 1 : cms*/, + process_io); + } + + protected: + rtc::Thread* message_queue_; + GtkMainWnd* wnd_; + Conductor* conductor_; + PeerConnectionClient* client_; +}; + +int main(int argc, char* argv[]) { + gtk_init(&argc, &argv); +// g_type_init API is deprecated (and does nothing) since glib 2.35.0, see: +// https://mail.gnome.org/archives/commits-list/2012-November/msg07809.html +#if !GLIB_CHECK_VERSION(2, 35, 0) + g_type_init(); +#endif +// g_thread_init API is deprecated since glib 2.31.0, see release note: +// http://mail.gnome.org/archives/gnome-announce-list/2011-October/msg00041.html +#if !GLIB_CHECK_VERSION(2, 31, 0) + g_thread_init(NULL); +#endif + + absl::ParseCommandLine(argc, argv); + + // InitFieldTrialsFromString stores the char*, so the char array must outlive + // the application. + const std::string forced_field_trials = + absl::GetFlag(FLAGS_force_fieldtrials); + webrtc::field_trial::InitFieldTrialsFromString(forced_field_trials.c_str()); + + // Abort if the user specifies a port that is outside the allowed + // range [1, 65535]. + if ((absl::GetFlag(FLAGS_port) < 1) || (absl::GetFlag(FLAGS_port) > 65535)) { + printf("Error: %i is not a valid port.\n", absl::GetFlag(FLAGS_port)); + return -1; + } + + const std::string server = absl::GetFlag(FLAGS_server); + GtkMainWnd wnd(server.c_str(), absl::GetFlag(FLAGS_port), + absl::GetFlag(FLAGS_autoconnect), + absl::GetFlag(FLAGS_autocall)); + wnd.Create(); + + CustomSocketServer socket_server(&wnd); + rtc::AutoSocketServerThread thread(&socket_server); + + rtc::InitializeSSL(); + // Must be constructed after we set the socketserver. + PeerConnectionClient client; + rtc::scoped_refptr conductor( + new rtc::RefCountedObject(&client, &wnd)); + socket_server.set_client(&client); + socket_server.set_conductor(conductor); + + thread.Run(); + + // gtk_main(); + wnd.Destroy(); + + // TODO(henrike): Run the Gtk main loop to tear down the connection. + /* + while (gtk_events_pending()) { + gtk_main_iteration(); + } + */ + rtc::CleanupSSL(); + return 0; +} diff --git a/examples/peerconnection/challenge_client/linux/main_wnd.cc b/examples/peerconnection/challenge_client/linux/main_wnd.cc new file mode 100644 index 0000000000..6b940ea108 --- /dev/null +++ b/examples/peerconnection/challenge_client/linux/main_wnd.cc @@ -0,0 +1,572 @@ +/* + * 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 "examples/peerconnection/client/linux/main_wnd.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "api/video/i420_buffer.h" +#include "api/video/video_frame_buffer.h" +#include "api/video/video_rotation.h" +#include "api/video/video_source_interface.h" +#include "rtc_base/checks.h" +#include "rtc_base/logging.h" +#include "third_party/libyuv/include/libyuv/convert_from.h" + +namespace { + +// +// Simple static functions that simply forward the callback to the +// GtkMainWnd instance. +// + +gboolean OnDestroyedCallback(GtkWidget* widget, + GdkEvent* event, + gpointer data) { + reinterpret_cast(data)->OnDestroyed(widget, event); + return FALSE; +} + +void OnClickedCallback(GtkWidget* widget, gpointer data) { + reinterpret_cast(data)->OnClicked(widget); +} + +gboolean SimulateButtonClick(gpointer button) { + g_signal_emit_by_name(button, "clicked"); + return false; +} + +gboolean OnKeyPressCallback(GtkWidget* widget, + GdkEventKey* key, + gpointer data) { + reinterpret_cast(data)->OnKeyPress(widget, key); + return false; +} + +void OnRowActivatedCallback(GtkTreeView* tree_view, + GtkTreePath* path, + GtkTreeViewColumn* column, + gpointer data) { + reinterpret_cast(data)->OnRowActivated(tree_view, path, column); +} + +gboolean SimulateLastRowActivated(gpointer data) { + GtkTreeView* tree_view = reinterpret_cast(data); + GtkTreeModel* model = gtk_tree_view_get_model(tree_view); + + // "if iter is NULL, then the number of toplevel nodes is returned." + int rows = gtk_tree_model_iter_n_children(model, NULL); + GtkTreePath* lastpath = gtk_tree_path_new_from_indices(rows - 1, -1); + + // Select the last item in the list + GtkTreeSelection* selection = gtk_tree_view_get_selection(tree_view); + gtk_tree_selection_select_path(selection, lastpath); + + // Our TreeView only has one column, so it is column 0. + GtkTreeViewColumn* column = gtk_tree_view_get_column(tree_view, 0); + + gtk_tree_view_row_activated(tree_view, lastpath, column); + + gtk_tree_path_free(lastpath); + return false; +} + +// Creates a tree view, that we use to display the list of peers. +void InitializeList(GtkWidget* list) { + GtkCellRenderer* renderer = gtk_cell_renderer_text_new(); + GtkTreeViewColumn* column = gtk_tree_view_column_new_with_attributes( + "List Items", renderer, "text", 0, NULL); + gtk_tree_view_append_column(GTK_TREE_VIEW(list), column); + GtkListStore* store = gtk_list_store_new(2, G_TYPE_STRING, G_TYPE_INT); + gtk_tree_view_set_model(GTK_TREE_VIEW(list), GTK_TREE_MODEL(store)); + g_object_unref(store); +} + +// Adds an entry to a tree view. +void AddToList(GtkWidget* list, const gchar* str, int value) { + GtkListStore* store = + GTK_LIST_STORE(gtk_tree_view_get_model(GTK_TREE_VIEW(list))); + + GtkTreeIter iter; + gtk_list_store_append(store, &iter); + gtk_list_store_set(store, &iter, 0, str, 1, value, -1); +} + +struct UIThreadCallbackData { + explicit UIThreadCallbackData(MainWndCallback* cb, int id, void* d) + : callback(cb), msg_id(id), data(d) {} + MainWndCallback* callback; + int msg_id; + void* data; +}; + +gboolean HandleUIThreadCallback(gpointer data) { + UIThreadCallbackData* cb_data = reinterpret_cast(data); + cb_data->callback->UIThreadCallback(cb_data->msg_id, cb_data->data); + delete cb_data; + return false; +} + +gboolean Redraw(gpointer data) { + GtkMainWnd* wnd = reinterpret_cast(data); + wnd->OnRedraw(); + return false; +} + +gboolean Draw(GtkWidget* widget, cairo_t* cr, gpointer data) { + GtkMainWnd* wnd = reinterpret_cast(data); + wnd->Draw(widget, cr); + return false; +} + +} // namespace + +// +// GtkMainWnd implementation. +// + +GtkMainWnd::GtkMainWnd(const char* server, + int port, + bool autoconnect, + bool autocall) + : window_(NULL), + draw_area_(NULL), + vbox_(NULL), + server_edit_(NULL), + port_edit_(NULL), + peer_list_(NULL), + callback_(NULL), + server_(server), + autoconnect_(autoconnect), + autocall_(autocall) { + char buffer[10]; + snprintf(buffer, sizeof(buffer), "%i", port); + port_ = buffer; +} + +GtkMainWnd::~GtkMainWnd() { + RTC_DCHECK(!IsWindow()); +} + +void GtkMainWnd::RegisterObserver(MainWndCallback* callback) { + callback_ = callback; +} + +bool GtkMainWnd::IsWindow() { + return window_ != NULL && GTK_IS_WINDOW(window_); +} + +void GtkMainWnd::MessageBox(const char* caption, + const char* text, + bool is_error) { + GtkWidget* dialog = gtk_message_dialog_new( + GTK_WINDOW(window_), GTK_DIALOG_DESTROY_WITH_PARENT, + is_error ? GTK_MESSAGE_ERROR : GTK_MESSAGE_INFO, GTK_BUTTONS_CLOSE, "%s", + text); + gtk_window_set_title(GTK_WINDOW(dialog), caption); + gtk_dialog_run(GTK_DIALOG(dialog)); + gtk_widget_destroy(dialog); +} + +MainWindow::UI GtkMainWnd::current_ui() { + if (vbox_) + return CONNECT_TO_SERVER; + + if (peer_list_) + return LIST_PEERS; + + return STREAMING; +} + +void GtkMainWnd::StartLocalRenderer(webrtc::VideoTrackInterface* local_video) { + local_renderer_.reset(new VideoRenderer(this, local_video)); +} + +void GtkMainWnd::StopLocalRenderer() { + local_renderer_.reset(); +} + +void GtkMainWnd::StartRemoteRenderer( + webrtc::VideoTrackInterface* remote_video) { + remote_renderer_.reset(new VideoRenderer(this, remote_video)); +} + +void GtkMainWnd::StopRemoteRenderer() { + remote_renderer_.reset(); +} + +void GtkMainWnd::QueueUIThreadCallback(int msg_id, void* data) { + g_idle_add(HandleUIThreadCallback, + new UIThreadCallbackData(callback_, msg_id, data)); +} + +bool GtkMainWnd::Create() { + RTC_DCHECK(window_ == NULL); + + window_ = gtk_window_new(GTK_WINDOW_TOPLEVEL); + if (window_) { + gtk_window_set_position(GTK_WINDOW(window_), GTK_WIN_POS_CENTER); + gtk_window_set_default_size(GTK_WINDOW(window_), 640, 480); + gtk_window_set_title(GTK_WINDOW(window_), "PeerConnection client"); + g_signal_connect(G_OBJECT(window_), "delete-event", + G_CALLBACK(&OnDestroyedCallback), this); + g_signal_connect(window_, "key-press-event", G_CALLBACK(OnKeyPressCallback), + this); + + SwitchToConnectUI(); + } + + return window_ != NULL; +} + +bool GtkMainWnd::Destroy() { + if (!IsWindow()) + return false; + + gtk_widget_destroy(window_); + window_ = NULL; + + return true; +} + +void GtkMainWnd::SwitchToConnectUI() { + RTC_LOG(INFO) << __FUNCTION__; + + RTC_DCHECK(IsWindow()); + RTC_DCHECK(vbox_ == NULL); + + gtk_container_set_border_width(GTK_CONTAINER(window_), 10); + + if (peer_list_) { + gtk_widget_destroy(peer_list_); + peer_list_ = NULL; + } + +#if GTK_MAJOR_VERSION == 2 + vbox_ = gtk_vbox_new(FALSE, 5); +#else + vbox_ = gtk_box_new(GTK_ORIENTATION_VERTICAL, 5); +#endif + GtkWidget* valign = gtk_alignment_new(0, 1, 0, 0); + gtk_container_add(GTK_CONTAINER(vbox_), valign); + gtk_container_add(GTK_CONTAINER(window_), vbox_); + +#if GTK_MAJOR_VERSION == 2 + GtkWidget* hbox = gtk_hbox_new(FALSE, 5); +#else + GtkWidget* hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 5); +#endif + + GtkWidget* label = gtk_label_new("Server"); + gtk_container_add(GTK_CONTAINER(hbox), label); + + server_edit_ = gtk_entry_new(); + gtk_entry_set_text(GTK_ENTRY(server_edit_), server_.c_str()); + gtk_widget_set_size_request(server_edit_, 400, 30); + gtk_container_add(GTK_CONTAINER(hbox), server_edit_); + + port_edit_ = gtk_entry_new(); + gtk_entry_set_text(GTK_ENTRY(port_edit_), port_.c_str()); + gtk_widget_set_size_request(port_edit_, 70, 30); + gtk_container_add(GTK_CONTAINER(hbox), port_edit_); + + GtkWidget* button = gtk_button_new_with_label("Connect"); + gtk_widget_set_size_request(button, 70, 30); + g_signal_connect(button, "clicked", G_CALLBACK(OnClickedCallback), this); + gtk_container_add(GTK_CONTAINER(hbox), button); + + GtkWidget* halign = gtk_alignment_new(1, 0, 0, 0); + gtk_container_add(GTK_CONTAINER(halign), hbox); + gtk_box_pack_start(GTK_BOX(vbox_), halign, FALSE, FALSE, 0); + + gtk_widget_show_all(window_); + + if (autoconnect_) + g_idle_add(SimulateButtonClick, button); +} + +void GtkMainWnd::SwitchToPeerList(const Peers& peers) { + RTC_LOG(INFO) << __FUNCTION__; + + if (!peer_list_) { + gtk_container_set_border_width(GTK_CONTAINER(window_), 0); + if (vbox_) { + gtk_widget_destroy(vbox_); + vbox_ = NULL; + server_edit_ = NULL; + port_edit_ = NULL; + } else if (draw_area_) { + gtk_widget_destroy(draw_area_); + draw_area_ = NULL; + draw_buffer_.reset(); + } + + peer_list_ = gtk_tree_view_new(); + g_signal_connect(peer_list_, "row-activated", + G_CALLBACK(OnRowActivatedCallback), this); + gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(peer_list_), FALSE); + InitializeList(peer_list_); + gtk_container_add(GTK_CONTAINER(window_), peer_list_); + gtk_widget_show_all(window_); + } else { + GtkListStore* store = + GTK_LIST_STORE(gtk_tree_view_get_model(GTK_TREE_VIEW(peer_list_))); + gtk_list_store_clear(store); + } + + AddToList(peer_list_, "List of currently connected peers:", -1); + for (Peers::const_iterator i = peers.begin(); i != peers.end(); ++i) + AddToList(peer_list_, i->second.c_str(), i->first); + + if (autocall_ && peers.begin() != peers.end()) + g_idle_add(SimulateLastRowActivated, peer_list_); +} + +void GtkMainWnd::SwitchToStreamingUI() { + RTC_LOG(INFO) << __FUNCTION__; + + RTC_DCHECK(draw_area_ == NULL); + + gtk_container_set_border_width(GTK_CONTAINER(window_), 0); + if (peer_list_) { + gtk_widget_destroy(peer_list_); + peer_list_ = NULL; + } + + draw_area_ = gtk_drawing_area_new(); + gtk_container_add(GTK_CONTAINER(window_), draw_area_); + g_signal_connect(G_OBJECT(draw_area_), "draw", G_CALLBACK(&::Draw), this); + + gtk_widget_show_all(window_); +} + +void GtkMainWnd::OnDestroyed(GtkWidget* widget, GdkEvent* event) { + callback_->Close(); + window_ = NULL; + draw_area_ = NULL; + vbox_ = NULL; + server_edit_ = NULL; + port_edit_ = NULL; + peer_list_ = NULL; +} + +void GtkMainWnd::OnClicked(GtkWidget* widget) { + // Make the connect button insensitive, so that it cannot be clicked more than + // once. Now that the connection includes auto-retry, it should not be + // necessary to click it more than once. + gtk_widget_set_sensitive(widget, false); + server_ = gtk_entry_get_text(GTK_ENTRY(server_edit_)); + port_ = gtk_entry_get_text(GTK_ENTRY(port_edit_)); + int port = port_.length() ? atoi(port_.c_str()) : 0; + callback_->StartLogin(server_, port); +} + +void GtkMainWnd::OnKeyPress(GtkWidget* widget, GdkEventKey* key) { + if (key->type == GDK_KEY_PRESS) { + switch (key->keyval) { +#if GTK_MAJOR_VERSION == 2 + case GDK_Escape: +#else + case GDK_KEY_Escape: +#endif + if (draw_area_) { + callback_->DisconnectFromCurrentPeer(); + } else if (peer_list_) { + callback_->DisconnectFromServer(); + } + break; + +#if GTK_MAJOR_VERSION == 2 + case GDK_KP_Enter: + case GDK_Return: +#else + case GDK_KEY_KP_Enter: + case GDK_KEY_Return: +#endif + if (vbox_) { + OnClicked(NULL); + } else if (peer_list_) { + // OnRowActivated will be called automatically when the user + // presses enter. + } + break; + + default: + break; + } + } +} + +void GtkMainWnd::OnRowActivated(GtkTreeView* tree_view, + GtkTreePath* path, + GtkTreeViewColumn* column) { + RTC_DCHECK(peer_list_ != NULL); + GtkTreeIter iter; + GtkTreeModel* model; + GtkTreeSelection* selection = + gtk_tree_view_get_selection(GTK_TREE_VIEW(tree_view)); + if (gtk_tree_selection_get_selected(selection, &model, &iter)) { + char* text; + int id = -1; + gtk_tree_model_get(model, &iter, 0, &text, 1, &id, -1); + if (id != -1) + callback_->ConnectToPeer(id); + g_free(text); + } +} + +void GtkMainWnd::OnRedraw() { + gdk_threads_enter(); + + VideoRenderer* remote_renderer = remote_renderer_.get(); + if (remote_renderer && remote_renderer->image() != NULL && + draw_area_ != NULL) { + width_ = remote_renderer->width(); + height_ = remote_renderer->height(); + + if (!draw_buffer_.get()) { + draw_buffer_size_ = (width_ * height_ * 4) * 4; + draw_buffer_.reset(new uint8_t[draw_buffer_size_]); + gtk_widget_set_size_request(draw_area_, width_ * 2, height_ * 2); + } + + const uint32_t* image = + reinterpret_cast(remote_renderer->image()); + uint32_t* scaled = reinterpret_cast(draw_buffer_.get()); + for (int r = 0; r < height_; ++r) { + for (int c = 0; c < width_; ++c) { + int x = c * 2; + scaled[x] = scaled[x + 1] = image[c]; + } + + uint32_t* prev_line = scaled; + scaled += width_ * 2; + memcpy(scaled, prev_line, (width_ * 2) * 4); + + image += width_; + scaled += width_ * 2; + } + + VideoRenderer* local_renderer = local_renderer_.get(); + if (local_renderer && local_renderer->image()) { + image = reinterpret_cast(local_renderer->image()); + scaled = reinterpret_cast(draw_buffer_.get()); + // Position the local preview on the right side. + scaled += (width_ * 2) - (local_renderer->width() / 2); + // right margin... + scaled -= 10; + // ... towards the bottom. + scaled += (height_ * width_ * 4) - ((local_renderer->height() / 2) * + (local_renderer->width() / 2) * 4); + // bottom margin... + scaled -= (width_ * 2) * 5; + for (int r = 0; r < local_renderer->height(); r += 2) { + for (int c = 0; c < local_renderer->width(); c += 2) { + scaled[c / 2] = image[c + r * local_renderer->width()]; + } + scaled += width_ * 2; + } + } + +#if GTK_MAJOR_VERSION == 2 + gdk_draw_rgb_32_image(draw_area_->window, + draw_area_->style->fg_gc[GTK_STATE_NORMAL], 0, 0, + width_ * 2, height_ * 2, GDK_RGB_DITHER_MAX, + draw_buffer_.get(), (width_ * 2) * 4); +#else + gtk_widget_queue_draw(draw_area_); +#endif + } + + gdk_threads_leave(); +} + +void GtkMainWnd::Draw(GtkWidget* widget, cairo_t* cr) { +#if GTK_MAJOR_VERSION != 2 + cairo_format_t format = CAIRO_FORMAT_ARGB32; + cairo_surface_t* surface = cairo_image_surface_create_for_data( + draw_buffer_.get(), format, width_ * 2, height_ * 2, + cairo_format_stride_for_width(format, width_ * 2)); + cairo_set_source_surface(cr, surface, 0, 0); + cairo_rectangle(cr, 0, 0, width_ * 2, height_ * 2); + cairo_fill(cr); + cairo_surface_destroy(surface); +#else + RTC_NOTREACHED(); +#endif +} + +GtkMainWnd::VideoRenderer::VideoRenderer( + GtkMainWnd* main_wnd, + webrtc::VideoTrackInterface* track_to_render) + : width_(0), + height_(0), + main_wnd_(main_wnd), + rendered_track_(track_to_render) { + rendered_track_->AddOrUpdateSink(this, rtc::VideoSinkWants()); +} + +GtkMainWnd::VideoRenderer::~VideoRenderer() { + rendered_track_->RemoveSink(this); +} + +void GtkMainWnd::VideoRenderer::SetSize(int width, int height) { + gdk_threads_enter(); + + if (width_ == width && height_ == height) { + return; + } + + width_ = width; + height_ = height; + image_.reset(new uint8_t[width * height * 4]); + gdk_threads_leave(); +} + +void GtkMainWnd::VideoRenderer::OnFrame(const webrtc::VideoFrame& video_frame) { + gdk_threads_enter(); + + 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()); + + // TODO(bugs.webrtc.org/6857): This conversion is correct for little-endian + // only. Cairo ARGB32 treats pixels as 32-bit values in *native* byte order, + // with B in the least significant byte of the 32-bit value. Which on + // little-endian means that memory layout is BGRA, with the B byte stored at + // lowest address. Libyuv's ARGB format (surprisingly?) uses the same + // little-endian format, with B in the first byte in memory, regardless of + // native endianness. + libyuv::I420ToARGB(buffer->DataY(), buffer->StrideY(), buffer->DataU(), + buffer->StrideU(), buffer->DataV(), buffer->StrideV(), + image_.get(), width_ * 4, buffer->width(), + buffer->height()); + + gdk_threads_leave(); + + g_idle_add(Redraw, main_wnd_); +} diff --git a/examples/peerconnection/challenge_client/linux/main_wnd.h b/examples/peerconnection/challenge_client/linux/main_wnd.h new file mode 100644 index 0000000000..3b31e1be3b --- /dev/null +++ b/examples/peerconnection/challenge_client/linux/main_wnd.h @@ -0,0 +1,128 @@ +/* + * 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_LINUX_MAIN_WND_H_ +#define EXAMPLES_PEERCONNECTION_CLIENT_LINUX_MAIN_WND_H_ + +#include + +#include +#include + +#include "api/media_stream_interface.h" +#include "api/scoped_refptr.h" +#include "api/video/video_frame.h" +#include "api/video/video_sink_interface.h" +#include "examples/peerconnection/client/main_wnd.h" +#include "examples/peerconnection/client/peer_connection_client.h" + +// Forward declarations. +typedef struct _GtkWidget GtkWidget; +typedef union _GdkEvent GdkEvent; +typedef struct _GdkEventKey GdkEventKey; +typedef struct _GtkTreeView GtkTreeView; +typedef struct _GtkTreePath GtkTreePath; +typedef struct _GtkTreeViewColumn GtkTreeViewColumn; +typedef struct _cairo cairo_t; + +// Implements the main UI of the peer connection client. +// This is functionally equivalent to the MainWnd class in the Windows +// implementation. +class GtkMainWnd : public MainWindow { + public: + GtkMainWnd(const char* server, int port, bool autoconnect, bool autocall); + ~GtkMainWnd(); + + 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 MainWindow::UI current_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); + + // Creates and shows the main window with the |Connect UI| enabled. + bool Create(); + + // Destroys the window. When the window is destroyed, it ends the + // main message loop. + bool Destroy(); + + // Callback for when the main window is destroyed. + void OnDestroyed(GtkWidget* widget, GdkEvent* event); + + // Callback for when the user clicks the "Connect" button. + void OnClicked(GtkWidget* widget); + + // Callback for keystrokes. Used to capture Esc and Return. + void OnKeyPress(GtkWidget* widget, GdkEventKey* key); + + // Callback when the user double clicks a peer in order to initiate a + // connection. + void OnRowActivated(GtkTreeView* tree_view, + GtkTreePath* path, + GtkTreeViewColumn* column); + + void OnRedraw(); + + void Draw(GtkWidget* widget, cairo_t* cr); + + protected: + class VideoRenderer : public rtc::VideoSinkInterface { + public: + VideoRenderer(GtkMainWnd* main_wnd, + webrtc::VideoTrackInterface* track_to_render); + virtual ~VideoRenderer(); + + // VideoSinkInterface implementation + void OnFrame(const webrtc::VideoFrame& frame) override; + + const uint8_t* image() const { return image_.get(); } + + int width() const { return width_; } + + int height() const { return height_; } + + protected: + void SetSize(int width, int height); + std::unique_ptr image_; + int width_; + int height_; + GtkMainWnd* main_wnd_; + rtc::scoped_refptr rendered_track_; + }; + + protected: + GtkWidget* window_; // Our main window. + GtkWidget* draw_area_; // The drawing surface for rendering video streams. + GtkWidget* vbox_; // Container for the Connect UI. + GtkWidget* server_edit_; + GtkWidget* port_edit_; + GtkWidget* peer_list_; // The list of peers. + MainWndCallback* callback_; + std::string server_; + std::string port_; + bool autoconnect_; + bool autocall_; + std::unique_ptr local_renderer_; + std::unique_ptr remote_renderer_; + int width_; + int height_; + std::unique_ptr draw_buffer_; + int draw_buffer_size_; +}; + +#endif // EXAMPLES_PEERCONNECTION_CLIENT_LINUX_MAIN_WND_H_ diff --git a/examples/peerconnection/challenge_client/logger.cc b/examples/peerconnection/challenge_client/logger.cc new file mode 100644 index 0000000000..cd8bd119d0 --- /dev/null +++ b/examples/peerconnection/challenge_client/logger.cc @@ -0,0 +1,33 @@ +#include "logger.h" +#include + +FileLogSink::FileLogSink(const std::string& log_filepath) + : log_filepath_(log_filepath) { + log_file_ = fopen(log_filepath_.c_str(), "a"); + if (log_file_ != NULL) { + rtc::LogMessage::AddLogToStream(this, rtc::LoggingSeverity::INFO); + } +} + +FileLogSink::~FileLogSink() { + if (log_file_ != NULL) { + fclose(log_file_); + rtc::LogMessage::RemoveLogToStream(this); + } +} + +void FileLogSink::OnLogMessage(const std::string& msg, + rtc::LoggingSeverity severity, + const char* tag) { + OnLogMessage(tag + (": " + msg), severity); +} + +void FileLogSink::OnLogMessage(const std::string& msg, + rtc::LoggingSeverity /* severity */) { + OnLogMessage(msg); +} + +void FileLogSink::OnLogMessage(const std::string& message) { + fwrite(message.c_str(), message.length(), 1, log_file_); + fflush(log_file_); +} \ No newline at end of file diff --git a/examples/peerconnection/challenge_client/logger.h b/examples/peerconnection/challenge_client/logger.h new file mode 100644 index 0000000000..7fc27ab5ea --- /dev/null +++ b/examples/peerconnection/challenge_client/logger.h @@ -0,0 +1,17 @@ +#include "rtc_base/logging.h" + +class FileLogSink : public rtc::LogSink { + public: + FileLogSink(const std::string& log_filepath); + ~FileLogSink(); + void OnLogMessage(const std::string& message); + void OnLogMessage(const std::string& msg, + rtc::LoggingSeverity severity, + const char* tag); + void OnLogMessage(const std::string& msg, + rtc::LoggingSeverity /* severity */); + + private: + std::string log_filepath_; + FILE* log_file_; +}; \ No newline at end of file diff --git a/examples/peerconnection/challenge_client/main.cc b/examples/peerconnection/challenge_client/main.cc new file mode 100644 index 0000000000..3b0ef58433 --- /dev/null +++ b/examples/peerconnection/challenge_client/main.cc @@ -0,0 +1,286 @@ +/* + * 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. + */ + +// clang-format off +// clang formating would change include order. +// #include +// #include // must come after windows.h +// clang-format on + +#include "conductor.h" +#include "defaults.h" +#include "logger.h" +#include "peer_connection_client.h" + +#ifdef WIN32 +#include "rtc_base/win32_socket_init.h" +#include "rtc_base/win32_socket_server.h" +#endif + +#include "api/alphacc_config.h" +#include "rtc_base/ssl_adapter.h" +#include "rtc_base/string_utils.h" // For ToUtf8 +#include "system_wrappers/include/field_trial.h" + +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +namespace { +// A helper class to translate Windows command line arguments into UTF8, +// which then allows us to just pass them to the flags system. +// This encapsulates all the work of getting the command line and translating +// it to an array of 8-bit strings; all you have to do is create one of these, +// and then call argc() and argv(). +class WindowsCommandLineArguments { + public: + WindowsCommandLineArguments(); + + int argc() { return argv_.size(); } + char** argv() { return argv_.data(); } + + private: + // Owned argument strings. + std::vector args_; + // Pointers, to get layout compatible with char** argv. + std::vector argv_; + + private: + RTC_DISALLOW_COPY_AND_ASSIGN(WindowsCommandLineArguments); +}; + +} +// WindowsCommandLineArguments::WindowsCommandLineArguments() { +// // start by getting the command line. +// LPCWSTR command_line = ::GetCommandLineW(); +// // now, convert it to a list of wide char strings. +// int argc; +// LPWSTR* wide_argv = ::CommandLineToArgvW(command_line, &argc); + +// // iterate over the returned wide strings; +// for (int i = 0; i < argc; ++i) { +// args_.push_back(rtc::ToUtf8(wide_argv[i], wcslen(wide_argv[i]))); +// // make sure the argv array points to the string data. +// argv_.push_back(const_cast(args_.back().c_str())); +// } +// LocalFree(wide_argv); +// } + +// } // namespace +// int PASCAL wWinMain(HINSTANCE instance, +// HINSTANCE prev_instance, +// wchar_t* cmd_line, +// int cmd_show) { +// rtc::WinsockInitializer winsock_init; +// rtc::Win32SocketServer w32_ss; +// rtc::Win32Thread w32_thread(&w32_ss); +// rtc::ThreadManager::Instance()->SetCurrentThread(&w32_thread); + +// WindowsCommandLineArguments win_args; +// int argc = win_args.argc(); +// char** argv = win_args.argv(); + +// absl::ParseCommandLine(argc, argv); + +// // InitFieldTrialsFromString stores the char*, so the char array must outlive +// // the application. +// const std::string forced_field_trials = +// absl::GetFlag(FLAGS_force_fieldtrials); +// webrtc::field_trial::InitFieldTrialsFromString(forced_field_trials.c_str()); + +// // Abort if the user specifies a port that is outside the allowed +// // range [1, 65535]. +// if ((absl::GetFlag(FLAGS_port) < 1) || (absl::GetFlag(FLAGS_port) > 65535)) { +// printf("Error: %i is not a valid port.\n", absl::GetFlag(FLAGS_port)); +// return -1; +// } + +// const std::string server = absl::GetFlag(FLAGS_server); +// MainWnd wnd(server.c_str(), absl::GetFlag(FLAGS_port), +// absl::GetFlag(FLAGS_autoconnect), absl::GetFlag(FLAGS_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; +// } + + +class VideoRenderer : public rtc::VideoSinkInterface { + public: + VideoRenderer(webrtc::VideoTrackInterface* track_to_render, + MainWndCallback* callback) + : track_(track_to_render), callback_(callback) { + track_->AddOrUpdateSink(this, rtc::VideoSinkWants()); + } + ~VideoRenderer() { track_->RemoveSink(this); } + void OnFrame(const webrtc::VideoFrame& frame) { + callback_->OnFrameCallback(frame); + } + + private: + rtc::scoped_refptr track_; + MainWndCallback* callback_; +}; + +class MainWindowMock : public MainWindow { + private: + std::unique_ptr remote_renderer_; + MainWndCallback* callback_; + std::shared_ptr socket_thread_; + const webrtc::AlphaCCConfig* config_; + int close_time_; + + public: + MainWindowMock(std::shared_ptr socket_thread) + : callback_(NULL), + socket_thread_(socket_thread), + config_(webrtc::GetAlphaCCConfig()), + close_time_(rtc::Thread::kForever) {} + void RegisterObserver(MainWndCallback* callback) override { + callback_ = callback; + } + + bool IsWindow() override { return true; } + + void MessageBox(const char* caption, + const char* text, + bool is_error) override { + RTC_LOG(LS_INFO) << caption << ": " << text; + } + + UI current_ui() override { return WAIT_FOR_CONNECTION; } + + void SwitchToConnectUI() override {} + void SwitchToStreamingUI() override {} + + void StartLocalRenderer(webrtc::VideoTrackInterface* local_video) override {} + + void StopLocalRenderer() override {} + + void StartRemoteRenderer(webrtc::VideoTrackInterface* remote_video) override { + remote_renderer_.reset(new VideoRenderer(remote_video, callback_)); + } + + void StopRemoteRenderer() override { remote_renderer_.reset(); } + + void QueueUIThreadCallback(int msg_id, void* data) override { + callback_->UIThreadCallback(msg_id, data); + } + + void Run() { + if (config_->conn_autoclose != kAutoCloseDisableValue) { + while (close_time_ == rtc::Thread::kForever) { + RTC_CHECK(socket_thread_->ProcessMessages(0)); + std::this_thread::sleep_for(std::chrono::milliseconds(10)); + } + RTC_CHECK(socket_thread_->ProcessMessages(close_time_)); + } else { + socket_thread_->Run(); + } + StopRemoteRenderer(); + socket_thread_->Stop(); + callback_->Close(); + } + + void StartAutoCloseTimer(int close_time) override { + close_time_ = close_time; + } +}; + + +int main(int argc, char* argv[]) { + if (argc != 2) { + fprintf(stderr, "Usage: %s config_file\n", argv[0]); + exit(EINVAL); + } + + const auto json_file_path = argv[1]; + if (!webrtc::ParseAlphaCCConfig(json_file_path)) { + std::cerr << "bad config file" << std::endl; + exit(EINVAL); + } + + rtc::LogMessage::LogToDebug(rtc::LS_INFO); + + auto config = webrtc::GetAlphaCCConfig(); + std::unique_ptr sink; + + if (config->save_log_to_file) { + sink = std::make_unique(config->log_output_path); + } + + webrtc::field_trial::InitFieldTrialsFromString( + "WebRTC-KeepAbsSendTimeExtension/Enabled/"); // Config for + // hasAbsSendTimestamp in + // RTP Header extension + +#ifdef WIN32 + rtc::WinsockInitializer win_sock_init; + rtc::Win32SocketServer socket_server; +#else + rtc::PhysicalSocketServer socket_server; +#endif + + std::shared_ptr thread( + new rtc::AutoSocketServerThread(&socket_server)); + + MainWindowMock wnd(thread); + + rtc::InitializeSSL(); + PeerConnectionClient client; + rtc::scoped_refptr conductor( + new rtc::RefCountedObject(&client, &wnd)); + + if (config->is_receiver) { + client.StartListen(config->listening_ip, config->listening_port); + } else if (config->is_sender) { + client.StartConnect(config->dest_ip, config->dest_port); + } + + wnd.Run(); + + rtc::CleanupSSL(); + return 0; +} \ No newline at end of file diff --git a/examples/peerconnection/challenge_client/main_wnd.h b/examples/peerconnection/challenge_client/main_wnd.h new file mode 100644 index 0000000000..fbcf2c9b74 --- /dev/null +++ b/examples/peerconnection/challenge_client/main_wnd.h @@ -0,0 +1,211 @@ +/* + * 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 "examples/peerconnection/challenge_client/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() = 0; + // virtual void DisconnectFromCurrentPeer() = 0; + virtual void UIThreadCallback(int msg_id, void* data) = 0; + virtual void Close() = 0; + + virtual void OnFrameCallback(const webrtc::VideoFrame& video_frame) = 0; + + protected: + virtual ~MainWndCallback() {} +}; + +// Pure virtual interface for the main window. +class MainWindow { + public: + virtual ~MainWindow() {} + + enum UI { + CONNECT_TO_SERVER, + LIST_PEERS, + STREAMING, + WAIT_FOR_CONNECTION, + }; + + 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; + + virtual void StartAutoCloseTimer(int interval_ms) = 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/examples/peerconnection/challenge_client/peer_connection_client.cc b/examples/peerconnection/challenge_client/peer_connection_client.cc new file mode 100644 index 0000000000..5fa381f3ea --- /dev/null +++ b/examples/peerconnection/challenge_client/peer_connection_client.cc @@ -0,0 +1,152 @@ +/* + * 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 "examples/peerconnection/challenge_client/peer_connection_client.h" + +#include "examples/peerconnection/challenge_client/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 + +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) { + cs_ = rtc::CriticalSection(); +} + +PeerConnectionClient::~PeerConnectionClient() {} + +void PeerConnectionClient::RegisterObserver( + PeerConnectionClientObserver* callback) { + RTC_DCHECK(!callback_); + callback_ = callback; +} + +void PeerConnectionClient::StartListen(const std::string& ip, int port) { + rtc::SocketAddress listening_addr(ip, port); + listen_socket_.reset(CreateClientSocket(listening_addr.ipaddr().family())); + + int err = listen_socket_->Bind(listening_addr); + if (err == SOCKET_ERROR) { + listen_socket_->Close(); + RTC_LOG(LS_ERROR) << "Failed to bind listen socket to port " << port; + RTC_NOTREACHED(); + } + listen_socket_->Listen(1); + listen_socket_->SignalReadEvent.connect( + this, &PeerConnectionClient::OnSenderConnect); +} + +void PeerConnectionClient::StartConnect(const std::string& ip, int port) { + rtc::SocketAddress send_to_addr(ip, port); + message_socket_.reset(CreateClientSocket(send_to_addr.ipaddr().family())); + message_socket_->SignalReadEvent.connect(this, + &PeerConnectionClient::OnGetMessage); + int err = message_socket_->Connect(send_to_addr); + if (err == SOCKET_ERROR) { + message_socket_->Close(); + RTC_LOG(LS_ERROR) << "Failed to connect to receiver"; + RTC_NOTREACHED(); + } else { + callback_->ConnectToPeer(); + } +} + +void PeerConnectionClient::SendAClientMessage(const std::string& message) { + // enter the critical section + rtc::CritScope cs(&cs_); + // add terminal symbol in sending message + std::string complete_message = message + messageTerminate; + size_t sent = 0; + do { + sent = message_socket_->Send(complete_message.c_str(), + complete_message.length()); + } while((sent != complete_message.length())); +} + +void PeerConnectionClient::SendClientMessage(const std::string& message) { + SendAClientMessage(message); +} + +void PeerConnectionClient::SignOut() { + if (message_socket_ != nullptr) + SendClientMessage(kByeMessage); +} + +// 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); +// } + + +void PeerConnectionClient::ReadIntoBuffer(rtc::AsyncSocket* socket, + std::string* data) { + char buffer[0xffff]; + do { + int bytes = socket->Recv(buffer, sizeof(buffer), nullptr); + if (bytes <= 0) + break; + data->append(buffer, bytes); + } while (true); +} + + + +void PeerConnectionClient::OnSenderConnect(rtc::AsyncSocket* socket) { + message_socket_.reset(socket->Accept(nullptr)); + message_socket_->SignalReadEvent.connect(this, + &PeerConnectionClient::OnGetMessage); +} + +void PeerConnectionClient::OnGetMessage(rtc::AsyncSocket* socket) { + std::string msg; + ReadIntoBuffer(socket, &msg); + + if (msg.length() == (sizeof(kByeMessage) - 1) && + msg.compare(kByeMessage) == 0) { + callback_->OnPeerDisconnected(); + } else { + callback_->OnGetMessage(msg); + } +} \ No newline at end of file diff --git a/examples/peerconnection/challenge_client/peer_connection_client.h b/examples/peerconnection/challenge_client/peer_connection_client.h new file mode 100644 index 0000000000..bf5782a994 --- /dev/null +++ b/examples/peerconnection/challenge_client/peer_connection_client.h @@ -0,0 +1,61 @@ +/* + * 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; +const char messageTerminate[] = "[EOF]"; + +struct PeerConnectionClientObserver { + virtual void OnGetMessage(const std::string& message) = 0; + virtual void OnPeerDisconnected() = 0; + virtual void ConnectToPeer() = 0; + + protected: + virtual ~PeerConnectionClientObserver() {} +}; + +class PeerConnectionClient : public sigslot::has_slots<> { + public: + PeerConnectionClient(); + ~PeerConnectionClient(); + + void RegisterObserver(PeerConnectionClientObserver* callback); + void StartListen(const std::string& ip, int port); + void StartConnect(const std::string& ip, int port); + void SendClientMessage(const std::string& message); + void SendAClientMessage(const std::string& message); + void SignOut(); + + protected: + void OnSenderConnect(rtc::AsyncSocket* socket); + void OnGetMessage(rtc::AsyncSocket* socket); + + // Returns true if the whole response has been read. + void ReadIntoBuffer(rtc::AsyncSocket* socket, + std::string* data); + + PeerConnectionClientObserver* callback_; + std::unique_ptr listen_socket_; + std::unique_ptr message_socket_; + rtc::CriticalSection cs_; +}; + +#endif // EXAMPLES_PEERCONNECTION_CLIENT_SERVERLESS_PEER_CONNECTION_CLIENT_H_ diff --git a/gcc-build.sh b/gcc-build.sh new file mode 100755 index 0000000000..18fe5f329b --- /dev/null +++ b/gcc-build.sh @@ -0,0 +1,21 @@ +#!/bin/bash + +if [ ! -d "out" ] +then + mkdir out + mkdir out/Default + # Do gclient sync only for initial build, + # as it takes a long time to do for every build + gclient sync + mv -fvn src/* . + rm -rf src + sudo apt install ninja-build +fi + + +# Build AlphaRTC. +gn gen out/Default --args='is_debug=false' + +# Build AlphaRTC e2e app that uses GCC (peerconnection_serverless.gcc). +ninja -C out/Default peerconnection_challenge_client +cp out/Default/peerconnection_challenge_client peerconnection_serverless_gcc \ No newline at end of file diff --git a/modules/audio_device/include/test_audio_device.h b/modules/audio_device/include/test_audio_device.h index ba12a9cac0..492ef4429d 100644 --- a/modules/audio_device/include/test_audio_device.h +++ b/modules/audio_device/include/test_audio_device.h @@ -86,7 +86,7 @@ class TestAudioDeviceModule : public AudioDeviceModule { float speed = 1); // AlphaCC version. - // Pass a rtc::Event |audio_started| to mark audio has started + // Pass a rtc::Event |audio_started| to mark audio has started static rtc::scoped_refptr Create( TaskQueueFactory* task_queue_factory, std::unique_ptr capturer, diff --git a/modules/congestion_controller/BUILD.gn b/modules/congestion_controller/BUILD.gn index a252a0d243..c048f958ab 100644 --- a/modules/congestion_controller/BUILD.gn +++ b/modules/congestion_controller/BUILD.gn @@ -49,7 +49,6 @@ if (rtc_include_tests) { "../../test:test_support", "../../test/scenario", "../pacing", - # Remove it for enabling AlphaCC and disabling GCC # Todo: The test doesn't work now # "goog_cc:estimators", # "goog_cc:goog_cc_unittests", diff --git a/modules/congestion_controller/alpha_cc/alpha_cc_network_control.cc b/modules/congestion_controller/alpha_cc/alpha_cc_network_control.cc index 3754e87495..9f51bd3239 100644 --- a/modules/congestion_controller/alpha_cc/alpha_cc_network_control.cc +++ b/modules/congestion_controller/alpha_cc/alpha_cc_network_control.cc @@ -40,8 +40,8 @@ bool IsNotDisabled(const WebRtcKeyValueConfig* config, absl::string_view key) { } } // namespace -GoogCcNetworkController::GoogCcNetworkController(NetworkControllerConfig config, - GoogCcConfig alpha_cc_config) +AlphaCcNetworkController::AlphaCcNetworkController(NetworkControllerConfig config, + AlphaCcConfig alpha_cc_config) : key_value_config_(config.key_value_config ? config.key_value_config : &trial_based_config_), safe_reset_on_route_change_("Enabled"), @@ -60,55 +60,55 @@ GoogCcNetworkController::GoogCcNetworkController(NetworkControllerConfig config, key_value_config_->Lookup("WebRTC-Bwe-SafeResetOnRouteChange")); } -GoogCcNetworkController::~GoogCcNetworkController() {} +AlphaCcNetworkController::~AlphaCcNetworkController() {} -NetworkControlUpdate GoogCcNetworkController::OnNetworkAvailability( +NetworkControlUpdate AlphaCcNetworkController::OnNetworkAvailability( NetworkAvailability msg) { return NetworkControlUpdate(); } -NetworkControlUpdate GoogCcNetworkController::OnNetworkRouteChange( +NetworkControlUpdate AlphaCcNetworkController::OnNetworkRouteChange( NetworkRouteChange msg) { return NetworkControlUpdate(); } -NetworkControlUpdate GoogCcNetworkController::OnProcessInterval( +NetworkControlUpdate AlphaCcNetworkController::OnProcessInterval( ProcessInterval msg) { return NetworkControlUpdate(); } -NetworkControlUpdate GoogCcNetworkController::OnRemoteBitrateReport( +NetworkControlUpdate AlphaCcNetworkController::OnRemoteBitrateReport( RemoteBitrateReport msg) { return NetworkControlUpdate(); } -NetworkControlUpdate GoogCcNetworkController::OnRoundTripTimeUpdate( +NetworkControlUpdate AlphaCcNetworkController::OnRoundTripTimeUpdate( RoundTripTimeUpdate msg) { return NetworkControlUpdate(); } -NetworkControlUpdate GoogCcNetworkController::OnSentPacket( +NetworkControlUpdate AlphaCcNetworkController::OnSentPacket( SentPacket sent_packet) { return NetworkControlUpdate(); } -NetworkControlUpdate GoogCcNetworkController::OnStreamsConfig( +NetworkControlUpdate AlphaCcNetworkController::OnStreamsConfig( StreamsConfig msg) { return GetDefaultState(msg.at_time); } -NetworkControlUpdate GoogCcNetworkController::OnReceivedPacket( +NetworkControlUpdate AlphaCcNetworkController::OnReceivedPacket( ReceivedPacket received_packet) { return NetworkControlUpdate(); } // Make this alive since this might be used to tune the birate. -NetworkControlUpdate GoogCcNetworkController::OnTargetRateConstraints( +NetworkControlUpdate AlphaCcNetworkController::OnTargetRateConstraints( TargetRateConstraints constraints) { return NetworkControlUpdate(); } -NetworkControlUpdate GoogCcNetworkController::GetDefaultState( +NetworkControlUpdate AlphaCcNetworkController::GetDefaultState( Timestamp at_time) { //*-----Set target_rate-----*// constexpr int32_t default_bitrate_bps = 300000; // default: 300000 bps = 300 kbps @@ -158,7 +158,7 @@ NetworkControlUpdate GoogCcNetworkController::GetDefaultState( return update; } -NetworkControlUpdate GoogCcNetworkController::OnReceiveBwe(BweMessage bwe) { +NetworkControlUpdate AlphaCcNetworkController::OnReceiveBwe(BweMessage bwe) { int32_t default_bitrate_bps = static_cast(bwe.target_rate); // default: 300000 bps = 300 kbps DataRate bandwidth = DataRate::BitsPerSec(default_bitrate_bps); TimeDelta rtt = TimeDelta::Millis(last_estimated_rtt_ms_); @@ -176,7 +176,7 @@ NetworkControlUpdate GoogCcNetworkController::OnReceiveBwe(BweMessage bwe) { update.target_rate->target_rate = bandwidth; //*-----Set pacing & padding_rate-----*// - int32_t default_pacing_rate = static_cast(bwe.pacing_rate); + int32_t default_pacing_rate = static_cast(bwe.pacing_rate); int32_t default_padding_rate = 0; // default: 0bps = 0kbps DataRate pacing_rate = DataRate::BitsPerSec(default_pacing_rate * pacing_factor_); DataRate padding_rate = DataRate::BitsPerSec(default_padding_rate); @@ -193,7 +193,7 @@ NetworkControlUpdate GoogCcNetworkController::OnReceiveBwe(BweMessage bwe) { return update; } -void GoogCcNetworkController::ClampConstraints() { +void AlphaCcNetworkController::ClampConstraints() { // TODO(holmer): We should make sure the default bitrates are set to 10 kbps, // and that we don't try to set the min bitrate to 0 from any applications. // The congestion controller should allow a min bitrate of 0. @@ -211,17 +211,17 @@ void GoogCcNetworkController::ClampConstraints() { } } -NetworkControlUpdate GoogCcNetworkController::OnTransportLossReport( +NetworkControlUpdate AlphaCcNetworkController::OnTransportLossReport( TransportLossReport msg) { return NetworkControlUpdate(); } -NetworkControlUpdate GoogCcNetworkController::OnTransportPacketsFeedback( +NetworkControlUpdate AlphaCcNetworkController::OnTransportPacketsFeedback( TransportPacketsFeedback report) { return NetworkControlUpdate(); } -NetworkControlUpdate GoogCcNetworkController::OnNetworkStateEstimate( +NetworkControlUpdate AlphaCcNetworkController::OnNetworkStateEstimate( NetworkStateEstimate msg) { return NetworkControlUpdate(); } diff --git a/modules/congestion_controller/alpha_cc/alpha_cc_network_control.h b/modules/congestion_controller/alpha_cc/alpha_cc_network_control.h index e9faeb46f6..c188bb8f6a 100644 --- a/modules/congestion_controller/alpha_cc/alpha_cc_network_control.h +++ b/modules/congestion_controller/alpha_cc/alpha_cc_network_control.h @@ -26,17 +26,17 @@ #include "rtc_base/experiments/field_trial_parser.h" namespace webrtc { -struct GoogCcConfig { +struct AlphaCcConfig { std::unique_ptr network_state_estimator = nullptr; std::unique_ptr network_state_predictor = nullptr; bool feedback_only = false; }; -class GoogCcNetworkController : public NetworkControllerInterface { +class AlphaCcNetworkController : public NetworkControllerInterface { public: - GoogCcNetworkController(NetworkControllerConfig config, - GoogCcConfig alpha_cc_config); - ~GoogCcNetworkController() override; + AlphaCcNetworkController(NetworkControllerConfig config, + AlphaCcConfig alpha_cc_config); + ~AlphaCcNetworkController() override; // NetworkControllerInterface NetworkControlUpdate OnNetworkAvailability(NetworkAvailability msg) override; @@ -53,13 +53,12 @@ class GoogCcNetworkController : public NetworkControllerInterface { NetworkControlUpdate OnTransportPacketsFeedback( TransportPacketsFeedback msg) override; NetworkControlUpdate OnNetworkStateEstimate( - NetworkStateEstimate msg) override; + NetworkStateEstimate msg) override; NetworkControlUpdate OnReceiveBwe(BweMessage msg) override; NetworkControlUpdate GetNetworkState(Timestamp at_time) const; NetworkControlUpdate GetDefaultState(Timestamp at_time); private: - friend class GoogCcStatePrinter; std::vector ResetConstraints( TargetRateConstraints new_constraints); void ClampConstraints(); @@ -98,7 +97,7 @@ class GoogCcNetworkController : public NetworkControllerInterface { absl::optional current_data_window_; - RTC_DISALLOW_IMPLICIT_CONSTRUCTORS(GoogCcNetworkController); + RTC_DISALLOW_IMPLICIT_CONSTRUCTORS(AlphaCcNetworkController); }; } // namespace webrtc diff --git a/modules/congestion_controller/alpha_cc/link_capacity_estimator.h b/modules/congestion_controller/alpha_cc/link_capacity_estimator.h index aa23491d9d..b10fb08e8c 100644 --- a/modules/congestion_controller/alpha_cc/link_capacity_estimator.h +++ b/modules/congestion_controller/alpha_cc/link_capacity_estimator.h @@ -26,7 +26,6 @@ class LinkCapacityEstimator { DataRate estimate() const; private: - friend class GoogCcStatePrinter; void Update(DataRate capacity_sample, double alpha); double deviation_estimate_kbps() const; diff --git a/modules/congestion_controller/goog_cc/alr_detector_unittest.cc b/modules/congestion_controller/goog_cc/alr_detector_unittest.cc new file mode 100644 index 0000000000..eac19d0081 --- /dev/null +++ b/modules/congestion_controller/goog_cc/alr_detector_unittest.cc @@ -0,0 +1,206 @@ +/* + * Copyright (c) 2016 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 "modules/congestion_controller/goog_cc/alr_detector.h" + +#include "api/transport/field_trial_based_config.h" +#include "rtc_base/checks.h" +#include "rtc_base/experiments/alr_experiment.h" +#include "test/field_trial.h" +#include "test/gtest.h" + +namespace { + +constexpr int kEstimatedBitrateBps = 300000; + +} // namespace + +namespace webrtc { +namespace { +class SimulateOutgoingTrafficIn { + public: + explicit SimulateOutgoingTrafficIn(AlrDetector* alr_detector, + int64_t* timestamp_ms) + : alr_detector_(alr_detector), timestamp_ms_(timestamp_ms) { + RTC_CHECK(alr_detector_); + } + + SimulateOutgoingTrafficIn& ForTimeMs(int time_ms) { + interval_ms_ = time_ms; + ProduceTraffic(); + return *this; + } + + SimulateOutgoingTrafficIn& AtPercentOfEstimatedBitrate(int usage_percentage) { + usage_percentage_.emplace(usage_percentage); + ProduceTraffic(); + return *this; + } + + private: + void ProduceTraffic() { + if (!interval_ms_ || !usage_percentage_) + return; + const int kTimeStepMs = 10; + for (int t = 0; t < *interval_ms_; t += kTimeStepMs) { + *timestamp_ms_ += kTimeStepMs; + alr_detector_->OnBytesSent(kEstimatedBitrateBps * *usage_percentage_ * + kTimeStepMs / (8 * 100 * 1000), + *timestamp_ms_); + } + int remainder_ms = *interval_ms_ % kTimeStepMs; + if (remainder_ms > 0) { + *timestamp_ms_ += kTimeStepMs; + alr_detector_->OnBytesSent(kEstimatedBitrateBps * *usage_percentage_ * + remainder_ms / (8 * 100 * 1000), + *timestamp_ms_); + } + } + AlrDetector* const alr_detector_; + int64_t* timestamp_ms_; + absl::optional interval_ms_; + absl::optional usage_percentage_; +}; +} // namespace + +TEST(AlrDetectorTest, AlrDetection) { + FieldTrialBasedConfig field_trials; + int64_t timestamp_ms = 1000; + AlrDetector alr_detector(&field_trials); + alr_detector.SetEstimatedBitrate(kEstimatedBitrateBps); + + // Start in non-ALR state. + EXPECT_FALSE(alr_detector.GetApplicationLimitedRegionStartTime()); + + // Stay in non-ALR state when usage is close to 100%. + SimulateOutgoingTrafficIn(&alr_detector, ×tamp_ms) + .ForTimeMs(1000) + .AtPercentOfEstimatedBitrate(90); + EXPECT_FALSE(alr_detector.GetApplicationLimitedRegionStartTime()); + + // Verify that we ALR starts when bitrate drops below 20%. + SimulateOutgoingTrafficIn(&alr_detector, ×tamp_ms) + .ForTimeMs(1500) + .AtPercentOfEstimatedBitrate(20); + EXPECT_TRUE(alr_detector.GetApplicationLimitedRegionStartTime()); + + // Verify that ALR ends when usage is above 65%. + SimulateOutgoingTrafficIn(&alr_detector, ×tamp_ms) + .ForTimeMs(4000) + .AtPercentOfEstimatedBitrate(100); + EXPECT_FALSE(alr_detector.GetApplicationLimitedRegionStartTime()); +} + +TEST(AlrDetectorTest, ShortSpike) { + FieldTrialBasedConfig field_trials; + int64_t timestamp_ms = 1000; + AlrDetector alr_detector(&field_trials); + alr_detector.SetEstimatedBitrate(kEstimatedBitrateBps); + // Start in non-ALR state. + EXPECT_FALSE(alr_detector.GetApplicationLimitedRegionStartTime()); + + // Verify that we ALR starts when bitrate drops below 20%. + SimulateOutgoingTrafficIn(&alr_detector, ×tamp_ms) + .ForTimeMs(1000) + .AtPercentOfEstimatedBitrate(20); + EXPECT_TRUE(alr_detector.GetApplicationLimitedRegionStartTime()); + + // Verify that we stay in ALR region even after a short bitrate spike. + SimulateOutgoingTrafficIn(&alr_detector, ×tamp_ms) + .ForTimeMs(100) + .AtPercentOfEstimatedBitrate(150); + EXPECT_TRUE(alr_detector.GetApplicationLimitedRegionStartTime()); + + // ALR ends when usage is above 65%. + SimulateOutgoingTrafficIn(&alr_detector, ×tamp_ms) + .ForTimeMs(3000) + .AtPercentOfEstimatedBitrate(100); + EXPECT_FALSE(alr_detector.GetApplicationLimitedRegionStartTime()); +} + +TEST(AlrDetectorTest, BandwidthEstimateChanges) { + FieldTrialBasedConfig field_trials; + int64_t timestamp_ms = 1000; + AlrDetector alr_detector(&field_trials); + alr_detector.SetEstimatedBitrate(kEstimatedBitrateBps); + + // Start in non-ALR state. + EXPECT_FALSE(alr_detector.GetApplicationLimitedRegionStartTime()); + + // ALR starts when bitrate drops below 20%. + SimulateOutgoingTrafficIn(&alr_detector, ×tamp_ms) + .ForTimeMs(1000) + .AtPercentOfEstimatedBitrate(20); + EXPECT_TRUE(alr_detector.GetApplicationLimitedRegionStartTime()); + + // When bandwidth estimate drops the detector should stay in ALR mode and quit + // it shortly afterwards as the sender continues sending the same amount of + // traffic. This is necessary to ensure that ProbeController can still react + // to the BWE drop by initiating a new probe. + alr_detector.SetEstimatedBitrate(kEstimatedBitrateBps / 5); + EXPECT_TRUE(alr_detector.GetApplicationLimitedRegionStartTime()); + SimulateOutgoingTrafficIn(&alr_detector, ×tamp_ms) + .ForTimeMs(1000) + .AtPercentOfEstimatedBitrate(50); + EXPECT_FALSE(alr_detector.GetApplicationLimitedRegionStartTime()); +} + +TEST(AlrDetectorTest, ParseControlFieldTrial) { + webrtc::test::ScopedFieldTrials scoped_field_trial( + "WebRTC-ProbingScreenshareBwe/Control/"); + absl::optional parsed_params = + AlrExperimentSettings::CreateFromFieldTrial( + FieldTrialBasedConfig(), "WebRTC-ProbingScreenshareBwe"); + EXPECT_FALSE(static_cast(parsed_params)); +} + +TEST(AlrDetectorTest, ParseActiveFieldTrial) { + webrtc::test::ScopedFieldTrials scoped_field_trial( + "WebRTC-ProbingScreenshareBwe/1.1,2875,85,20,-20,1/"); + absl::optional parsed_params = + AlrExperimentSettings::CreateFromFieldTrial( + FieldTrialBasedConfig(), "WebRTC-ProbingScreenshareBwe"); + ASSERT_TRUE(static_cast(parsed_params)); + EXPECT_EQ(1.1f, parsed_params->pacing_factor); + EXPECT_EQ(2875, parsed_params->max_paced_queue_time); + EXPECT_EQ(85, parsed_params->alr_bandwidth_usage_percent); + EXPECT_EQ(20, parsed_params->alr_start_budget_level_percent); + EXPECT_EQ(-20, parsed_params->alr_stop_budget_level_percent); + EXPECT_EQ(1, parsed_params->group_id); +} + +TEST(AlrDetectorTest, ParseAlrSpecificFieldTrial) { + webrtc::test::ScopedFieldTrials scoped_field_trial( + "WebRTC-AlrDetectorParameters/" + "bw_usage:90%,start:0%,stop:-10%/"); + FieldTrialBasedConfig field_trials; + AlrDetector alr_detector(&field_trials); + int64_t timestamp_ms = 1000; + alr_detector.SetEstimatedBitrate(kEstimatedBitrateBps); + + // Start in non-ALR state. + EXPECT_FALSE(alr_detector.GetApplicationLimitedRegionStartTime()); + + // ALR does not start at 100% utilization. + SimulateOutgoingTrafficIn(&alr_detector, ×tamp_ms) + .ForTimeMs(1000) + .AtPercentOfEstimatedBitrate(100); + EXPECT_FALSE(alr_detector.GetApplicationLimitedRegionStartTime()); + + // ALR does start at 85% utilization. + // Overused 10% above so it should take about 2s to reach a budget level of + // 0%. + SimulateOutgoingTrafficIn(&alr_detector, ×tamp_ms) + .ForTimeMs(2100) + .AtPercentOfEstimatedBitrate(85); + EXPECT_TRUE(alr_detector.GetApplicationLimitedRegionStartTime()); +} + +} // namespace webrtc diff --git a/modules/congestion_controller/goog_cc/goog_cc_network_control.cc b/modules/congestion_controller/goog_cc/goog_cc_network_control.cc index b8be0982d6..a93271f145 100644 --- a/modules/congestion_controller/goog_cc/goog_cc_network_control.cc +++ b/modules/congestion_controller/goog_cc/goog_cc_network_control.cc @@ -126,6 +126,7 @@ GoogCcNetworkController::GoogCcNetworkController(NetworkControllerConfig config, key_value_config_->Lookup("WebRTC-Bwe-SafeResetOnRouteChange")); if (delay_based_bwe_) delay_based_bwe_->SetMinBitrate(congestion_controller::GetMinBitrate()); + RTC_LOG(LS_INFO) << "Using GCC"; } GoogCcNetworkController::~GoogCcNetworkController() {} diff --git a/modules/congestion_controller/goog_cc/goog_cc_network_control.h b/modules/congestion_controller/goog_cc/goog_cc_network_control.h new file mode 100644 index 0000000000..1e4dcf62e1 --- /dev/null +++ b/modules/congestion_controller/goog_cc/goog_cc_network_control.h @@ -0,0 +1,146 @@ +/* + * Copyright (c) 2018 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 MODULES_CONGESTION_CONTROLLER_GOOG_CC_GOOG_CC_NETWORK_CONTROL_H_ +#define MODULES_CONGESTION_CONTROLLER_GOOG_CC_GOOG_CC_NETWORK_CONTROL_H_ + +#include + +#include +#include +#include + +#include "absl/types/optional.h" +#include "api/network_state_predictor.h" +#include "api/rtc_event_log/rtc_event_log.h" +#include "api/transport/field_trial_based_config.h" +#include "api/transport/network_control.h" +#include "api/transport/network_types.h" +#include "api/transport/webrtc_key_value_config.h" +#include "api/units/data_rate.h" +#include "api/units/data_size.h" +#include "api/units/timestamp.h" +#include "modules/congestion_controller/goog_cc/acknowledged_bitrate_estimator_interface.h" +#include "modules/congestion_controller/goog_cc/alr_detector.h" +#include "modules/congestion_controller/goog_cc/congestion_window_pushback_controller.h" +#include "modules/congestion_controller/goog_cc/delay_based_bwe.h" +#include "modules/congestion_controller/goog_cc/probe_controller.h" +#include "modules/congestion_controller/goog_cc/send_side_bandwidth_estimation.h" +#include "rtc_base/constructor_magic.h" +#include "rtc_base/experiments/field_trial_parser.h" +#include "rtc_base/experiments/rate_control_settings.h" + +namespace webrtc { +struct GoogCcConfig { + std::unique_ptr network_state_estimator = nullptr; + std::unique_ptr network_state_predictor = nullptr; + bool feedback_only = false; +}; + +class GoogCcNetworkController : public NetworkControllerInterface { + public: + GoogCcNetworkController(NetworkControllerConfig config, + GoogCcConfig goog_cc_config); + ~GoogCcNetworkController() override; + + // NetworkControllerInterface + NetworkControlUpdate OnNetworkAvailability(NetworkAvailability msg) override; + NetworkControlUpdate OnNetworkRouteChange(NetworkRouteChange msg) override; + NetworkControlUpdate OnProcessInterval(ProcessInterval msg) override; + NetworkControlUpdate OnRemoteBitrateReport(RemoteBitrateReport msg) override; + NetworkControlUpdate OnRoundTripTimeUpdate(RoundTripTimeUpdate msg) override; + NetworkControlUpdate OnSentPacket(SentPacket msg) override; + NetworkControlUpdate OnReceivedPacket(ReceivedPacket msg) override; + NetworkControlUpdate OnStreamsConfig(StreamsConfig msg) override; + NetworkControlUpdate OnTargetRateConstraints( + TargetRateConstraints msg) override; + NetworkControlUpdate OnTransportLossReport(TransportLossReport msg) override; + NetworkControlUpdate OnTransportPacketsFeedback( + TransportPacketsFeedback msg) override; + NetworkControlUpdate OnNetworkStateEstimate( + NetworkStateEstimate msg) override; + + NetworkControlUpdate GetNetworkState(Timestamp at_time) const; + + private: + friend class GoogCcStatePrinter; + std::vector ResetConstraints( + TargetRateConstraints new_constraints); + void ClampConstraints(); + void MaybeTriggerOnNetworkChanged(NetworkControlUpdate* update, + Timestamp at_time); + void UpdateCongestionWindowSize(); + PacerConfig GetPacingRates(Timestamp at_time) const; + const FieldTrialBasedConfig trial_based_config_; + + const WebRtcKeyValueConfig* const key_value_config_; + RtcEventLog* const event_log_; + const bool packet_feedback_only_; + FieldTrialFlag safe_reset_on_route_change_; + FieldTrialFlag safe_reset_acknowledged_rate_; + const bool use_min_allocatable_as_lower_bound_; + const bool ignore_probes_lower_than_network_estimate_; + const bool limit_probes_lower_than_throughput_estimate_; + const RateControlSettings rate_control_settings_; + const bool loss_based_stable_rate_; + + const std::unique_ptr probe_controller_; + const std::unique_ptr + congestion_window_pushback_controller_; + + std::unique_ptr bandwidth_estimation_; + std::unique_ptr alr_detector_; + std::unique_ptr probe_bitrate_estimator_; + std::unique_ptr network_estimator_; + std::unique_ptr network_state_predictor_; + std::unique_ptr delay_based_bwe_; + std::unique_ptr + acknowledged_bitrate_estimator_; + + absl::optional initial_config_; + + DataRate min_target_rate_ = DataRate::Zero(); + DataRate min_data_rate_ = DataRate::Zero(); + DataRate max_data_rate_ = DataRate::PlusInfinity(); + absl::optional starting_rate_; + + bool first_packet_sent_ = false; + + absl::optional estimate_; + + Timestamp next_loss_update_ = Timestamp::MinusInfinity(); + int lost_packets_since_last_loss_update_ = 0; + int expected_packets_since_last_loss_update_ = 0; + + std::deque feedback_max_rtts_; + + DataRate last_loss_based_target_rate_; + DataRate last_pushback_target_rate_; + DataRate last_stable_target_rate_; + + absl::optional last_estimated_fraction_loss_ = 0; + TimeDelta last_estimated_round_trip_time_ = TimeDelta::PlusInfinity(); + Timestamp last_packet_received_time_ = Timestamp::MinusInfinity(); + + double pacing_factor_; + DataRate min_total_allocated_bitrate_; + DataRate max_padding_rate_; + DataRate max_total_allocated_bitrate_; + + bool previously_in_alr_ = false; + + absl::optional current_data_window_; + + RTC_DISALLOW_IMPLICIT_CONSTRUCTORS(GoogCcNetworkController); +}; + +} // namespace webrtc + +#endif // MODULES_CONGESTION_CONTROLLER_GOOG_CC_GOOG_CC_NETWORK_CONTROL_H_ diff --git a/modules/congestion_controller/goog_cc/link_capacity_estimator.cc b/modules/congestion_controller/goog_cc/link_capacity_estimator.cc new file mode 100644 index 0000000000..9fd537a422 --- /dev/null +++ b/modules/congestion_controller/goog_cc/link_capacity_estimator.cc @@ -0,0 +1,77 @@ +/* + * Copyright 2018 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 "modules/congestion_controller/goog_cc/link_capacity_estimator.h" + +#include + +#include "rtc_base/numerics/safe_minmax.h" + +namespace webrtc { +LinkCapacityEstimator::LinkCapacityEstimator() {} + +DataRate LinkCapacityEstimator::UpperBound() const { + if (estimate_kbps_.has_value()) + return DataRate::KilobitsPerSec(estimate_kbps_.value() + + 3 * deviation_estimate_kbps()); + return DataRate::Infinity(); +} + +DataRate LinkCapacityEstimator::LowerBound() const { + if (estimate_kbps_.has_value()) + return DataRate::KilobitsPerSec( + std::max(0.0, estimate_kbps_.value() - 3 * deviation_estimate_kbps())); + return DataRate::Zero(); +} + +void LinkCapacityEstimator::Reset() { + estimate_kbps_.reset(); +} + +void LinkCapacityEstimator::OnOveruseDetected(DataRate acknowledged_rate) { + Update(acknowledged_rate, 0.05); +} + +void LinkCapacityEstimator::OnProbeRate(DataRate probe_rate) { + Update(probe_rate, 0.5); +} + +void LinkCapacityEstimator::Update(DataRate capacity_sample, double alpha) { + double sample_kbps = capacity_sample.kbps(); + if (!estimate_kbps_.has_value()) { + estimate_kbps_ = sample_kbps; + } else { + estimate_kbps_ = (1 - alpha) * estimate_kbps_.value() + alpha * sample_kbps; + } + // Estimate the variance of the link capacity estimate and normalize the + // variance with the link capacity estimate. + const double norm = std::max(estimate_kbps_.value(), 1.0); + double error_kbps = estimate_kbps_.value() - sample_kbps; + deviation_kbps_ = + (1 - alpha) * deviation_kbps_ + alpha * error_kbps * error_kbps / norm; + // 0.4 ~= 14 kbit/s at 500 kbit/s + // 2.5f ~= 35 kbit/s at 500 kbit/s + deviation_kbps_ = rtc::SafeClamp(deviation_kbps_, 0.4f, 2.5f); +} + +bool LinkCapacityEstimator::has_estimate() const { + return estimate_kbps_.has_value(); +} + +DataRate LinkCapacityEstimator::estimate() const { + return DataRate::KilobitsPerSec(*estimate_kbps_); +} + +double LinkCapacityEstimator::deviation_estimate_kbps() const { + // Calculate the max bit rate std dev given the normalized + // variance and the current throughput bitrate. The standard deviation will + // only be used if estimate_kbps_ has a value. + return sqrt(deviation_kbps_ * estimate_kbps_.value()); +} +} // namespace webrtc diff --git a/modules/congestion_controller/goog_cc/link_capacity_estimator.h b/modules/congestion_controller/goog_cc/link_capacity_estimator.h new file mode 100644 index 0000000000..aa23491d9d --- /dev/null +++ b/modules/congestion_controller/goog_cc/link_capacity_estimator.h @@ -0,0 +1,38 @@ +/* + * Copyright 2018 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 MODULES_CONGESTION_CONTROLLER_GOOG_CC_LINK_CAPACITY_ESTIMATOR_H_ +#define MODULES_CONGESTION_CONTROLLER_GOOG_CC_LINK_CAPACITY_ESTIMATOR_H_ + +#include "absl/types/optional.h" +#include "api/units/data_rate.h" + +namespace webrtc { +class LinkCapacityEstimator { + public: + LinkCapacityEstimator(); + DataRate UpperBound() const; + DataRate LowerBound() const; + void Reset(); + void OnOveruseDetected(DataRate acknowledged_rate); + void OnProbeRate(DataRate probe_rate); + bool has_estimate() const; + DataRate estimate() const; + + private: + friend class GoogCcStatePrinter; + void Update(DataRate capacity_sample, double alpha); + + double deviation_estimate_kbps() const; + absl::optional estimate_kbps_; + double deviation_kbps_ = 0.4; +}; +} // namespace webrtc + +#endif // MODULES_CONGESTION_CONTROLLER_GOOG_CC_LINK_CAPACITY_ESTIMATOR_H_ diff --git a/modules/remote_bitrate_estimator/BUILD.gn b/modules/remote_bitrate_estimator/BUILD.gn index 3fbaa72c8e..fdc538f46f 100644 --- a/modules/remote_bitrate_estimator/BUILD.gn +++ b/modules/remote_bitrate_estimator/BUILD.gn @@ -49,8 +49,7 @@ rtc_library("remote_bitrate_estimator") { "../../api/units:timestamp", "../../modules:module_api", "../../modules:module_api_public", - # Revision for enabling AlphaCC and disabling GCC - "../../modules/congestion_controller/alpha_cc:link_capacity_estimator", + "../../modules/congestion_controller/goog_cc:link_capacity_estimator", "../../modules/rtp_rtcp:rtp_rtcp_format", "../../rtc_base:checks", "../../rtc_base:rtc_base_approved", @@ -63,7 +62,6 @@ rtc_library("remote_bitrate_estimator") { "//third_party/abseil-cpp/absl/strings", "//third_party/abseil-cpp/absl/types:optional", "//modules/third_party/statcollect:stat_collect", - "//modules/third_party/cmdinfer:cmdinfer" ] if (is_linux) { diff --git a/modules/remote_bitrate_estimator/aimd_rate_control.h b/modules/remote_bitrate_estimator/aimd_rate_control.h index 7df4f40c91..c9e9470c58 100644 --- a/modules/remote_bitrate_estimator/aimd_rate_control.h +++ b/modules/remote_bitrate_estimator/aimd_rate_control.h @@ -18,8 +18,7 @@ #include "api/transport/webrtc_key_value_config.h" #include "api/units/data_rate.h" #include "api/units/timestamp.h" -// Revision for enabling AlphaCC and disabling GCC -#include "modules/congestion_controller/alpha_cc/link_capacity_estimator.h" +#include "modules/congestion_controller/goog_cc/link_capacity_estimator.h" #include "modules/remote_bitrate_estimator/include/bwe_defines.h" #include "rtc_base/experiments/field_trial_parser.h" diff --git a/modules/remote_bitrate_estimator/remote_estimator_proxy.cc b/modules/remote_bitrate_estimator/remote_estimator_proxy.cc index 6955963467..63f3541962 100644 --- a/modules/remote_bitrate_estimator/remote_estimator_proxy.cc +++ b/modules/remote_bitrate_estimator/remote_estimator_proxy.cc @@ -8,10 +8,6 @@ * be found in the AUTHORS file in the root of the source tree. */ -#ifdef WIN32 -#pragma comment(lib, "../../modules/third_party/onnxinfer/lib/onnxinfer.lib") -#endif // WIN32 - #include "modules/remote_bitrate_estimator/remote_estimator_proxy.h" #include "modules/third_party/cmdinfer/cmdinfer.h" @@ -54,27 +50,16 @@ RemoteEstimatorProxy::RemoteEstimatorProxy( send_periodic_feedback_(true), bwe_sendback_interval_ms_(GetAlphaCCConfig()->bwe_feedback_duration_ms), last_bwe_sendback_ms_(clock->TimeInMilliseconds()), - stats_collect_(StatCollect::SC_TYPE_STRUCT), cycles_(-1), - max_abs_send_time_(0), - onnx_infer_(nullptr) { - - if (!GetAlphaCCConfig()->onnx_model_path.empty()) { - onnx_infer_ = onnxinfer::CreateONNXInferInterface( - GetAlphaCCConfig()->onnx_model_path.c_str()); - if (!onnxinfer::IsReady(onnx_infer_)) { - RTC_LOG(LS_ERROR) << "Failed to create onnx_infer_."; - } - } + max_abs_send_time_(0) { + // previous_abs_send_time_(0), + // abs_send_timestamp_(clock->CurrentTime()) { RTC_LOG(LS_INFO) << "Maximum interval between transport feedback RTCP messages (ms): " << send_config_.max_interval->ms(); } RemoteEstimatorProxy::~RemoteEstimatorProxy() { - if (onnx_infer_) { - onnxinfer::DestroyONNXInferInterface(onnx_infer_); - } } void RemoteEstimatorProxy::IncomingPacket(int64_t arrival_time_ms, @@ -86,69 +71,52 @@ void RemoteEstimatorProxy::IncomingPacket(int64_t arrival_time_ms, } rtc::CritScope cs(&lock_); media_ssrc_ = header.ssrc; - OnPacketArrival(header.extension.transportSequenceNumber, arrival_time_ms, - header.extension.feedback_request); + int64_t seq = 0; - //--- ONNXInfer: Input the per-packet info to ONNXInfer module --- - uint32_t send_time_ms = - GetTtimeFromAbsSendtime(header.extension.absoluteSendTime); - - // lossCound and RTT field for onnxinfer::OnReceived() are set to -1 since - // no available lossCound and RTT in webrtc - if (onnx_infer_) { - onnxinfer::OnReceived(onnx_infer_, header.payloadType, header.sequenceNumber, - send_time_ms, header.ssrc, header.paddingLength, - header.headerLength, arrival_time_ms, payload_size, -1, -1); - } else { - cmdinfer::ReportStates( - send_time_ms, - arrival_time_ms, - payload_size, - header.payloadType, - header.sequenceNumber, - header.ssrc, - header.paddingLength, - header.headerLength); - } + if (header.extension.hasTransportSequenceNumber) { + seq = unwrapper_.Unwrap(header.extension.transportSequenceNumber); - //--- BandWidthControl: Send back bandwidth estimation into to sender --- - bool time_to_send_bew_message = TimeToSendBweMessage(); - float estimation = 0; - if (time_to_send_bew_message) { - BweMessage bwe; - if (onnx_infer_) { - estimation = onnxinfer::GetBweEstimate(onnx_infer_); - } else { - estimation = cmdinfer::GetEstimatedBandwidth(); + if (send_periodic_feedback_) { + if (periodic_window_start_seq_ && + packet_arrival_times_.lower_bound(*periodic_window_start_seq_) == + packet_arrival_times_.end()) { + // Start new feedback packet, cull old packets. + for (auto it = packet_arrival_times_.begin(); + it != packet_arrival_times_.end() && it->first < seq && + arrival_time_ms - it->second >= send_config_.back_window->ms();) { + it = packet_arrival_times_.erase(it); + } + } + if (!periodic_window_start_seq_ || seq < *periodic_window_start_seq_) { + periodic_window_start_seq_ = seq; + } } - bwe.pacing_rate = bwe.padding_rate = bwe.target_rate = estimation; - bwe.timestamp_ms = clock_->TimeInMilliseconds(); - SendbackBweEstimation(bwe); - } - // Save per-packet info locally on receiving - // ---------- Collect packet-related info into a local file ---------- - double pacing_rate = - time_to_send_bew_message ? estimation : SC_PACER_PACING_RATE_EMPTY; - double padding_rate = - time_to_send_bew_message ? estimation : SC_PACER_PADDING_RATE_EMPTY; - - // Save per-packet info locally on receiving - auto res = stats_collect_.StatsCollect( - pacing_rate, padding_rate, header.payloadType, - header.sequenceNumber, send_time_ms, header.ssrc, - header.paddingLength, header.headerLength, - arrival_time_ms, payload_size, 0); - if (res != StatCollect::SCResult::SC_SUCCESS) - { - RTC_LOG(LS_ERROR) << "Collect data failed"; - } - std::string out_data = stats_collect_.DumpData(); - if (out_data.empty()) - { - RTC_LOG(LS_ERROR) << "Save data failed"; + // We are only interested in the first time a packet is received. + if (packet_arrival_times_.find(seq) != packet_arrival_times_.end()) + return; + + packet_arrival_times_[seq] = arrival_time_ms; + + // Limit the range of sequence numbers to send feedback for. + auto first_arrival_time_to_keep = packet_arrival_times_.lower_bound( + packet_arrival_times_.rbegin()->first - kMaxNumberOfPackets); + if (first_arrival_time_to_keep != packet_arrival_times_.begin()) { + packet_arrival_times_.erase(packet_arrival_times_.begin(), + first_arrival_time_to_keep); + if (send_periodic_feedback_) { + // |packet_arrival_times_| cannot be empty since we just added one + // element and the last element is not deleted. + RTC_DCHECK(!packet_arrival_times_.empty()); + periodic_window_start_seq_ = packet_arrival_times_.begin()->first; + } + } + + if (header.extension.feedback_request) { + // Send feedback packet immediately. + SendFeedbackOnRequest(seq, header.extension.feedback_request.value()); + } } - RTC_LOG(LS_INFO) << out_data; } bool RemoteEstimatorProxy::LatestEstimate(std::vector* ssrcs, @@ -258,15 +226,6 @@ void RemoteEstimatorProxy::OnPacketArrival( } } -bool RemoteEstimatorProxy::TimeToSendBweMessage() { - int64_t time_now = clock_->TimeInMilliseconds(); - if (time_now - bwe_sendback_interval_ms_ > last_bwe_sendback_ms_) { - last_bwe_sendback_ms_ = time_now; - return true; - } - return false; -} - void RemoteEstimatorProxy::SendPeriodicFeedbacks() { // |periodic_window_start_seq_| is the first sequence number to include in the // current feedback packet. Some older may still be in the map, in case a @@ -338,17 +297,6 @@ void RemoteEstimatorProxy::SendFeedbackOnRequest( feedback_sender_->SendCombinedRtcpPacket(std::move(packets)); } -void RemoteEstimatorProxy::SendbackBweEstimation(const BweMessage& bwe) { - auto app_packet = std::make_unique(); - app_packet->SetSubType(kAppPacketSubType); - app_packet->SetName(kAppPacketName); - - app_packet->SetData(reinterpret_cast(&bwe), sizeof(bwe)); - std::vector> packets; - packets.push_back(std::move(app_packet)); - feedback_sender_->SendCombinedRtcpPacket(std::move(packets)); -} - int64_t RemoteEstimatorProxy::BuildFeedbackPacket( uint8_t feedback_packet_count, uint32_t media_ssrc, diff --git a/modules/remote_bitrate_estimator/remote_estimator_proxy.h b/modules/remote_bitrate_estimator/remote_estimator_proxy.h index e56f89684b..b553d4ce9e 100644 --- a/modules/remote_bitrate_estimator/remote_estimator_proxy.h +++ b/modules/remote_bitrate_estimator/remote_estimator_proxy.h @@ -83,10 +83,6 @@ class RemoteEstimatorProxy : public RemoteBitrateEstimator { const FeedbackRequest& feedback_request) RTC_EXCLUSIVE_LOCKS_REQUIRED(&lock_); - void SendbackBweEstimation(const BweMessage& bwe_message) - RTC_EXCLUSIVE_LOCKS_REQUIRED(&lock_); - bool TimeToSendBweMessage() RTC_EXCLUSIVE_LOCKS_REQUIRED(&lock_); - int64_t BuildFeedbackPacket( uint8_t feedback_packet_count, uint32_t media_ssrc, @@ -122,11 +118,8 @@ class RemoteEstimatorProxy : public RemoteBitrateEstimator { int64_t bwe_sendback_interval_ms_ RTC_GUARDED_BY(&lock_); int64_t last_bwe_sendback_ms_ RTC_GUARDED_BY(&lock_); - // StatCollect moudule - StatCollect::StatsCollectModule stats_collect_; int cycles_ RTC_GUARDED_BY(&lock_); uint32_t max_abs_send_time_ RTC_GUARDED_BY(&lock_); - void* onnx_infer_; }; } // namespace webrtc diff --git a/modules/rtp_rtcp/include/rtp_rtcp_defines.h b/modules/rtp_rtcp/include/rtp_rtcp_defines.h index d0365ddc22..961deba519 100644 --- a/modules/rtp_rtcp/include/rtp_rtcp_defines.h +++ b/modules/rtp_rtcp/include/rtp_rtcp_defines.h @@ -253,7 +253,6 @@ class TransportFeedbackObserver { virtual void OnAddPacket(const RtpPacketSendInfo& packet_info) = 0; virtual void OnTransportFeedback(const rtcp::TransportFeedback& feedback) = 0; - virtual void OnApplicationPacket(const rtcp::App& app){} }; // Interface for PacketRouter to send rtcp feedback on behalf of diff --git a/modules/rtp_rtcp/source/rtcp_receiver.cc b/modules/rtp_rtcp/source/rtcp_receiver.cc index 5aee5552e0..d64a58925b 100644 --- a/modules/rtp_rtcp/source/rtcp_receiver.cc +++ b/modules/rtp_rtcp/source/rtcp_receiver.cc @@ -1079,12 +1079,6 @@ void RTCPReceiver::TriggerCallbacksFromRtcpPacket( } } - if (transport_feedback_observer_ && - (packet_information.packet_type_flags & kRtcpApp)) { - transport_feedback_observer_->OnApplicationPacket( - *packet_information.application); - } - if (network_state_estimate_observer_ && packet_information.network_state_estimate) { network_state_estimate_observer_->OnRemoteNetworkEstimate( diff --git a/peerconnection_serverless_gcc b/peerconnection_serverless_gcc new file mode 100755 index 0000000000..9d30b1a59a Binary files /dev/null and b/peerconnection_serverless_gcc differ diff --git a/receiver_360p.json b/receiver_360p.json new file mode 100644 index 0000000000..27a09df7f0 --- /dev/null +++ b/receiver_360p.json @@ -0,0 +1,50 @@ +{ + "serverless_connection": { + "autoclose": 30, + "sender": { + "enabled": false + }, + "receiver": { + "enabled": true, + "listening_ip": "0.0.0.0", + "listening_port": 8000 + } + }, + "bwe_feedback_duration": 200, + "video_source": { + "video_disabled": { + "enabled": true + }, + "webcam": { + "enabled": false + }, + "video_file": { + "enabled": false + } + }, + "audio_source": { + "microphone": { + "enabled": false + }, + "audio_file": { + "enabled": true, + "file_path": "testmedia/test.wav" + } + }, + "save_to_file": { + "enabled": true, + "audio": { + "file_path": "outaudio.wav" + }, + "video": { + "width": 640, + "height": 360, + "fps": 25, + "file_path": "outvideo-360p.yuv" + } + }, + "logging": { + "enabled": true, + "log_output_path": "webrtc-receiver.log" + } +} diff --git a/receiver_720p.json b/receiver_720p.json new file mode 100644 index 0000000000..0a86793cbb --- /dev/null +++ b/receiver_720p.json @@ -0,0 +1,50 @@ +{ + "serverless_connection": { + "autoclose": 30, + "sender": { + "enabled": false + }, + "receiver": { + "enabled": true, + "listening_ip": "0.0.0.0", + "listening_port": 8000 + } + }, + "bwe_feedback_duration": 200, + "video_source": { + "video_disabled": { + "enabled": true + }, + "webcam": { + "enabled": false + }, + "video_file": { + "enabled": false + } + }, + "audio_source": { + "microphone": { + "enabled": false + }, + "audio_file": { + "enabled": true, + "file_path": "testmedia/test.wav" + } + }, + "save_to_file": { + "enabled": true, + "audio": { + "file_path": "outaudio.wav" + }, + "video": { + "width": 1280, + "height": 720, + "fps": 25, + "file_path": "outvideo-720p.yuv" + } + }, + "logging": { + "enabled": true, + "log_output_path": "webrtc-receiver.log" + } +} diff --git a/rtc_tools/BUILD.gn b/rtc_tools/BUILD.gn index 52a5dc2947..f293853f6e 100644 --- a/rtc_tools/BUILD.gn +++ b/rtc_tools/BUILD.gn @@ -339,8 +339,7 @@ if (!build_with_chromium) { # TODO(kwiberg): Remove this dependency. "../api/audio_codecs:audio_codecs_api", "../api/transport:field_trial_based_config", - # Revision for enabling AlphaCC and disabling GCC - "../api/transport:alpha_cc", + "../api/transport:goog_cc", "../api/transport:network_control", "../call:call_interfaces", "../call:video_stream_api", @@ -350,10 +349,8 @@ if (!build_with_chromium) { "../modules/audio_coding:audio_network_adaptor", "../modules/audio_coding:neteq_tools", "../modules/congestion_controller", - # Remove it for enabling AlphaCC and disabling GCC - # Todo: event_log_visualizer_utils doesn't work - # "../modules/congestion_controller/goog_cc:delay_based_bwe", - # "../modules/congestion_controller/goog_cc:estimators", + "../modules/congestion_controller/goog_cc:delay_based_bwe", + "../modules/congestion_controller/goog_cc:estimators", "../modules/congestion_controller/rtp:transport_feedback", "../modules/pacing", "../modules/remote_bitrate_estimator", diff --git a/sender_360p.json b/sender_360p.json new file mode 100644 index 0000000000..dc5f23ac30 --- /dev/null +++ b/sender_360p.json @@ -0,0 +1,45 @@ +{ + "serverless_connection": { + "autoclose": 30, + "sender": { + "enabled": true, + "dest_ip": "0.0.0.0", + "dest_port": 8000 + }, + "receiver": { + "enabled": false + } + }, + "bwe_feedback_duration": 200, + "video_source": { + "video_disabled": { + "enabled": false + }, + "webcam": { + "enabled": false + }, + "video_file": { + "enabled": true, + "width": 640, + "height": 360, + "fps": 25, + "file_path": "testmedia/test-360p.yuv" + } + }, + "audio_source": { + "microphone": { + "enabled": false + }, + "audio_file": { + "enabled": true, + "file_path": "testmedia/test.wav" + } + }, + "save_to_file": { + "enabled": false + }, + "logging": { + "enabled": true, + "log_output_path": "webrtc-sender.log" + } +} diff --git a/sender_720p.json b/sender_720p.json new file mode 100644 index 0000000000..dc5f23ac30 --- /dev/null +++ b/sender_720p.json @@ -0,0 +1,45 @@ +{ + "serverless_connection": { + "autoclose": 30, + "sender": { + "enabled": true, + "dest_ip": "0.0.0.0", + "dest_port": 8000 + }, + "receiver": { + "enabled": false + } + }, + "bwe_feedback_duration": 200, + "video_source": { + "video_disabled": { + "enabled": false + }, + "webcam": { + "enabled": false + }, + "video_file": { + "enabled": true, + "width": 640, + "height": 360, + "fps": 25, + "file_path": "testmedia/test-360p.yuv" + } + }, + "audio_source": { + "microphone": { + "enabled": false + }, + "audio_file": { + "enabled": true, + "file_path": "testmedia/test.wav" + } + }, + "save_to_file": { + "enabled": false + }, + "logging": { + "enabled": true, + "log_output_path": "webrtc-sender.log" + } +} diff --git a/testmedia/test-360p.yuv b/testmedia/test-360p.yuv new file mode 100644 index 0000000000..4587f35132 Binary files /dev/null and b/testmedia/test-360p.yuv differ diff --git a/testmedia/test-720p.yuv b/testmedia/test-720p.yuv new file mode 100644 index 0000000000..8d0cd436be Binary files /dev/null and b/testmedia/test-720p.yuv differ diff --git a/testmedia/test.wav b/testmedia/test.wav new file mode 100644 index 0000000000..04ff11d0d8 Binary files /dev/null and b/testmedia/test.wav differ